@@ -49,62 +49,6 @@ def __init__(
4949 self ._is_reduced = False
5050 self .children = []
5151
52- @classmethod
53- def from_matrix (
54- cls , matrix : Matrix , precision : int = 5 , force : bool = False
55- ) -> "Reducer" :
56- if not matrix .is_square ():
57- raise ValueError ("Input matrix must be square." )
58-
59- free_syms = list (matrix .free_symbols )
60- if len (free_syms ) > 1 :
61- raise ValueError ("Input matrix must depend on at most one variable." )
62-
63- p = 1
64- factorial_power = max (matrix .degrees (n ))
65-
66- normalized_matrix = matrix / (n ** factorial_power )
67-
68- degrees = [d for d in normalized_matrix .degrees (n ) if d != - sp .oo ]
69- S = max (degrees ) - min (degrees ) if degrees else 1
70- poincare_bound = S * 2 + 1
71-
72- negative_bound = (
73- - min (
74- [
75- factorial_power
76- for factorial_power in normalized_matrix .degrees (n )
77- if factorial_power > - sp .oo
78- ],
79- default = 0 ,
80- )
81- * p
82- + 1
83- )
84-
85- required_precision = max (poincare_bound , negative_bound )
86- if not force and precision < required_precision :
87- raise PrecisionExhaustedError (
88- "Encountered a silent rational Taylor truncation."
89- )
90-
91- logger .debug (f"FROM_MATRIX: Expanding Taylor Series to { precision = } ..." )
92- series = SeriesMatrix .from_matrix (normalized_matrix , n , p , precision )
93-
94- return cls (
95- series = series ,
96- factorial_power = factorial_power ,
97- precision = precision ,
98- p = p ,
99- )
100-
101- def _unramified_target (self , ramified_target : int | sp .Expr ) -> int :
102- """
103- Converts a local ramified precision requirement back to the
104- global unramified scale for the top-level backoff loop.
105- """
106- return int (sp .ceiling (ramified_target / self .p ))
107-
10852 @staticmethod
10953 def _solve_sylvester (A : Matrix , B : Matrix , C : Matrix ) -> Matrix :
11054 """Solves the Sylvester equation: A*X - X*B = C for X using Kronecker flattening."""
@@ -426,70 +370,36 @@ def _check_cfm_validity(self, grid: list[list["GrowthRate"]]) -> None:
426370 def asymptotic_growth (self ) -> list [GrowthRate ]:
427371 """
428372 Extracts the raw, unmapped asymptotic components of the internal canonical basis.
429- Returns a list of strongly-typed GrowthRate objects.
430373 """
431374 if not self ._is_reduced :
432375 self .reduce ()
433376
434377 if self .children :
435- for i , child in enumerate (self .children ):
436- logger .debug (
437- f"CFM: Child { i } has precision { child .S_total .precision = } . "
438- f"Is it identity? { child .S_total .coeffs [0 ].is_Identity } "
439- )
440378 return [sol for child in self .children for sol in child .asymptotic_growth ()]
441379
442380 growths , log_power = [], 0
443381
444382 for i in range (self .dim ):
445- exp_base = sp . cancel ( sp . expand ( self .M .coeffs [0 ][i , i ]))
383+ exp_base = self .M .coeffs [0 ][i , i ]
446384 self ._check_eigenvalue_blindness (exp_base )
447385
448- is_jordan_link = False
449- if i > 0 and exp_base == sp .cancel (
450- sp .expand (self .M .coeffs [0 ][i - 1 , i - 1 ])
451- ):
386+ if i > 0 and exp_base == self .M .coeffs [0 ][i - 1 , i - 1 ]:
452387 is_jordan_link = any (
453- sp . cancel ( sp . expand ( self .M .coeffs [k ][i - 1 , i ])) != sp .S .Zero
388+ self .M .coeffs [k ][i - 1 , i ] != sp .S .Zero
454389 for k in range (self .precision )
455390 )
456- log_power = log_power + 1 if is_jordan_link else 0
457-
458- x = sp .S .Zero
459- max_k = min (self .precision , self .p + 1 )
460- for k in range (1 , max_k ):
461- x += (self .M .coeffs [k ][i , i ] / exp_base ) * (t ** k )
462-
463- log_series = sp .S .Zero
464- for j in range (1 , self .p + 1 ):
465- log_series += ((- 1 ) ** (j + 1 ) / sp .Rational (j )) * (x ** j )
466-
467- log_series = sp .expand (log_series )
468-
469- sub_exp , polynomial_degree = sp .S .Zero , sp .S .Zero
470-
471- for k in range (1 , self .p + 1 ):
472- c_k = log_series .coeff (t , k )
473- if c_k == sp .S .Zero :
474- continue
475-
476- c_k = sp .cancel (sp .expand (c_k ))
477-
478- if k < self .p :
479- power = 1 - sp .Rational (k , self .p )
480- sub_exp += (c_k / power ) * (n ** power )
481- elif k == self .p :
482- polynomial_degree = c_k
391+ log_power = log_power + 1 if is_jordan_link else 0
392+ else :
393+ log_power = 0
483394
484- logger .debug (
485- f"GROWTH: Variable { i = } , { k = } , { c_k = } , { exp_base = } , { x = } , { log_series = } , { polynomial_degree = } "
486- )
395+ # Isolate the diagonal Taylor coefficients for this variable
396+ diag_coeffs = [self .M .coeffs [k ][i , i ] for k in range (self .precision )]
487397
398+ # Let GrowthRate parse its own math!
488399 growths .append (
489- GrowthRate (
490- exp_base = exp_base ,
491- sub_exp = sub_exp ,
492- polynomial_degree = polynomial_degree ,
400+ GrowthRate .from_taylor_coefficients (
401+ coeffs = diag_coeffs ,
402+ p = self .p ,
493403 log_power = log_power ,
494404 factorial_power = self .factorial_power ,
495405 )
@@ -524,58 +434,47 @@ def canonical_growth_matrix(self) -> list[list[GrowthRate]]:
524434 if not self ._is_reduced :
525435 self .reduce ()
526436
527- # 1. Base Grid Construction (Recursive Block Diagonal or Leaf Nodes)
437+ base_grid = [[ GrowthRate () for _ in range ( self . dim )] for _ in range ( self . dim )]
528438 if self .children :
529- base_grid = [
530- [GrowthRate () for _ in range (self .dim )] for _ in range (self .dim )
531- ]
532439 offset = 0
533440 for child in self .children :
534- c_grid = child .canonical_growth_matrix () # RECURSIVE CALL
535- c_dim = child .dim
536- for r in range (c_dim ):
537- for c in range (c_dim ):
538- base_grid [offset + r ][offset + c ] = c_grid [r ][c ]
539- offset += c_dim
441+ c_grid = child .canonical_growth_matrix ()
442+ for r , row in enumerate (c_grid ):
443+ for c , val in enumerate (row ):
444+ base_grid [offset + r ][offset + c ] = val
445+ offset += child .dim
540446 else :
541- growths = self .asymptotic_growth ()
542- base_grid = [
543- [GrowthRate () if r != c else growths [r ] for c in range (self .dim )]
544- for r in range (self .dim )
447+ for i , growth in enumerate (self .asymptotic_growth ()):
448+ base_grid [i ][i ] = growth
449+
450+ shift_matrix = [
451+ [GrowthRate () for _ in range (self .dim )] for _ in range (self .dim )
452+ ]
453+ for r in range (self .dim ):
454+ for c in range (self .dim ):
455+ for k , mat in enumerate (self .S_total .coeffs ):
456+ if mat [r , c ] != sp .S .Zero :
457+ shift_matrix [r ][c ] = GrowthRate (
458+ exp_base = sp .S .One ,
459+ polynomial_degree = - sp .Rational (k , self .p ),
460+ )
461+ break
462+
463+ final_grid = [
464+ [
465+ sum (
466+ (shift_matrix [row ][k ] * base_grid [k ][col ] for k in range (self .dim )),
467+ start = GrowthRate (),
468+ )
469+ for col in range (self .dim )
545470 ]
546-
547- # 2. Map the assembled block through the local gauge transformation S_total
548- final_grid = []
549- for row in range (self .dim ):
550- final_row = []
551- for col in range (self .dim ):
552- cell_growth = GrowthRate ()
553-
554- for k_idx in range (self .dim ):
555- base_cell = base_grid [k_idx ][col ]
556- if base_cell .exp_base == sp .S .Zero :
557- continue
558-
559- # Find the leading shift in S_total for this mapping
560- for series_k in range (self .S_total .precision ):
561- coeff = self .S_total .coeffs [series_k ][row , k_idx ]
562- if coeff != sp .S .Zero :
563- shift_growth = GrowthRate (
564- exp_base = sp .S .One ,
565- polynomial_degree = - sp .Rational (series_k , self .p ),
566- )
567- cell_growth += shift_growth * base_cell
568- break
569-
570- final_row .append (cell_growth )
571- final_grid .append (final_row )
471+ for row in range (self .dim )
472+ ]
572473
573474 sorted_indices = sorted (
574475 range (self .dim ), key = lambda c : final_grid [- 1 ][c ], reverse = True
575476 )
576-
577- for i in range (self .dim ):
578- final_grid [i ] = [final_grid [i ][c ] for c in sorted_indices ]
477+ final_grid = [[row [c ] for c in sorted_indices ] for row in final_grid ]
579478
580479 self ._check_cfm_validity (final_grid )
581480 return final_grid
0 commit comments