From c4f48f712c69a833e973261f715baf6a8eebd756 Mon Sep 17 00:00:00 2001 From: Ulysses Tiberious <132908333+utiberious@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:01:26 +0800 Subject: [PATCH 1/2] perf(metric): pre-apply cancel() before Simp.apply in normalize_metric and Christoffel_symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SymPy 1.13 introduced a slow .replace() traversal in TR3/fu.py that is triggered by both simplify() and trigsimp() (default method). Simp.apply is called ~72 times during Ga.build(..., norm=True) — 18 times in normalize_metric and 54 times for Christoffel symbols — making the prolate spheroidal coordinates example take >600 s. Wrapping each expression with cancel() before passing it to Simp.apply reduces the expression size via polynomial GCD cancellation before the expensive trigonometric simplification step, significantly reducing the number of nodes that TR3 must traverse. This is the library-level fix for issue #576 (option 2), complementing the minimal example-file fix in PR #590. --- galgebra/metric.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/galgebra/metric.py b/galgebra/metric.py index aa26f91b..6a1f8574 100644 --- a/galgebra/metric.py +++ b/galgebra/metric.py @@ -7,7 +7,7 @@ from typing import List, Optional from sympy import ( - diff, trigsimp, Matrix, Rational, + cancel, diff, trigsimp, Matrix, Rational, sqf_list, sqrt, eye, S, expand, Mul, Add, simplify, Expr, Abs, Function, MatrixSymbol ) @@ -567,7 +567,7 @@ def Gamma_ijk(i, j, k): # dG[i][j][k] = S.Half * (dg[j][k][i] + dg[i][k][j] - dg[i][j][k]) dG = [[[ - Simp.apply(Gamma_ijk(i, j, k)) + Simp.apply(cancel(Gamma_ijk(i, j, k))) for k in n_range] for j in n_range] for i in n_range] @@ -587,7 +587,7 @@ def Gamma2_ijk(i, j, k): return sum([Gamma_ijl * self.g_inv[l, k] for l, Gamma_ijl in enumerate(Gamma1[i][j])]) Gamma2 = [[[ - Simp.apply(Gamma2_ijk(i, j, k)) + Simp.apply(cancel(Gamma2_ijk(i, j, k))) for k in n_range] for j in n_range] for i in n_range] @@ -609,9 +609,9 @@ def normalize_metric(self): # Normalize derivatives of basis vectors for x_i in self.n_range: for jb in self.n_range: - self.de[x_i][jb] = Simp.apply((((self.de[x_i][jb].subs(renorm) + self.de[x_i][jb] = Simp.apply(cancel((self.de[x_i][jb].subs(renorm) - diff(self.e_norm[jb], self.coords[x_i]) * - self.basis[jb]) / self.e_norm[jb]))) + self.basis[jb]) / self.e_norm[jb])) if self.debug: printer.oprint('e^{i}->e^{i}/|e_{i}|', renorm) for x_i in self.n_range: @@ -621,7 +621,7 @@ def normalize_metric(self): # Normalize metric tensor for ib in self.n_range: for jb in self.n_range: - self.g[ib, jb] = Simp.apply(self.g[ib, jb] / (self.e_norm[ib] * self.e_norm[jb])) + self.g[ib, jb] = Simp.apply(cancel(self.g[ib, jb] / (self.e_norm[ib] * self.e_norm[jb]))) if self.debug: printer.oprint('renorm(g)', self.g) From 746fcabf264018f83f712a5287440c339f132eac Mon Sep 17 00:00:00 2001 From: Ulysses Tiberious <132908333+utiberious@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:31:06 +0800 Subject: [PATCH 2/2] fix(metric): remove cancel() from Christoffel_symbols to prevent form change cancel() on purely polynomial Christoffel symbol expressions can change their representation (e.g. expanded 4G^2M^2 - 4GMc^2r + c^4r^2 becomes 4G^2M^2 + c^2r(-4GM + c^2r)) because cancel() uses Poly arithmetic internally, which may normalize polynomials differently. When simplify() then runs on the altered form it produces a different output, breaking the stored gr_metrics notebook output. The cancel() pre-pass is retained only in normalize_metric, where the inputs are genuine fractions (divided by e_norm) rather than polynomial sums, so cancel() there reduces actual rational factors without changing the polynomial normal form. --- galgebra/metric.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galgebra/metric.py b/galgebra/metric.py index 6a1f8574..b25d220f 100644 --- a/galgebra/metric.py +++ b/galgebra/metric.py @@ -567,7 +567,7 @@ def Gamma_ijk(i, j, k): # dG[i][j][k] = S.Half * (dg[j][k][i] + dg[i][k][j] - dg[i][j][k]) dG = [[[ - Simp.apply(cancel(Gamma_ijk(i, j, k))) + Simp.apply(Gamma_ijk(i, j, k)) for k in n_range] for j in n_range] for i in n_range] @@ -587,7 +587,7 @@ def Gamma2_ijk(i, j, k): return sum([Gamma_ijl * self.g_inv[l, k] for l, Gamma_ijl in enumerate(Gamma1[i][j])]) Gamma2 = [[[ - Simp.apply(cancel(Gamma2_ijk(i, j, k))) + Simp.apply(Gamma2_ijk(i, j, k)) for k in n_range] for j in n_range] for i in n_range]