clear_result.py script with the appropriate parameters. The script takes the base scale a, the starting value b0, and the number of telescopic steps k.
clear_result.py с нужными параметрами. Скрипт принимает масштабный коэффициент a, начальное значение b0 и число телескопических шагов k.
import math
import sympy as sp
import argparse
from collections import Counter
def W(a, b):
return a**7 - 3*a**4*b + a*(3*b**2 - 1)
def X(a, b):
return a**7 - 3*a**4*(1 + b) + a*(2 + 6*b + 3*b**2)
def Y(a, b):
return 2*a**6 - 3*a**3*(1 + 2*b) + 1 + 3*b + 3*b**2
def Z(a, b):
return a**6 - 1 - 3*b - 3*b**2
def format_factorization(total_val):
v = int(total_val)
if abs(v) == 1:
return str(v)
factors = sp.factorint(v)
return "*".join([f"{p}^{e}" if e > 1 else f"{p}" for p, e in sorted(factors.items())])
def ru_cubes_phrase(n: int) -> str:
"""Число и слово «куб» в правильном числе (1 куб, 2 куба, 5 кубов)."""
n = abs(int(n))
if n % 10 == 1 and n % 100 != 11:
w = "куб"
elif n % 10 in (2, 3, 4) and n % 100 not in (12, 13, 14):
w = "куба"
else:
w = "кубов"
return f"{n} {w}"
def order_sides_for_display(L_final, R_final):
"""Для вывода: слева — сторона с меньшим числом кубов (при строгом неравенстве меняем местами).
Суммы кубов по обеим сторонам равны, тождество остаётся верным.
"""
L, R = list(L_final), list(R_final)
if len(L) > len(R):
return R, L
return L, R
def compute_ramanujan_decomposition(a, b_start, k, *, factorize=True):
"""Возвращает данные разложения без печати (для CLI и GUI).
factorize=False пропускает sp.factorint (быстро для проверки числа членов в GUI).
"""
# Collect all 4 polynomials for each step
b_seq = list(range(b_start, b_start + k))
w_seq = [W(a, b) for b in b_seq]
x_seq = [X(a, b) for b in b_seq]
y_seq = [Y(a, b) for b in b_seq]
z_seq = [Z(a, b) for b in b_seq]
# Base equation: W^3 - X^3 = Y^3 + Z^3
# After telescopic cancellation of the left side, we get:
# W_0^3 - X_{k-1}^3 = \sum (Y_i^3 + Z_i^3)
left_raw = [w_seq[0], -x_seq[-1]]
right_raw = []
for y, z in zip(y_seq, z_seq):
right_raw.extend([y, z])
L_vals = []
R_vals = []
for val in left_raw:
if val > 0:
L_vals.append(val)
elif val < 0:
R_vals.append(abs(val))
for val in right_raw:
if val > 0:
R_vals.append(val)
elif val < 0:
L_vals.append(abs(val))
L_counts = Counter(L_vals)
R_counts = Counter(R_vals)
common = L_counts & R_counts
L_final = list((L_counts - common).elements())
R_final = list((R_counts - common).elements())
L_final.sort()
R_final.sort()
total_terms = len(L_final) + len(R_final)
total_val = sum(x**3 for x in L_final)
factor_str = format_factorization(total_val) if factorize else None
return {
"a": a,
"b_start": b_start,
"k": k,
"L_final": L_final,
"R_final": R_final,
"total_terms": total_terms,
"total_val": total_val,
"factor_str": factor_str,
}
def try_defactored_equation_lines(L_final, R_final, width=72, *, lang="ru"):
"""Общий gcd оснований кубов по всем членам тождества (в порядке показа).
Если gcd g > 1 — основания делятся на g; в сумме кубов общий множитель g³.
Возвращает пятёрку:
(1) строка про g³ (RU/EN по lang);
(2) строка «N = факторизация(N) =» для сокращённой суммы N;
(3) строки сокращённого тождества (wrap_equation_lines);
(4) L2 — основания слева после деления на gcd (в порядке показа);
(5) R2 — основания справа после деления на gcd.
lang: \"ru\" | \"en\" — подписи к первой строке блока дефакторизации.
Если gcd == 1 — ранний выход при первом же обнулении общей части (инкрементальный gcd).
Возвращает None, если сокращать нечего или недостаточно оснований.
"""
Ld, Rd = order_sides_for_display(L_final, R_final)
bases = list(Ld) + list(Rd)
if len(bases) < 2:
return None
g = abs(int(bases[0]))
if g == 0:
return None
for b in bases[1:]:
bi = abs(int(b))
if bi == 0:
return None
g = math.gcd(g, bi)
if g == 1:
return None
if g <= 1:
return None
L2 = [int(x) // g for x in Ld]
R2 = [int(x) // g for x in Rd]
v_left = sum(x**3 for x in L2)
v_right = sum(x**3 for x in R2)
if v_left != v_right:
return None
g3 = g**3
if lang == "en":
gcd_cube_line = f"Cubed gcd of bases (g³): {g3} = {format_factorization(g3)}"
else:
gcd_cube_line = f"Куб НОД оснований (g³): {g3} = {format_factorization(g3)}"
factor_line = f"{v_left} = {format_factorization(v_left)} ="
eq_lines = wrap_equation_lines(L2, R2, width)
return (gcd_cube_line, factor_line, eq_lines, L2, R2)
def wrap_equation_lines(L_final, R_final, width=72):
tokens = []
for i, val in enumerate(L_final):
tokens.append(f"{val}^3" if i == 0 else f"+{val}^3")
tokens.append("=")
for i, val in enumerate(R_final):
tokens.append(f"{val}^3" if i == 0 else f"+{val}^3")
lines = []
current_line = ""
for token in tokens:
if token == "=":
current_line += " = "
continue
if len(current_line) + len(token) > width:
if current_line.strip():
lines.append(current_line.rstrip())
current_line = token
else:
current_line += token
if current_line.strip():
lines.append(current_line.rstrip())
return lines
def generate_ramanujan_decomposition(a, b_start, k):
data = compute_ramanujan_decomposition(a, b_start, k)
L_final = data["L_final"]
R_final = data["R_final"]
total_terms = data["total_terms"]
total_val = data["total_val"]
factor_str = data["factor_str"]
print(f"Разложение: a = {a}, b0 = {b_start}, k = {k}")
print(f"Параметры сформированы. Число членов в последовательности: {total_terms}.\n")
print("Результат:")
L_disp, R_disp = order_sides_for_display(L_final, R_final)
print(f"Слева: {ru_cubes_phrase(len(L_disp))}")
print(f"Справа: {ru_cubes_phrase(len(R_disp))}")
print("="*72)
print(f"{total_val} = {factor_str} =")
for line in wrap_equation_lines(L_disp, R_disp, 72):
print(line)
print("="*72)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate Ramanujan cube decompositions.")
parser.add_argument("--a", type=int, default=2, help="Parameter a (default: 2)")
parser.add_argument("--b", type=int, default=10, help="Starting value for b (default: 10)")
parser.add_argument("--k", type=int, default=5, help="Number of steps (default: 5)")
args = parser.parse_args()
generate_ramanujan_decomposition(args.a, args.b, args.k)
import math
import sympy as sp
import argparse
from collections import Counter
def W(a, b):
return a**7 - 3*a**4*b + a*(3*b**2 - 1)
def X(a, b):
return a**7 - 3*a**4*(1 + b) + a*(2 + 6*b + 3*b**2)
def Y(a, b):
return 2*a**6 - 3*a**3*(1 + 2*b) + 1 + 3*b + 3*b**2
def Z(a, b):
return a**6 - 1 - 3*b - 3*b**2
def format_factorization(total_val):
v = int(total_val)
if abs(v) == 1:
return str(v)
factors = sp.factorint(v)
return "*".join([f"{p}^{e}" if e > 1 else f"{p}" for p, e in sorted(factors.items())])
def ru_cubes_phrase(n: int) -> str:
"""Число и слово «куб» в правильном числе (1 куб, 2 куба, 5 кубов)."""
n = abs(int(n))
if n % 10 == 1 and n % 100 != 11:
w = "куб"
elif n % 10 in (2, 3, 4) and n % 100 not in (12, 13, 14):
w = "куба"
else:
w = "кубов"
return f"{n} {w}"
def order_sides_for_display(L_final, R_final):
"""Для вывода: слева — сторона с меньшим числом кубов (при строгом неравенстве меняем местами).
Суммы кубов по обеим сторонам равны, тождество остаётся верным.
"""
L, R = list(L_final), list(R_final)
if len(L) > len(R):
return R, L
return L, R
def compute_ramanujan_decomposition(a, b_start, k, *, factorize=True):
"""Возвращает данные разложения без печати (для CLI и GUI).
factorize=False пропускает sp.factorint (быстро для проверки числа членов в GUI).
"""
# Collect all 4 polynomials for each step
b_seq = list(range(b_start, b_start + k))
w_seq = [W(a, b) for b in b_seq]
x_seq = [X(a, b) for b in b_seq]
y_seq = [Y(a, b) for b in b_seq]
z_seq = [Z(a, b) for b in b_seq]
# Base equation: W^3 - X^3 = Y^3 + Z^3
# After telescopic cancellation of the left side, we get:
# W_0^3 - X_{k-1}^3 = \sum (Y_i^3 + Z_i^3)
left_raw = [w_seq[0], -x_seq[-1]]
right_raw = []
for y, z in zip(y_seq, z_seq):
right_raw.extend([y, z])
L_vals = []
R_vals = []
for val in left_raw:
if val > 0:
L_vals.append(val)
elif val < 0:
R_vals.append(abs(val))
for val in right_raw:
if val > 0:
R_vals.append(val)
elif val < 0:
L_vals.append(abs(val))
L_counts = Counter(L_vals)
R_counts = Counter(R_vals)
common = L_counts & R_counts
L_final = list((L_counts - common).elements())
R_final = list((R_counts - common).elements())
L_final.sort()
R_final.sort()
total_terms = len(L_final) + len(R_final)
total_val = sum(x**3 for x in L_final)
factor_str = format_factorization(total_val) if factorize else None
return {
"a": a,
"b_start": b_start,
"k": k,
"L_final": L_final,
"R_final": R_final,
"total_terms": total_terms,
"total_val": total_val,
"factor_str": factor_str,
}
def try_defactored_equation_lines(L_final, R_final, width=72, *, lang="ru"):
"""Общий gcd оснований кубов по всем членам тождества (в порядке показа).
Если gcd g > 1 — основания делятся на g; в сумме кубов общий множитель g³.
Возвращает пятёрку:
(1) строка про g³ (RU/EN по lang);
(2) строка «N = факторизация(N) =» для сокращённой суммы N;
(3) строки сокращённого тождества (wrap_equation_lines);
(4) L2 — основания слева после деления на gcd (в порядке показа);
(5) R2 — основания справа после деления на gcd.
lang: \"ru\" | \"en\" — подписи к первой строке блока дефакторизации.
Если gcd == 1 — ранний выход при первом же обнулении общей части (инкрементальный gcd).
Возвращает None, если сокращать нечего или недостаточно оснований.
"""
Ld, Rd = order_sides_for_display(L_final, R_final)
bases = list(Ld) + list(Rd)
if len(bases) < 2:
return None
g = abs(int(bases[0]))
if g == 0:
return None
for b in bases[1:]:
bi = abs(int(b))
if bi == 0:
return None
g = math.gcd(g, bi)
if g == 1:
return None
if g <= 1:
return None
L2 = [int(x) // g for x in Ld]
R2 = [int(x) // g for x in Rd]
v_left = sum(x**3 for x in L2)
v_right = sum(x**3 for x in R2)
if v_left != v_right:
return None
g3 = g**3
if lang == "en":
gcd_cube_line = f"Cubed gcd of bases (g³): {g3} = {format_factorization(g3)}"
else:
gcd_cube_line = f"Куб НОД оснований (g³): {g3} = {format_factorization(g3)}"
factor_line = f"{v_left} = {format_factorization(v_left)} ="
eq_lines = wrap_equation_lines(L2, R2, width)
return (gcd_cube_line, factor_line, eq_lines, L2, R2)
def wrap_equation_lines(L_final, R_final, width=72):
tokens = []
for i, val in enumerate(L_final):
tokens.append(f"{val}^3" if i == 0 else f"+{val}^3")
tokens.append("=")
for i, val in enumerate(R_final):
tokens.append(f"{val}^3" if i == 0 else f"+{val}^3")
lines = []
current_line = ""
for token in tokens:
if token == "=":
current_line += " = "
continue
if len(current_line) + len(token) > width:
if current_line.strip():
lines.append(current_line.rstrip())
current_line = token
else:
current_line += token
if current_line.strip():
lines.append(current_line.rstrip())
return lines
def generate_ramanujan_decomposition(a, b_start, k):
data = compute_ramanujan_decomposition(a, b_start, k)
L_final = data["L_final"]
R_final = data["R_final"]
total_terms = data["total_terms"]
total_val = data["total_val"]
factor_str = data["factor_str"]
print(f"Разложение: a = {a}, b0 = {b_start}, k = {k}")
print(f"Параметры сформированы. Число членов в последовательности: {total_terms}.\n")
print("Результат:")
L_disp, R_disp = order_sides_for_display(L_final, R_final)
print(f"Слева: {ru_cubes_phrase(len(L_disp))}")
print(f"Справа: {ru_cubes_phrase(len(R_disp))}")
print("="*72)
print(f"{total_val} = {factor_str} =")
for line in wrap_equation_lines(L_disp, R_disp, 72):
print(line)
print("="*72)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate Ramanujan cube decompositions.")
parser.add_argument("--a", type=int, default=2, help="Parameter a (default: 2)")
parser.add_argument("--b", type=int, default=10, help="Starting value for b (default: 10)")
parser.add_argument("--k", type=int, default=5, help="Number of steps (default: 5)")
args = parser.parse_args()
generate_ramanujan_decomposition(args.a, args.b, args.k)
python clear_result.py --a 3 --b -16 --k 15 Generating decomposition for a = 3, b0 = -16, k = 15 Generated parameters. Sequence length will be: 32 terms. Result: Left: 1 cube Right: 31 cubes ======================================================================== 587638181376 = 2^9*3^3*349^3 = 8376^3 = 8^3+98^3+182^3+260^3+332^3+398^3+458^3+512^3+560^3+602^3+638^3 +668^3+692^3+710^3+722^3+1708^3+1882^3+2062^3+2248^3+2436^3+2440^3 +2638^3+2842^3+3052^3+3268^3+3490^3+3718^3+3952^3+4192^3+4438^3+4690^3 ========================================================================
python clear_result.py --a 3 --b -16 --k 15 Разложение: a = 3, b0 = -16, k = 15 Параметры сформированы. Число членов в последовательности: 32. Результат: Слева: 1 куб Справа: 31 куб ======================================================================== 587638181376 = 2^9*3^3*349^3 = 8376^3 = 8^3+98^3+182^3+260^3+332^3+398^3+458^3+512^3+560^3+602^3+638^3 +668^3+692^3+710^3+722^3+1708^3+1882^3+2062^3+2248^3+2436^3+2440^3 +2638^3+2842^3+3052^3+3268^3+3490^3+3718^3+3952^3+4192^3+4438^3+4690^3 ========================================================================
decompose_gui_EN.py)clear_result.py.
decompose_gui.py)clear_result.py.
"""
GUI for Ramanujan telescopic cube decomposition experiments.
Place clear_result.py in the same folder (download both from publication 5 on the site).
Run:
python decompose_gui_EN.py
"""
import sys
import traceback
from typing import Optional, Tuple
import webbrowser
from pathlib import Path
import sympy as sp
import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk, messagebox
_DIR = Path(__file__).resolve().parent
if str(_DIR) not in sys.path:
sys.path.insert(0, str(_DIR))
from clear_result import ( # noqa: E402
compute_ramanujan_decomposition,
format_factorization,
order_sides_for_display,
try_defactored_equation_lines,
wrap_equation_lines,
)
AUTHOR_SITE_URL = "https://nvvorobtsov.github.io/"
# g = gcd of all bases before division; factoring out g from each base divides the cube sum by g³.
DEFACTOR_SECTION_TITLE = "After defactorization (g³ — cube of gcd of all bases):"
TITLE_FACTORED_CUBES = "With full factorization of the reduced identity*:"
TITLE_FACTORED_TABLE = "Or: factorizations of the bases only*:"
FOOTNOTE_DEFACT_REDUCED = "* The reduced identity after defactorization."
TAG_PRIME_TABLE_ROW = "prime_table_row"
TABLE_PRIME_BLUE = "#1565C0"
def _highlight_prime_or_unit_base(x) -> bool:
"""Highlight table row: prime base or 1 (no nontrivial factorization)."""
v = abs(int(x))
if v == 1:
return True
return v >= 2 and bool(sp.isprime(v))
def en_cubes_phrase(n: int) -> str:
"""English count + 'cube' / 'cubes'."""
n = abs(int(n))
if n == 1:
return "1 cube"
return f"{n} cubes"
def _actual_text_widget(st: tk.Widget) -> tk.Text:
"""ScrolledText may expose the inner Text as self or as a child, depending on Python version."""
inner = getattr(st, "text", None)
if isinstance(inner, tk.Text):
return inner
if isinstance(st, tk.Text):
return st
for ch in st.winfo_children():
if isinstance(ch, tk.Text):
return ch
return st # nonstandard wrapper fallback
def _estimate_wrap_chars(text_w: tk.Widget) -> int:
"""Approximate monospace character count that fits the widget width (for line wrapping)."""
tw = _actual_text_widget(text_w)
tw.update_idletasks()
px = tw.winfo_width()
if px < 80:
px = max(tw.winfo_reqwidth(), 720)
try:
fn = tkfont.Font(font=tw.cget("font"))
cw = fn.measure("8")
if cw <= 0:
cw = 7
except tk.TclError:
cw = 7
inner = max(px - 28, 64)
n = max(inner // cw, 48)
return min(int(n), 5000)
# (a, b₀, k, term count) — with b₀=0 and these a,k, one cube on the left of «=», the rest on the right, no «-»
PRESET_EXAMPLES = [
(50, 0, 4, 10),
(50, 0, 49, 100),
(50, 0, 499, 1000),
(50, 0, 4999, 10000),
(50, 0, 24999, 50000),
(50, 0, 49999, 100000),
(100, 0, 249999, 500000),
]
PRESET_TARGETS_EN = [
"~10 terms, left: 1 cube",
"~100, left: 1 cube",
"~1000, left: 1 cube",
"~10000, left: 1 cube",
"~50000, left: 1 cube",
"~100000, left: 1 cube",
"~500000, left: 1 cube (a=100)",
]
def build_output_parts(data: dict, *, wrap_width: int = 72):
"""Full output: header, wrapped equation lines, bottom bar of =."""
bar_w = min(max(wrap_width, 40), 200)
sep = "=" * bar_w
Ld, Rd = order_sides_for_display(data["L_final"], data["R_final"])
header = [
(
f"Decomposition: a = {data['a']}, b₀ = {data['b_start']}, k = {data['k']}"
),
f"Parameters set. Number of terms in the sequence: {data['total_terms']}.",
"",
"Result:",
f"Left: {en_cubes_phrase(len(Ld))}",
f"Right: {en_cubes_phrase(len(Rd))}",
sep,
f"{data['total_val']} = {data['factor_str']} =",
]
eq_lines = wrap_equation_lines(Ld, Rd, wrap_width)
return header, eq_lines, sep
def wrap_factored_cube_equation(L: list, R: list, width: int) -> list:
"""Equation lines (like wrap_equation_lines) with fully factored bases in parentheses."""
tokens = []
for i, val in enumerate(L):
fx = format_factorization(val)
tok = f"({fx})^3"
tokens.append(tok if i == 0 else f"+{tok}")
tokens.append("=")
for i, val in enumerate(R):
fx = format_factorization(val)
tok = f"({fx})^3"
tokens.append(tok if i == 0 else f"+{tok}")
lines = []
current_line = ""
for token in tokens:
if token == "=":
current_line += " = "
continue
if len(current_line) + len(token) > width:
if current_line.strip():
lines.append(current_line.rstrip())
current_line = token
else:
current_line += token
if current_line.strip():
lines.append(current_line.rstrip())
return lines
def format_bases_factor_table(L: list, R: list) -> tuple[list[str], list[Optional[str]]]:
"""Table: integer base | format_factorization (monospace).
Second list: per-line tags (None or TAG_PRIME_TABLE_ROW for data rows with prime base).
"""
bases_order = [int(x) for x in L] + [int(x) for x in R]
rows = []
for x in L:
rows.append((str(int(x)), format_factorization(x)))
for x in R:
rows.append((str(int(x)), format_factorization(x)))
h0, h1 = "Base", "Factorization"
wn = max(len(h0), max((len(r[0]) for r in rows), default=0))
wf = max(len(h1), max((len(r[1]) for r in rows), default=0))
sep = "+" + "-" * (wn + 2) + "+" + "-" * (wf + 2) + "+"
out = [
sep,
"| " + h0.rjust(wn) + " | " + h1.ljust(wf) + " |",
sep,
]
tags: list[Optional[str]] = [None, None, None]
for i, (n, fac) in enumerate(rows):
out.append("| " + n.rjust(wn) + " | " + fac.ljust(wf) + " |")
tags.append(TAG_PRIME_TABLE_ROW if _base_is_prime(bases_order[i]) else None)
out.append(sep)
tags.append(None)
return out, tags
def _factor_string_tokens(fac_str: str) -> list[str]:
s = (fac_str or "").strip()
if not s:
return []
return s.split("*")
def format_bases_factor_table_by_columns(
L: list,
R: list,
*,
h_base: str = "Base",
) -> tuple[list[str], list[Optional[str]]]:
"""Table: base | factor 1 | factor 2 | … (monospace, factor cells right-aligned).
Second list: per-line tags (same convention as format_bases_factor_table).
"""
bases_order = [int(x) for x in L] + [int(x) for x in R]
rows = []
for x in L:
rows.append((str(int(x)), format_factorization(x)))
for x in R:
rows.append((str(int(x)), format_factorization(x)))
factor_rows = [_factor_string_tokens(f) for _, f in rows]
nf = max((len(fr) for fr in factor_rows), default=0)
if nf < 1:
nf = 1
wn = max(len(h_base), max((len(r[0]) for r in rows), default=0))
col_widths = []
for j in range(nf):
h = str(j + 1)
w = len(h)
for fr in factor_rows:
if j < len(fr):
w = max(w, len(fr[j]))
col_widths.append(max(w, 1))
parts = ["+" + "-" * (wn + 2)] + ["+" + "-" * (cw + 2) for cw in col_widths] + ["+"]
sep = "".join(parts)
header_line = "| " + h_base.rjust(wn) + " |"
for j, cw in enumerate(col_widths):
header_line += " " + str(j + 1).center(cw) + " |"
out = [sep, header_line, sep]
tags: list[Optional[str]] = [None, None, None]
for row_i, ((_n, _f), fr) in enumerate(zip(rows, factor_rows)):
row_line = "| " + _n.rjust(wn) + " |"
for j, cw in enumerate(col_widths):
cell = fr[j] if j < len(fr) else ""
row_line += " " + cell.rjust(cw) + " |"
out.append(row_line)
tags.append(TAG_PRIME_TABLE_ROW if _highlight_prime_or_unit_base(bases_order[row_i]) else None)
out.append(sep)
tags.append(None)
return out, tags
def build_full_factor_display(
L2: list,
R2: list,
wrap_width: int,
*,
bases_only: bool,
factor_columns: bool = False,
):
"""(mode, lines, line_tags): line_tags aligned with lines, or None for cubes mode."""
foot = FOOTNOTE_DEFACT_REDUCED
if bases_only:
if factor_columns:
body, body_tags = format_bases_factor_table_by_columns(L2, R2)
else:
body, body_tags = format_bases_factor_table(L2, R2)
lines = [TITLE_FACTORED_TABLE] + body + [foot]
line_tags = [None] + body_tags + [None]
return ("table", lines, line_tags)
body = wrap_factored_cube_equation(L2, R2, wrap_width)
lines = [TITLE_FACTORED_CUBES] + body + [foot]
return ("cubes", lines, None)
def between_separators_one_line(
data: dict,
wrap_width: int,
defactor_bundle: Optional[Tuple[str, str, list, list, list]] = None,
omit_raw_decomposition: bool = False,
between_tail_lines: Optional[list] = None,
) -> str:
"""Text between === lines without wrapping (single logical line) for clipboard copy."""
tail = ("\n" + "\n".join(between_tail_lines)) if between_tail_lines else ""
if omit_raw_decomposition and defactor_bundle:
d_g3, d_n, d_eq, _L2, _R2 = defactor_bundle
s = (
DEFACTOR_SECTION_TITLE
+ "\n"
+ d_g3
+ "\n"
+ d_n
+ "\n"
+ "\n".join(d_eq)
)
return s + tail
header, eq_lines, _ = build_output_parts(data, wrap_width=wrap_width)
factor_line = header[-1]
s = factor_line.rstrip() + "".join(eq_lines)
if defactor_bundle:
d_g3, d_n, d_eq, _L2, _R2 = defactor_bundle
s += (
"\n"
+ DEFACTOR_SECTION_TITLE
+ "\n"
+ d_g3
+ "\n"
+ d_n
+ "\n"
+ "\n".join(d_eq)
)
return s + tail
def build_output(data: dict, term_count_only: bool, *, wrap_width: int = 72) -> str:
if term_count_only:
Ld, Rd = order_sides_for_display(data["L_final"], data["R_final"])
return (
f"a = {data['a']}, b₀ = {data['b_start']}, k = {data['k']}\n"
f"Terms in decomposition (after reductions): {data['total_terms']}\n"
f"Left: {en_cubes_phrase(len(Ld))}\n"
f"Right: {en_cubes_phrase(len(Rd))}\n"
)
h, e, s = build_output_parts(data, wrap_width=wrap_width)
return "\n".join(h + e + [s])
def _insert_eq_lines_bold_left(tw: tk.Text, lines: list, tag: str) -> None:
"""Left-hand side up to first « = » in bold (tag)."""
left_zone = True
for line in lines:
if left_zone:
if " = " in line:
left_part, _, rest = line.partition(" = ")
tw.insert(tk.END, left_part, (tag,))
tw.insert(tk.END, " = " + rest + "\n")
left_zone = False
else:
tw.insert(tk.END, line + "\n", (tag,))
else:
tw.insert(tk.END, line + "\n")
def insert_result_with_bold_left_side(
tw: tk.Text,
header: list,
eq_lines: list,
sep: str,
defactor_bundle: Optional[Tuple[str, str, list, list, list]] = None,
full_factor_display: Optional[Tuple[str, list, Optional[list]]] = None,
) -> None:
"""In equation lines, left side up to first « = » is bold blue (#1565C0)."""
tag = "decomp_left"
blue = "#1565C0"
try:
base = tkfont.Font(font=tw.cget("font"))
fam = base.actual("family")
sz = int(base.actual("size"))
tw.tag_configure(tag, font=(fam, sz, "bold"), foreground=blue)
except tk.TclError:
tw.tag_configure(tag, font=("Consolas", 10, "bold"), foreground=blue)
for line in header:
tw.insert(tk.END, line + "\n")
_insert_eq_lines_bold_left(tw, eq_lines, tag)
if defactor_bundle:
d_g3, d_n, d_eq, _L2, _R2 = defactor_bundle
if eq_lines:
tw.insert(tk.END, "\n")
tw.insert(tk.END, DEFACTOR_SECTION_TITLE + "\n")
tw.insert(tk.END, d_g3 + "\n")
tw.insert(tk.END, d_n + "\n")
_insert_eq_lines_bold_left(tw, d_eq, tag)
if full_factor_display:
mode, lines, line_tags = full_factor_display
tw.insert(tk.END, "\n")
tw.insert(tk.END, lines[0] + "\n")
mid = lines[1:-1]
if mode == "cubes":
_insert_eq_lines_bold_left(tw, mid, tag)
else:
if line_tags:
try:
bf = tkfont.Font(font=tw.cget("font"))
fam = bf.actual("family")
sz = int(bf.actual("size"))
tw.tag_configure(
TAG_PRIME_TABLE_ROW,
font=(fam, sz, "bold"),
foreground=TABLE_PRIME_BLUE,
)
except tk.TclError:
tw.tag_configure(
TAG_PRIME_TABLE_ROW,
font=("Consolas", 10, "bold"),
foreground=TABLE_PRIME_BLUE,
)
for i, ln in enumerate(mid):
tidx = 1 + i
tg = (
line_tags[tidx]
if line_tags and tidx < len(line_tags) and line_tags[tidx]
else None
)
if tg:
tw.insert(tk.END, ln + "\n", (tg,))
else:
tw.insert(tk.END, ln + "\n")
tw.insert(tk.END, lines[-1] + "\n")
tw.insert(tk.END, sep + "\n")
def main():
root = tk.Tk()
root.title("Ramanujan decomposition — parameters a, b₀, k")
root.minsize(640, 880)
frm = ttk.Frame(root, padding=12)
frm.grid(row=0, column=0, sticky="nsew")
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frm.columnconfigure(1, weight=1)
frm.rowconfigure(9, weight=1)
var_a = tk.IntVar(value=2)
var_b = tk.IntVar(value=10)
var_k = tk.IntVar(value=5)
var_warn_max = tk.IntVar(value=50000)
ttk.Label(frm, text="a:").grid(row=0, column=0, sticky="w", pady=2)
sp_a = tk.Spinbox(
frm,
from_=-300,
to=300,
increment=1,
textvariable=var_a,
width=12,
)
sp_a.grid(row=0, column=1, sticky="w", pady=2)
ttk.Label(frm, text="b₀ (initial b):").grid(row=1, column=0, sticky="w", pady=2)
sp_b = tk.Spinbox(
frm,
from_=-20000,
to=20000,
increment=1,
textvariable=var_b,
width=12,
)
sp_b.grid(row=1, column=1, sticky="w", pady=2)
ttk.Label(frm, text="k (number of steps):").grid(row=2, column=0, sticky="w", pady=2)
sp_k = tk.Spinbox(
frm,
from_=1,
to=300000,
increment=1,
textvariable=var_k,
width=12,
)
sp_k.grid(row=2, column=1, sticky="w", pady=2)
only_count = tk.BooleanVar(value=False)
chk = ttk.Checkbutton(
frm,
text="Term count only (no decomposition lines)",
variable=only_count,
)
chk.grid(row=3, column=0, columnspan=2, sticky="w", pady=(8, 4))
var_defactor = tk.BooleanVar(value=True)
chk_def = ttk.Checkbutton(
frm,
text=(
"Defactorization check (g³ = (gcd of bases)³, N = factorization(N) =, reduced "
"identity; if gcd>1 the raw decomposition is hidden; if gcd=1 — full output)"
),
variable=var_defactor,
)
chk_def.grid(row=4, column=0, columnspan=2, sticky="w", pady=(0, 4))
var_full_factor = tk.BooleanVar(value=False)
var_bases_only = tk.BooleanVar(value=True)
opt_sub = ttk.Frame(frm)
opt_sub.grid(row=5, column=0, columnspan=2, sticky="w", pady=(0, 4))
chk_ff = ttk.Checkbutton(
opt_sub,
text=(
"Full factorization of reduced identity (cubes with factored bases; "
"extra block below defactorization)"
),
variable=var_full_factor,
)
chk_ff.pack(side=tk.LEFT)
chk_bo = ttk.Checkbutton(
opt_sub,
text="Bases only (table, no ^3)",
variable=var_bases_only,
)
chk_bo.pack(side=tk.LEFT, padx=(18, 0))
var_factor_columns = tk.BooleanVar(value=False)
chk_fc = ttk.Checkbutton(
opt_sub,
text="Factors in separate table columns",
variable=var_factor_columns,
)
chk_fc.pack(side=tk.LEFT, padx=(18, 0))
def sync_factor_suboptions(*_args):
ff = var_full_factor.get()
bo = var_bases_only.get()
st_sub = tk.NORMAL if ff else tk.DISABLED
chk_bo.configure(state=st_sub)
chk_fc.configure(state=tk.NORMAL if (ff and bo) else tk.DISABLED)
var_full_factor.trace_add("write", sync_factor_suboptions)
var_bases_only.trace_add("write", sync_factor_suboptions)
sync_factor_suboptions()
ttk.Label(
frm,
text=(
"Max decomposition terms — warn if output is large\n"
"(only when «term count only» is unchecked):"
),
justify=tk.LEFT,
).grid(row=6, column=0, sticky="nw", pady=2)
sp_warn = tk.Spinbox(
frm,
from_=4,
to=2_000_000,
increment=1,
textvariable=var_warn_max,
width=12,
)
sp_warn.grid(row=6, column=1, sticky="w", pady=2)
ex_frame = ttk.LabelFrame(frm, text="Examples: parameter triple → term count (reference)")
ex_frame.grid(row=7, column=0, columnspan=2, sticky="ew", pady=(10, 6))
ex_frame.columnconfigure(0, weight=1)
ttk.Label(ex_frame, text="Parameters a, b₀, k").grid(row=0, column=0, sticky="w", padx=4, pady=2)
ttk.Label(ex_frame, text="Terms").grid(row=0, column=1, sticky="w", padx=4, pady=2)
ttk.Label(ex_frame, text="").grid(row=0, column=2, padx=2, pady=2)
def make_apply(a_i, b_i, k_i):
def _apply():
var_a.set(a_i)
var_b.set(b_i)
var_k.set(k_i)
return _apply
for i, ((a_i, b_i, k_i, cnt), hint) in enumerate(
zip(PRESET_EXAMPLES, PRESET_TARGETS_EN), start=1
):
param_txt = f"a = {a_i}, b₀ = {b_i}, k = {k_i}"
row_txt = f"{param_txt} ({hint})"
ttk.Label(ex_frame, text=row_txt).grid(row=i, column=0, sticky="w", padx=4, pady=1)
ttk.Label(ex_frame, text=str(cnt)).grid(row=i, column=1, sticky="e", padx=8, pady=1)
ttk.Button(ex_frame, text="Apply", width=11, command=make_apply(a_i, b_i, k_i)).grid(
row=i, column=2, padx=4, pady=1
)
def clipboard_set(text: str) -> None:
root.clipboard_clear()
root.clipboard_append(text)
root.update()
clip_block_state = {
"data": None,
"wrap": None,
"defactor_bundle": None,
"omit_raw_decomposition": False,
"between_tail_lines": None,
}
def copy_between_separators() -> None:
d = clip_block_state["data"]
w = clip_block_state["wrap"]
if d is None or w is None:
messagebox.showinfo(
"No block",
"Run a full computation first (without «term count only») "
"so text between the = bars is available.",
)
return
clipboard_set(
between_separators_one_line(
d,
w,
clip_block_state.get("defactor_bundle"),
clip_block_state.get("omit_raw_decomposition", False),
clip_block_state.get("between_tail_lines"),
)
)
out_wrap = ttk.Frame(frm)
out_wrap.grid(row=9, column=0, columnspan=2, sticky="nsew", pady=(4, 0))
out_wrap.rowconfigure(1, weight=1)
out_wrap.columnconfigure(0, weight=1)
out_top = ttk.Frame(out_wrap)
out_top.grid(row=0, column=0, sticky="ew")
ttk.Label(out_top, text="Output").pack(side=tk.LEFT)
text_holder = ttk.Frame(out_wrap)
text_holder.grid(row=1, column=0, sticky="nsew")
text_holder.rowconfigure(0, weight=1)
text_holder.columnconfigure(0, weight=1)
v_scroll = ttk.Scrollbar(text_holder, orient=tk.VERTICAL)
h_scroll = ttk.Scrollbar(text_holder, orient=tk.HORIZONTAL)
tw = tk.Text(
text_holder,
height=28,
width=88,
font=("Consolas", 10),
wrap=tk.NONE,
exportselection=True,
undo=False,
xscrollcommand=h_scroll.set,
yscrollcommand=v_scroll.set,
)
v_scroll.config(command=tw.yview)
h_scroll.config(command=tw.xview)
tw.grid(row=0, column=0, sticky="nsew")
v_scroll.grid(row=0, column=1, sticky="ns")
h_scroll.grid(row=1, column=0, sticky="ew")
def copy_all_out() -> None:
clipboard_set(tw.get("1.0", "end-1c"))
def copy_selection(_event=None):
if not tw.tag_ranges("sel"):
return None
try:
clipboard_set(tw.get("sel.first", "sel.last"))
except tk.TclError:
return None
return "break"
for seq in ("<Control-c>", "<Control-C>", "<Control-Insert>"):
tw.bind(seq, copy_selection)
tw.bind("<<Copy>>", copy_selection)
tw.bind("<Button-1>", lambda e: tw.focus_set())
ctx = tk.Menu(root, tearoff=0)
ctx.add_command(label="Copy", command=lambda: copy_selection())
def show_ctx(event):
try:
ctx.tk_popup(event.x_root, event.y_root)
finally:
ctx.grab_release()
tw.bind("<Button-3>", show_ctx)
# Copy all — Copy glyph (Segoe MDL2)
copy_all_btn = tk.Button(
out_top,
text="\uE8C8",
font=("Segoe MDL2 Assets", 12),
command=copy_all_out,
cursor="hand2",
relief=tk.GROOVE,
padx=6,
pady=0,
takefocus=False,
)
copy_all_btn.pack(side=tk.RIGHT, padx=(8, 0))
# Between === as one line (no wrap)
copy_between_btn = tk.Button(
out_top,
text="\u2261",
font=("Segoe UI Symbol", 12),
command=copy_between_separators,
cursor="hand2",
relief=tk.GROOVE,
padx=5,
pady=0,
takefocus=False,
)
copy_between_btn.pack(side=tk.RIGHT, padx=(0, 4))
def run_compute():
try:
a = var_a.get()
b0 = var_b.get()
k = var_k.get()
warn_max = var_warn_max.get()
except tk.TclError:
messagebox.showerror("Parameters", "Enter integers in all fields.")
return
if k < 1:
messagebox.showerror("Parameters", "k must be ≥ 1")
return
if a == 0:
messagebox.showerror("Parameters", "a must not be 0")
return
if warn_max < 1:
messagebox.showerror("Parameters", "Warning threshold must be ≥ 1")
return
tw.delete("1.0", tk.END)
tw.insert(tk.END, "Computing…\n")
tw.update_idletasks()
use_styled = False
text = ""
header = eq_lines = sep_line = None
defactor_bundle = None
full_factor_display = None
between_tail_lines = None
clip_block_state["data"] = None
clip_block_state["wrap"] = None
clip_block_state["defactor_bundle"] = None
clip_block_state["omit_raw_decomposition"] = False
clip_block_state["between_tail_lines"] = None
try:
data = compute_ramanujan_decomposition(a, b0, k, factorize=False)
want_full = not only_count.get()
if want_full and data["total_terms"] > warn_max:
ok = messagebox.askyesno(
"Large output",
(
f"Terms in decomposition: {data['total_terms']}\n"
f"This exceeds the threshold ({warn_max}). Full output and factorization "
f"may take a while.\n\n"
f"Continue?"
),
icon="warning",
)
if not ok:
clip_block_state["data"] = None
clip_block_state["wrap"] = None
clip_block_state["defactor_bundle"] = None
clip_block_state["omit_raw_decomposition"] = False
clip_block_state["between_tail_lines"] = None
tw.delete("1.0", tk.END)
tw.insert(
tk.END,
f"Cancelled. Would have been {data['total_terms']} terms "
f"(warning threshold: {warn_max}).\n",
)
return
if want_full:
data = {
**data,
"factor_str": format_factorization(data["total_val"]),
}
wrap_w = _estimate_wrap_chars(tw)
if only_count.get():
text = build_output(data, True, wrap_width=wrap_w)
else:
header, eq_lines, sep_line = build_output_parts(data, wrap_width=wrap_w)
if var_defactor.get():
defactor_bundle = try_defactored_equation_lines(
data["L_final"], data["R_final"], wrap_w, lang="en"
)
if var_defactor.get() and defactor_bundle is not None:
header = header[:-1]
eq_lines = []
if (
defactor_bundle is not None
and var_full_factor.get()
):
_g3, _n, _eq, L2, R2 = defactor_bundle
full_factor_display = build_full_factor_display(
L2,
R2,
wrap_w,
bases_only=var_bases_only.get(),
factor_columns=var_bases_only.get()
and var_factor_columns.get(),
)
between_tail_lines = list(full_factor_display[1])
use_styled = True
except Exception:
use_styled = False
text = traceback.format_exc()
tw.delete("1.0", tk.END)
if use_styled:
insert_result_with_bold_left_side(
tw,
header,
eq_lines,
sep_line,
defactor_bundle,
full_factor_display,
)
clip_block_state["data"] = data
clip_block_state["wrap"] = wrap_w
clip_block_state["defactor_bundle"] = defactor_bundle
clip_block_state["omit_raw_decomposition"] = bool(
var_defactor.get() and defactor_bundle is not None
)
clip_block_state["between_tail_lines"] = between_tail_lines
else:
tw.insert(tk.END, text)
btn_row = ttk.Frame(frm)
btn_row.grid(row=8, column=0, columnspan=2, sticky="ew", pady=6)
btn_row.columnconfigure(0, weight=1)
ttk.Button(btn_row, text="Compute", command=run_compute).grid(row=0, column=0, sticky="w")
def open_author_site():
webbrowser.open(AUTHOR_SITE_URL)
ttk.Button(btn_row, text="Author's site", command=open_author_site).grid(
row=0, column=1, sticky="e"
)
for w in (sp_a, sp_b, sp_k, sp_warn):
w.bind("<Return>", lambda e: run_compute())
root.mainloop()
if __name__ == "__main__":
main()
"""
GUI для экспериментов с телескопическим разложением.
Файл clear_result.py должен лежать в той же папке (скачайте оба с сайта публикации 5).
Запуск:
python decompose_gui.py
"""
import sys
import traceback
from typing import Optional, Tuple
import webbrowser
from pathlib import Path
import sympy as sp
import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk, messagebox
_DIR = Path(__file__).resolve().parent
if str(_DIR) not in sys.path:
sys.path.insert(0, str(_DIR))
from clear_result import ( # noqa: E402
compute_ramanujan_decomposition,
format_factorization,
order_sides_for_display,
ru_cubes_phrase,
try_defactored_equation_lines,
wrap_equation_lines,
)
AUTHOR_SITE_URL = "https://nvvorobtsov.github.io/"
# Заголовок блока: g — НОД всех оснований кубов до деления; сокращение на g даёт тождество для суммы, делённой на g³.
DEFACTOR_SECTION_TITLE = "После дефакторизации (g³ — куб НОД всех оснований):"
TITLE_FACTORED_CUBES = "С полной факторизацией результата*:"
TITLE_FACTORED_TABLE = "Или с факторизацией оснований результата*:"
FOOTNOTE_DEFACT_REDUCED = "* Имеется в виду сокращённое тождество после дефакторизации."
TAG_PRIME_TABLE_ROW = "prime_table_row"
TABLE_PRIME_BLUE = "#1565C0"
def _highlight_prime_or_unit_base(x) -> bool:
"""Подсветка строки таблицы: простое основание или 1 (у 1 нет нетривиальной факторизации)."""
v = abs(int(x))
if v == 1:
return True
return v >= 2 and bool(sp.isprime(v))
def _actual_text_widget(st: tk.Widget) -> tk.Text:
"""У ScrolledText реальный Text может быть self или дочерний — от версии Python."""
inner = getattr(st, "text", None)
if isinstance(inner, tk.Text):
return inner
if isinstance(st, tk.Text):
return st
for ch in st.winfo_children():
if isinstance(ch, tk.Text):
return ch
return st # на случай нестандартной обёртки
def _estimate_wrap_chars(text_w: tk.Widget) -> int:
"""Сколько символов моноширинного шрифта помещается по ширине виджета (для рубки строк)."""
tw = _actual_text_widget(text_w)
tw.update_idletasks()
px = tw.winfo_width()
if px < 80:
px = max(tw.winfo_reqwidth(), 720)
try:
fn = tkfont.Font(font=tw.cget("font"))
cw = fn.measure("8")
if cw <= 0:
cw = 7
except tk.TclError:
cw = 7
inner = max(px - 28, 64)
n = max(inner // cw, 48)
return min(int(n), 5000)
# (a, b₀, k, число членов) — при b₀=0 и этих a,k слева от «=» ровно один куб, справа — остальное, без «-»
PRESET_EXAMPLES = [
(50, 0, 4, 10),
(50, 0, 49, 100),
(50, 0, 499, 1000),
(50, 0, 4999, 10000),
(50, 0, 24999, 50000),
(50, 0, 49999, 100000),
(100, 0, 249999, 500000),
]
PRESET_TARGETS_RU = [
"~10 членов, слева: 1 куб",
"~100, слева: 1 куб",
"~1000, слева: 1 куб",
"~10000, слева: 1 куб",
"~50000, слева: 1 куб",
"~100000, слева: 1 куб",
"~500000, слева: 1 куб (a=100)",
]
def build_output_parts(data: dict, *, wrap_width: int = 72):
"""Полный вывод: шапка, строки тождества (wrap), нижняя линия из =."""
bar_w = min(max(wrap_width, 40), 200)
sep = "=" * bar_w
Ld, Rd = order_sides_for_display(data["L_final"], data["R_final"])
header = [
(
f"Разложение: a = {data['a']}, b₀ = {data['b_start']}, k = {data['k']}"
),
f"Параметры сформированы. Число членов в последовательности: {data['total_terms']}.",
"",
"Результат:",
f"Слева: {ru_cubes_phrase(len(Ld))}",
f"Справа: {ru_cubes_phrase(len(Rd))}",
sep,
f"{data['total_val']} = {data['factor_str']} =",
]
eq_lines = wrap_equation_lines(Ld, Rd, wrap_width)
return header, eq_lines, sep
def wrap_factored_cube_equation(L: list, R: list, width: int) -> list:
"""Строки тождества (как wrap_equation_lines), но основания — полная факторизация в скобках."""
tokens = []
for i, val in enumerate(L):
fx = format_factorization(val)
tok = f"({fx})^3"
tokens.append(tok if i == 0 else f"+{tok}")
tokens.append("=")
for i, val in enumerate(R):
fx = format_factorization(val)
tok = f"({fx})^3"
tokens.append(tok if i == 0 else f"+{tok}")
lines = []
current_line = ""
for token in tokens:
if token == "=":
current_line += " = "
continue
if len(current_line) + len(token) > width:
if current_line.strip():
lines.append(current_line.rstrip())
current_line = token
else:
current_line += token
if current_line.strip():
lines.append(current_line.rstrip())
return lines
def format_bases_factor_table(L: list, R: list) -> tuple[list[str], list[Optional[str]]]:
"""Таблица: целое основание | разложение format_factorization (моноширинно).
Второй список — теги по строкам (None или TAG_PRIME_TABLE_ROW для строк данных с простым основанием).
"""
bases_order = [int(x) for x in L] + [int(x) for x in R]
rows = []
for x in L:
rows.append((str(int(x)), format_factorization(x)))
for x in R:
rows.append((str(int(x)), format_factorization(x)))
h0, h1 = "Основание", "Факторизация"
wn = max(len(h0), max((len(r[0]) for r in rows), default=0))
wf = max(len(h1), max((len(r[1]) for r in rows), default=0))
sep = "+" + "-" * (wn + 2) + "+" + "-" * (wf + 2) + "+"
out = [
sep,
"| " + h0.rjust(wn) + " | " + h1.ljust(wf) + " |",
sep,
]
tags: list[Optional[str]] = [None, None, None]
for i, (n, fac) in enumerate(rows):
out.append("| " + n.rjust(wn) + " | " + fac.ljust(wf) + " |")
tags.append(TAG_PRIME_TABLE_ROW if _highlight_prime_or_unit_base(bases_order[i]) else None)
out.append(sep)
tags.append(None)
return out, tags
def _factor_string_tokens(fac_str: str) -> list[str]:
s = (fac_str or "").strip()
if not s:
return []
return s.split("*")
def format_bases_factor_table_by_columns(
L: list,
R: list,
*,
h_base: str = "Основание",
) -> tuple[list[str], list[Optional[str]]]:
"""Таблица: основание | фактор 1 | фактор 2 | … (моноширинно, ячейки факторов выровнены вправо).
Второй список — теги по строкам (как у format_bases_factor_table).
"""
bases_order = [int(x) for x in L] + [int(x) for x in R]
rows = []
for x in L:
rows.append((str(int(x)), format_factorization(x)))
for x in R:
rows.append((str(int(x)), format_factorization(x)))
factor_rows = [_factor_string_tokens(f) for _, f in rows]
nf = max((len(fr) for fr in factor_rows), default=0)
if nf < 1:
nf = 1
wn = max(len(h_base), max((len(r[0]) for r in rows), default=0))
col_widths = []
for j in range(nf):
h = str(j + 1)
w = len(h)
for fr in factor_rows:
if j < len(fr):
w = max(w, len(fr[j]))
col_widths.append(max(w, 1))
parts = ["+" + "-" * (wn + 2)] + ["+" + "-" * (cw + 2) for cw in col_widths] + ["+"]
sep = "".join(parts)
header_line = "| " + h_base.rjust(wn) + " |"
for j, cw in enumerate(col_widths):
header_line += " " + str(j + 1).center(cw) + " |"
out = [sep, header_line, sep]
tags: list[Optional[str]] = [None, None, None]
for row_i, ((_n, _f), fr) in enumerate(zip(rows, factor_rows)):
row_line = "| " + _n.rjust(wn) + " |"
for j, cw in enumerate(col_widths):
cell = fr[j] if j < len(fr) else ""
row_line += " " + cell.rjust(cw) + " |"
out.append(row_line)
tags.append(TAG_PRIME_TABLE_ROW if _highlight_prime_or_unit_base(bases_order[row_i]) else None)
out.append(sep)
tags.append(None)
return out, tags
def build_full_factor_display(
L2: list,
R2: list,
wrap_width: int,
*,
bases_only: bool,
factor_columns: bool = False,
):
"""(mode, lines, line_tags): line_tags выравнивается с lines или None для режима cubes."""
foot = FOOTNOTE_DEFACT_REDUCED
if bases_only:
if factor_columns:
body, body_tags = format_bases_factor_table_by_columns(L2, R2)
else:
body, body_tags = format_bases_factor_table(L2, R2)
lines = [TITLE_FACTORED_TABLE] + body + [foot]
line_tags = [None] + body_tags + [None]
return ("table", lines, line_tags)
body = wrap_factored_cube_equation(L2, R2, wrap_width)
lines = [TITLE_FACTORED_CUBES] + body + [foot]
return ("cubes", lines, None)
def between_separators_one_line(
data: dict,
wrap_width: int,
defactor_bundle: Optional[Tuple[str, str, list, list, list]] = None,
omit_raw_decomposition: bool = False,
between_tail_lines: Optional[list] = None,
) -> str:
"""Текст между линиями === без переносов (как одна строка) для копирования."""
tail = ("\n" + "\n".join(between_tail_lines)) if between_tail_lines else ""
if omit_raw_decomposition and defactor_bundle:
d_g3, d_n, d_eq, _L2, _R2 = defactor_bundle
s = (
DEFACTOR_SECTION_TITLE
+ "\n"
+ d_g3
+ "\n"
+ d_n
+ "\n"
+ "\n".join(d_eq)
)
return s + tail
header, eq_lines, _ = build_output_parts(data, wrap_width=wrap_width)
factor_line = header[-1]
s = factor_line.rstrip() + "".join(eq_lines)
if defactor_bundle:
d_g3, d_n, d_eq, _L2, _R2 = defactor_bundle
s += (
"\n"
+ DEFACTOR_SECTION_TITLE
+ "\n"
+ d_g3
+ "\n"
+ d_n
+ "\n"
+ "\n".join(d_eq)
)
return s + tail
def build_output(data: dict, term_count_only: bool, *, wrap_width: int = 72) -> str:
if term_count_only:
Ld, Rd = order_sides_for_display(data["L_final"], data["R_final"])
return (
f"a = {data['a']}, b₀ = {data['b_start']}, k = {data['k']}\n"
f"Членов в разложении (после сокращений): {data['total_terms']}\n"
f"Слева: {ru_cubes_phrase(len(Ld))}\n"
f"Справа: {ru_cubes_phrase(len(Rd))}\n"
)
h, e, s = build_output_parts(data, wrap_width=wrap_width)
return "\n".join(h + e + [s])
def _insert_eq_lines_bold_left(tw: tk.Text, lines: list, tag: str) -> None:
"""Левая часть до первого « = » — жирным (tag)."""
left_zone = True
for line in lines:
if left_zone:
if " = " in line:
left_part, _, rest = line.partition(" = ")
tw.insert(tk.END, left_part, (tag,))
tw.insert(tk.END, " = " + rest + "\n")
left_zone = False
else:
tw.insert(tk.END, line + "\n", (tag,))
else:
tw.insert(tk.END, line + "\n")
def insert_result_with_bold_left_side(
tw: tk.Text,
header: list,
eq_lines: list,
sep: str,
defactor_bundle: Optional[Tuple[str, str, list, list, list]] = None,
full_factor_display: Optional[Tuple[str, list, Optional[list]]] = None,
) -> None:
"""В строках тождества левая часть до первого « = » — жирным и синим (#1565C0)."""
tag = "decomp_left"
blue = "#1565C0"
try:
base = tkfont.Font(font=tw.cget("font"))
fam = base.actual("family")
sz = int(base.actual("size"))
tw.tag_configure(tag, font=(fam, sz, "bold"), foreground=blue)
except tk.TclError:
tw.tag_configure(tag, font=("Consolas", 10, "bold"), foreground=blue)
for line in header:
tw.insert(tk.END, line + "\n")
_insert_eq_lines_bold_left(tw, eq_lines, tag)
if defactor_bundle:
d_g3, d_n, d_eq, _L2, _R2 = defactor_bundle
if eq_lines:
tw.insert(tk.END, "\n")
tw.insert(tk.END, DEFACTOR_SECTION_TITLE + "\n")
tw.insert(tk.END, d_g3 + "\n")
tw.insert(tk.END, d_n + "\n")
_insert_eq_lines_bold_left(tw, d_eq, tag)
if full_factor_display:
mode, lines, line_tags = full_factor_display
tw.insert(tk.END, "\n")
tw.insert(tk.END, lines[0] + "\n")
mid = lines[1:-1]
if mode == "cubes":
_insert_eq_lines_bold_left(tw, mid, tag)
else:
if line_tags:
try:
bf = tkfont.Font(font=tw.cget("font"))
fam = bf.actual("family")
sz = int(bf.actual("size"))
tw.tag_configure(
TAG_PRIME_TABLE_ROW,
font=(fam, sz, "bold"),
foreground=TABLE_PRIME_BLUE,
)
except tk.TclError:
tw.tag_configure(
TAG_PRIME_TABLE_ROW,
font=("Consolas", 10, "bold"),
foreground=TABLE_PRIME_BLUE,
)
for i, ln in enumerate(mid):
tidx = 1 + i
tg = (
line_tags[tidx]
if line_tags and tidx < len(line_tags) and line_tags[tidx]
else None
)
if tg:
tw.insert(tk.END, ln + "\n", (tg,))
else:
tw.insert(tk.END, ln + "\n")
tw.insert(tk.END, lines[-1] + "\n")
tw.insert(tk.END, sep + "\n")
def main():
root = tk.Tk()
root.title("Ramanujan decomposition — параметры a, b₀, k")
root.minsize(640, 880)
frm = ttk.Frame(root, padding=12)
frm.grid(row=0, column=0, sticky="nsew")
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frm.columnconfigure(1, weight=1)
frm.rowconfigure(9, weight=1)
var_a = tk.IntVar(value=2)
var_b = tk.IntVar(value=10)
var_k = tk.IntVar(value=5)
var_warn_max = tk.IntVar(value=50000)
ttk.Label(frm, text="a:").grid(row=0, column=0, sticky="w", pady=2)
sp_a = tk.Spinbox(
frm,
from_=-300,
to=300,
increment=1,
textvariable=var_a,
width=12,
)
sp_a.grid(row=0, column=1, sticky="w", pady=2)
ttk.Label(frm, text="b₀ (начальное b):").grid(row=1, column=0, sticky="w", pady=2)
sp_b = tk.Spinbox(
frm,
from_=-20000,
to=20000,
increment=1,
textvariable=var_b,
width=12,
)
sp_b.grid(row=1, column=1, sticky="w", pady=2)
ttk.Label(frm, text="k (число шагов):").grid(row=2, column=0, sticky="w", pady=2)
sp_k = tk.Spinbox(
frm,
from_=1,
to=300000,
increment=1,
textvariable=var_k,
width=12,
)
sp_k.grid(row=2, column=1, sticky="w", pady=2)
only_count = tk.BooleanVar(value=False)
chk = ttk.Checkbutton(
frm,
text="Только число членов (без строки разложения)",
variable=only_count,
)
chk.grid(row=3, column=0, columnspan=2, sticky="w", pady=(8, 4))
var_defactor = tk.BooleanVar(value=True)
chk_def = ttk.Checkbutton(
frm,
text=(
"Проверка дефакторизации (g³ = (НОД оснований)³, N = факторизация(N) =, сокращённое "
"тождество; при НОД>1 сырое разложение не показывается; при НОД=1 — полный вывод)"
),
variable=var_defactor,
)
chk_def.grid(row=4, column=0, columnspan=2, sticky="w", pady=(0, 4))
var_full_factor = tk.BooleanVar(value=False)
var_bases_only = tk.BooleanVar(value=True)
opt_sub = ttk.Frame(frm)
opt_sub.grid(row=5, column=0, columnspan=2, sticky="w", pady=(0, 4))
chk_ff = ttk.Checkbutton(
opt_sub,
text=(
"Полная факторизация сокращённого тождества (кубы с разложенными основаниями; "
"ниже блок после дефакторизации)"
),
variable=var_full_factor,
)
chk_ff.pack(side=tk.LEFT)
chk_bo = ttk.Checkbutton(
opt_sub,
text="Только основания (таблица, без ^3)",
variable=var_bases_only,
)
chk_bo.pack(side=tk.LEFT, padx=(18, 0))
var_factor_columns = tk.BooleanVar(value=False)
chk_fc = ttk.Checkbutton(
opt_sub,
text="Факторы по колонкам таблицы",
variable=var_factor_columns,
)
chk_fc.pack(side=tk.LEFT, padx=(18, 0))
def sync_factor_suboptions(*_args):
ff = var_full_factor.get()
bo = var_bases_only.get()
st_sub = tk.NORMAL if ff else tk.DISABLED
chk_bo.configure(state=st_sub)
chk_fc.configure(state=tk.NORMAL if (ff and bo) else tk.DISABLED)
var_full_factor.trace_add("write", sync_factor_suboptions)
var_bases_only.trace_add("write", sync_factor_suboptions)
sync_factor_suboptions()
ttk.Label(
frm,
text=(
"Макс. членов разложения — предупреждать о большом выводе\n"
"(только если галочка «только число» выключена):"
),
justify=tk.LEFT,
).grid(row=6, column=0, sticky="nw", pady=2)
sp_warn = tk.Spinbox(
frm,
from_=4,
to=2_000_000,
increment=1,
textvariable=var_warn_max,
width=12,
)
sp_warn.grid(row=6, column=1, sticky="w", pady=2)
ex_frame = ttk.LabelFrame(frm, text="Примеры: тройка параметров → число членов (для ориентира)")
ex_frame.grid(row=7, column=0, columnspan=2, sticky="ew", pady=(10, 6))
ex_frame.columnconfigure(0, weight=1)
ttk.Label(ex_frame, text="Параметры a, b₀, k").grid(row=0, column=0, sticky="w", padx=4, pady=2)
ttk.Label(ex_frame, text="Членов").grid(row=0, column=1, sticky="w", padx=4, pady=2)
ttk.Label(ex_frame, text="").grid(row=0, column=2, padx=2, pady=2)
def make_apply(a_i, b_i, k_i):
def _apply():
var_a.set(a_i)
var_b.set(b_i)
var_k.set(k_i)
return _apply
for i, ((a_i, b_i, k_i, cnt), hint) in enumerate(
zip(PRESET_EXAMPLES, PRESET_TARGETS_RU), start=1
):
param_txt = f"a = {a_i}, b₀ = {b_i}, k = {k_i}"
row_txt = f"{param_txt} ({hint})"
ttk.Label(ex_frame, text=row_txt).grid(row=i, column=0, sticky="w", padx=4, pady=1)
ttk.Label(ex_frame, text=str(cnt)).grid(row=i, column=1, sticky="e", padx=8, pady=1)
ttk.Button(ex_frame, text="Установить", width=11, command=make_apply(a_i, b_i, k_i)).grid(
row=i, column=2, padx=4, pady=1
)
def clipboard_set(text: str) -> None:
root.clipboard_clear()
root.clipboard_append(text)
root.update()
clip_block_state = {
"data": None,
"wrap": None,
"defactor_bundle": None,
"omit_raw_decomposition": False,
"between_tail_lines": None,
}
def copy_between_separators() -> None:
d = clip_block_state["data"]
w = clip_block_state["wrap"]
if d is None or w is None:
messagebox.showinfo(
"Нет блока",
"Сначала выполните полный расчёт (без режима «только число»), "
"чтобы появился текст между линиями из знаков =.",
)
return
clipboard_set(
between_separators_one_line(
d,
w,
clip_block_state.get("defactor_bundle"),
clip_block_state.get("omit_raw_decomposition", False),
clip_block_state.get("between_tail_lines"),
)
)
out_wrap = ttk.Frame(frm)
out_wrap.grid(row=9, column=0, columnspan=2, sticky="nsew", pady=(4, 0))
out_wrap.rowconfigure(1, weight=1)
out_wrap.columnconfigure(0, weight=1)
out_top = ttk.Frame(out_wrap)
out_top.grid(row=0, column=0, sticky="ew")
ttk.Label(out_top, text="Результат").pack(side=tk.LEFT)
text_holder = ttk.Frame(out_wrap)
text_holder.grid(row=1, column=0, sticky="nsew")
text_holder.rowconfigure(0, weight=1)
text_holder.columnconfigure(0, weight=1)
v_scroll = ttk.Scrollbar(text_holder, orient=tk.VERTICAL)
h_scroll = ttk.Scrollbar(text_holder, orient=tk.HORIZONTAL)
tw = tk.Text(
text_holder,
height=28,
width=88,
font=("Consolas", 10),
wrap=tk.NONE,
exportselection=True,
undo=False,
xscrollcommand=h_scroll.set,
yscrollcommand=v_scroll.set,
)
v_scroll.config(command=tw.yview)
h_scroll.config(command=tw.xview)
tw.grid(row=0, column=0, sticky="nsew")
v_scroll.grid(row=0, column=1, sticky="ns")
h_scroll.grid(row=1, column=0, sticky="ew")
def copy_all_out() -> None:
clipboard_set(tw.get("1.0", "end-1c"))
def copy_selection(_event=None):
if not tw.tag_ranges("sel"):
return None
try:
clipboard_set(tw.get("sel.first", "sel.last"))
except tk.TclError:
return None
return "break"
for seq in ("<Control-c>", "<Control-C>", "<Control-Insert>"):
tw.bind(seq, copy_selection)
tw.bind("<<Copy>>", copy_selection)
tw.bind("<Button-1>", lambda e: tw.focus_set())
ctx = tk.Menu(root, tearoff=0)
ctx.add_command(label="Копировать", command=lambda: copy_selection())
def show_ctx(event):
try:
ctx.tk_popup(event.x_root, event.y_root)
finally:
ctx.grab_release()
tw.bind("<Button-3>", show_ctx)
# Копировать всё — глиф Copy (Segoe MDL2)
copy_all_btn = tk.Button(
out_top,
text="\uE8C8",
font=("Segoe MDL2 Assets", 12),
command=copy_all_out,
cursor="hand2",
relief=tk.GROOVE,
padx=6,
pady=0,
takefocus=False,
)
copy_all_btn.pack(side=tk.RIGHT, padx=(8, 0))
# Между === в одну строку (без рубки)
copy_between_btn = tk.Button(
out_top,
text="\u2261",
font=("Segoe UI Symbol", 12),
command=copy_between_separators,
cursor="hand2",
relief=tk.GROOVE,
padx=5,
pady=0,
takefocus=False,
)
copy_between_btn.pack(side=tk.RIGHT, padx=(0, 4))
def run_compute():
try:
a = var_a.get()
b0 = var_b.get()
k = var_k.get()
warn_max = var_warn_max.get()
except tk.TclError:
messagebox.showerror("Параметры", "Введите целые числа во все поля.")
return
if k < 1:
messagebox.showerror("Параметры", "k должно быть ≥ 1")
return
if a == 0:
messagebox.showerror("Параметры", "a не должно быть 0")
return
if warn_max < 1:
messagebox.showerror("Параметры", "Порог предупреждения должен быть ≥ 1")
return
tw.delete("1.0", tk.END)
tw.insert(tk.END, "Считаю\n")
tw.update_idletasks()
use_styled = False
text = ""
header = eq_lines = sep_line = None
defactor_bundle = None
full_factor_display = None
between_tail_lines = None
clip_block_state["data"] = None
clip_block_state["wrap"] = None
clip_block_state["defactor_bundle"] = None
clip_block_state["omit_raw_decomposition"] = False
clip_block_state["between_tail_lines"] = None
try:
data = compute_ramanujan_decomposition(a, b0, k, factorize=False)
want_full = not only_count.get()
if want_full and data["total_terms"] > warn_max:
ok = messagebox.askyesno(
"Большой вывод",
(
f"Членов в разложении: {data['total_terms']}\n"
f"Это больше порога ({warn_max}). Полный вывод и факторизация "
f"могут занять заметное время.\n\n"
f"Продолжить?"
),
icon="warning",
)
if not ok:
clip_block_state["data"] = None
clip_block_state["wrap"] = None
clip_block_state["defactor_bundle"] = None
clip_block_state["omit_raw_decomposition"] = False
clip_block_state["between_tail_lines"] = None
tw.delete("1.0", tk.END)
tw.insert(
tk.END,
f"Отменено. Членов было бы: {data['total_terms']} "
f"(порог предупреждения: {warn_max}).\n",
)
return
if want_full:
data = {
**data,
"factor_str": format_factorization(data["total_val"]),
}
wrap_w = _estimate_wrap_chars(tw)
if only_count.get():
text = build_output(data, True, wrap_width=wrap_w)
else:
header, eq_lines, sep_line = build_output_parts(data, wrap_width=wrap_w)
if var_defactor.get():
defactor_bundle = try_defactored_equation_lines(
data["L_final"], data["R_final"], wrap_w
)
if var_defactor.get() and defactor_bundle is not None:
header = header[:-1]
eq_lines = []
if (
defactor_bundle is not None
and var_full_factor.get()
):
_g3, _n, _eq, L2, R2 = defactor_bundle
full_factor_display = build_full_factor_display(
L2,
R2,
wrap_w,
bases_only=var_bases_only.get(),
factor_columns=var_bases_only.get()
and var_factor_columns.get(),
)
between_tail_lines = list(full_factor_display[1])
use_styled = True
except Exception:
use_styled = False
text = traceback.format_exc()
tw.delete("1.0", tk.END)
if use_styled:
insert_result_with_bold_left_side(
tw,
header,
eq_lines,
sep_line,
defactor_bundle,
full_factor_display,
)
clip_block_state["data"] = data
clip_block_state["wrap"] = wrap_w
clip_block_state["defactor_bundle"] = defactor_bundle
clip_block_state["omit_raw_decomposition"] = bool(
var_defactor.get() and defactor_bundle is not None
)
clip_block_state["between_tail_lines"] = between_tail_lines
else:
tw.insert(tk.END, text)
btn_row = ttk.Frame(frm)
btn_row.grid(row=8, column=0, columnspan=2, sticky="ew", pady=6)
btn_row.columnconfigure(0, weight=1)
ttk.Button(btn_row, text="Вычислить", command=run_compute).grid(row=0, column=0, sticky="w")
def open_author_site():
webbrowser.open(AUTHOR_SITE_URL)
ttk.Button(btn_row, text="Сайт автора", command=open_author_site).grid(
row=0, column=1, sticky="e"
)
for w in (sp_a, sp_b, sp_k, sp_warn):
w.bind("<Return>", lambda e: run_compute())
root.mainloop()
if __name__ == "__main__":
main()
Sample GUI window (example after running decompose_gui_EN.py).
Пример окна GUI (после запуска decompose_gui.py).