odeanalysis 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. odeanalysis/__init__.py +296 -0
  2. odeanalysis/_api_policy.py +179 -0
  3. odeanalysis/_assumptions.py +89 -0
  4. odeanalysis/_block_common.py +106 -0
  5. odeanalysis/_formal_gauge.py +115 -0
  6. odeanalysis/_local.py +166 -0
  7. odeanalysis/_moser.py +271 -0
  8. odeanalysis/_power_simplify.py +32 -0
  9. odeanalysis/_spectral.py +215 -0
  10. odeanalysis/_symbolic_compare.py +16 -0
  11. odeanalysis/_symbolic_errors.py +19 -0
  12. odeanalysis/_zero.py +28 -0
  13. odeanalysis/analytic_continuation.py +343 -0
  14. odeanalysis/bell.py +87 -0
  15. odeanalysis/block_decomposition.py +1144 -0
  16. odeanalysis/canonical.py +471 -0
  17. odeanalysis/certified_continuation.py +160 -0
  18. odeanalysis/diagnostics.py +17 -0
  19. odeanalysis/dominance.py +134 -0
  20. odeanalysis/factorization.py +88 -0
  21. odeanalysis/formal.py +1028 -0
  22. odeanalysis/formal_basis.py +1009 -0
  23. odeanalysis/frobenius.py +349 -0
  24. odeanalysis/fuchsian.py +400 -0
  25. odeanalysis/interchange.py +604 -0
  26. odeanalysis/interoperability.py +143 -0
  27. odeanalysis/irregular.py +250 -0
  28. odeanalysis/kovacic.py +478 -0
  29. odeanalysis/levelt.py +679 -0
  30. odeanalysis/local_analysis.py +386 -0
  31. odeanalysis/local_structure.py +290 -0
  32. odeanalysis/matrix_series.py +357 -0
  33. odeanalysis/newton.py +501 -0
  34. odeanalysis/operator.py +193 -0
  35. odeanalysis/parameter_wkb.py +92 -0
  36. odeanalysis/py.typed +0 -0
  37. odeanalysis/series.py +199 -0
  38. odeanalysis/singularities.py +279 -0
  39. odeanalysis/stokes.py +782 -0
  40. odeanalysis/system.py +347 -0
  41. odeanalysis/system_analysis.py +615 -0
  42. odeanalysis/transition_loci.py +302 -0
  43. odeanalysis/turning.py +516 -0
  44. odeanalysis/wronskian.py +110 -0
  45. odeanalysis-0.1.0.dist-info/METADATA +180 -0
  46. odeanalysis-0.1.0.dist-info/RECORD +49 -0
  47. odeanalysis-0.1.0.dist-info/WHEEL +5 -0
  48. odeanalysis-0.1.0.dist-info/licenses/LICENSE +677 -0
  49. odeanalysis-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,296 @@
1
+ """Primary public API for symbolic structural ODE analysis.
2
+
3
+ Expert APIs remain available from their defining submodules; see
4
+ ``docs/api-classification.md`` for the stability policy.
5
+ """
6
+
7
+ __version__ = "0.25.2"
8
+
9
+ from ._moser import moser_reduce
10
+ from .analytic_continuation import (
11
+ CanonicalBasis,
12
+ ConnectionMatrix,
13
+ LocalMonodromy,
14
+ StokesMatrix,
15
+ connection_matrix,
16
+ hypergeometric_connection_matrix,
17
+ kummer_connection_matrices,
18
+ local_monodromy,
19
+ stokes_matrices,
20
+ )
21
+ from .block_decomposition import (
22
+ LeveltTurrittinReduction,
23
+ formal_block_diagonalize,
24
+ levelt_turrittin_reduce,
25
+ )
26
+ from .canonical import (
27
+ CanonicalEquationFamily,
28
+ CanonicalEquationRecognition,
29
+ recognize_canonical_equation,
30
+ transform_to_canonical,
31
+ )
32
+ from .certified_continuation import (
33
+ CertifiedContinuationResult,
34
+ CertifiedMatrixEnclosure,
35
+ certified_system_continuation,
36
+ )
37
+ from .dominance import (
38
+ SolutionDominanceAnalysis,
39
+ classify_solution_dominance,
40
+ )
41
+ from .factorization import (
42
+ FirstOrderFactorization,
43
+ factor_differential_operator,
44
+ is_reducible_operator,
45
+ )
46
+ from .formal import complete_formal_exponential_parts, formal_asymptotic_solutions
47
+ from .formal_basis import formal_monodromy, logarithmic_frobenius_basis
48
+ from .frobenius import FrobeniusAnalysis, frobenius_analysis
49
+ from .fuchsian import (
50
+ ApparentSingularityAnalysis,
51
+ FuchsRelation,
52
+ RiemannScheme,
53
+ apparent_singularity_analysis,
54
+ fuchs_relation,
55
+ is_apparent_singularity,
56
+ riemann_scheme,
57
+ )
58
+ from .interchange import FormalODEData, formal_ode_data
59
+ from .interoperability import (
60
+ CyclicScalarization,
61
+ ScalarSystemCorrespondence,
62
+ scalar_system_correspondence,
63
+ scalarize_system,
64
+ )
65
+ from .irregular import formal_exponential_parts, wkb_ansatze
66
+ from .kovacic import KovacicAnalysis, KovacicOutcome, kovacic_analysis
67
+ from .levelt import LeveltStructure, levelt_structure
68
+ from .local_analysis import (
69
+ LocalAnalysisStratum,
70
+ ParameterizedLocalAnalysis,
71
+ ResonanceStratum,
72
+ local_parameter_analysis,
73
+ )
74
+ from .local_structure import (
75
+ FrobeniusConvergence,
76
+ FrobeniusLocalMonodromy,
77
+ SingularityPointStructure,
78
+ SingularityStructure,
79
+ frobenius_convergence,
80
+ frobenius_local_monodromy,
81
+ singularity_structure,
82
+ )
83
+ from .matrix_series import MatrixLaurentSeries
84
+ from .newton import (
85
+ DifferentialNewtonPolygon,
86
+ SlopeFiltration,
87
+ differential_newton_polygon,
88
+ katz_rank,
89
+ newton_slopes,
90
+ poincare_rank,
91
+ slope_filtration,
92
+ )
93
+ from .operator import LinearDifferentialOperator
94
+ from .parameter_wkb import (
95
+ ParameterizedTurningAnalysis,
96
+ ParameterizedTurningStratum,
97
+ parameterized_turning_analysis,
98
+ )
99
+ from .singularities import (
100
+ ODESingularityAnalysis,
101
+ ODESingularityKind,
102
+ analyze_ode_singularities,
103
+ classify_ode_point,
104
+ ode_singular_points,
105
+ )
106
+ from .stokes import StokesGeometry, stokes_geometry
107
+ from .system import FirstOrderSystem
108
+ from .system_analysis import (
109
+ FormalReductionCertificate,
110
+ FormalSystemAnalysis,
111
+ ParameterizedSystemAnalysis,
112
+ ParameterizedSystemFormalTypes,
113
+ SystemFormalTypeSignature,
114
+ SystemFormalTypeStratum,
115
+ SystemParameterStratum,
116
+ SystemResonance,
117
+ SystemSingularityAnalysis,
118
+ SystemStokesGeometry,
119
+ SystemStokesPair,
120
+ analyze_system_singularity,
121
+ formal_system_analysis,
122
+ system_formal_type_stratification,
123
+ system_parameter_analysis,
124
+ system_stokes_geometry,
125
+ )
126
+ from .transition_loci import (
127
+ LeadingMatrixRankAnalysis,
128
+ ProjectivePolynomialStratum,
129
+ leading_rank_analysis,
130
+ newton_loci,
131
+ singularity_loci,
132
+ singularity_projective_strata,
133
+ stokes_formal_loci,
134
+ stokes_ray_loci,
135
+ turning_loci,
136
+ turning_point_projective_strata,
137
+ )
138
+ from .turning import (
139
+ LiouvilleNormalForm,
140
+ TurningPoint,
141
+ TurningPointAnalysis,
142
+ TurningPointKind,
143
+ UniformWKBReduction,
144
+ WKBExpansion,
145
+ airy_uniformization,
146
+ analyze_turning_points,
147
+ classify_turning_point,
148
+ liouville_normal_form,
149
+ turning_points,
150
+ weber_uniformization,
151
+ wkb_expansion,
152
+ )
153
+ from .wronskian import WronskianAnalysis, abel_wronskian, wronskian_analysis
154
+
155
+ __all__ = (
156
+ "ApparentSingularityAnalysis",
157
+ "CanonicalBasis",
158
+ "CanonicalEquationFamily",
159
+ "CanonicalEquationRecognition",
160
+ "CertifiedContinuationResult",
161
+ "CertifiedMatrixEnclosure",
162
+ "ConnectionMatrix",
163
+ "CyclicScalarization",
164
+ "DifferentialNewtonPolygon",
165
+ "FirstOrderFactorization",
166
+ "FirstOrderSystem",
167
+ "FormalODEData",
168
+ "FormalReductionCertificate",
169
+ "FormalSystemAnalysis",
170
+ "FrobeniusAnalysis",
171
+ "FrobeniusConvergence",
172
+ "FrobeniusLocalMonodromy",
173
+ "FuchsRelation",
174
+ "KovacicAnalysis",
175
+ "KovacicOutcome",
176
+ "LeadingMatrixRankAnalysis",
177
+ "LeveltStructure",
178
+ "LeveltTurrittinReduction",
179
+ "LinearDifferentialOperator",
180
+ "LiouvilleNormalForm",
181
+ "LocalAnalysisStratum",
182
+ "LocalMonodromy",
183
+ "MatrixLaurentSeries",
184
+ "ODESingularityAnalysis",
185
+ "ODESingularityKind",
186
+ "ParameterizedLocalAnalysis",
187
+ "ParameterizedSystemAnalysis",
188
+ "ParameterizedSystemFormalTypes",
189
+ "ParameterizedTurningAnalysis",
190
+ "ParameterizedTurningStratum",
191
+ "ProjectivePolynomialStratum",
192
+ "ResonanceStratum",
193
+ "RiemannScheme",
194
+ "ScalarSystemCorrespondence",
195
+ "SingularityPointStructure",
196
+ "SingularityStructure",
197
+ "SlopeFiltration",
198
+ "SolutionDominanceAnalysis",
199
+ "StokesGeometry",
200
+ "StokesMatrix",
201
+ "SystemFormalTypeSignature",
202
+ "SystemFormalTypeStratum",
203
+ "SystemParameterStratum",
204
+ "SystemResonance",
205
+ "SystemSingularityAnalysis",
206
+ "SystemStokesGeometry",
207
+ "SystemStokesPair",
208
+ "TurningPoint",
209
+ "TurningPointAnalysis",
210
+ "TurningPointKind",
211
+ "UniformWKBReduction",
212
+ "WKBExpansion",
213
+ "WronskianAnalysis",
214
+ "__version__",
215
+ "abel_wronskian",
216
+ "airy_uniformization",
217
+ "analyze_ode_singularities",
218
+ "analyze_system_singularity",
219
+ "analyze_turning_points",
220
+ "apparent_singularity_analysis",
221
+ "certified_system_continuation",
222
+ "classify_ode_point",
223
+ "classify_solution_dominance",
224
+ "classify_turning_point",
225
+ "complete_formal_exponential_parts",
226
+ "connection_matrix",
227
+ "differential_newton_polygon",
228
+ "factor_differential_operator",
229
+ "formal_asymptotic_solutions",
230
+ "formal_block_diagonalize",
231
+ "formal_exponential_parts",
232
+ "formal_monodromy",
233
+ "formal_ode_data",
234
+ "formal_system_analysis",
235
+ "frobenius_analysis",
236
+ "frobenius_convergence",
237
+ "frobenius_local_monodromy",
238
+ "fuchs_relation",
239
+ "hypergeometric_connection_matrix",
240
+ "is_apparent_singularity",
241
+ "is_reducible_operator",
242
+ "katz_rank",
243
+ "kovacic_analysis",
244
+ "kummer_connection_matrices",
245
+ "leading_rank_analysis",
246
+ "levelt_structure",
247
+ "levelt_turrittin_reduce",
248
+ "liouville_normal_form",
249
+ "local_monodromy",
250
+ "local_parameter_analysis",
251
+ "logarithmic_frobenius_basis",
252
+ "moser_reduce",
253
+ "newton_loci",
254
+ "newton_slopes",
255
+ "ode_singular_points",
256
+ "parameterized_turning_analysis",
257
+ "poincare_rank",
258
+ "recognize_canonical_equation",
259
+ "riemann_scheme",
260
+ "scalar_system_correspondence",
261
+ "scalarize_system",
262
+ "singularity_loci",
263
+ "singularity_projective_strata",
264
+ "singularity_structure",
265
+ "slope_filtration",
266
+ "stokes_formal_loci",
267
+ "stokes_geometry",
268
+ "stokes_matrices",
269
+ "stokes_ray_loci",
270
+ "system_formal_type_stratification",
271
+ "system_parameter_analysis",
272
+ "system_stokes_geometry",
273
+ "transform_to_canonical",
274
+ "turning_loci",
275
+ "turning_point_projective_strata",
276
+ "turning_points",
277
+ "weber_uniformization",
278
+ "wkb_ansatze",
279
+ "wkb_expansion",
280
+ "wronskian_analysis",
281
+ )
282
+
283
+
284
+ def __dir__() -> list[str]:
285
+ """Return the documented root API and standard module metadata."""
286
+
287
+ metadata = {
288
+ "__doc__",
289
+ "__file__",
290
+ "__loader__",
291
+ "__name__",
292
+ "__package__",
293
+ "__path__",
294
+ "__spec__",
295
+ }
296
+ return sorted(metadata | set(__all__))
@@ -0,0 +1,179 @@
1
+ """Classification of the supported public API surface.
2
+
3
+ Primary names are available directly from :mod:`odeanalysis`. Expert names
4
+ remain public from their defining submodules. Private modules and helpers are
5
+ implementation details and carry no compatibility contract.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ PRIMARY_EXPORTS: dict[str, str] = {
11
+ "FormalSystemAnalysis": ".system_analysis",
12
+ "ParameterizedSystemAnalysis": ".system_analysis",
13
+ "SystemParameterStratum": ".system_analysis",
14
+ "SystemResonance": ".system_analysis",
15
+ "SystemSingularityAnalysis": ".system_analysis",
16
+ "SystemStokesGeometry": ".system_analysis",
17
+ "SystemStokesPair": ".system_analysis",
18
+ "analyze_system_singularity": ".system_analysis",
19
+ "formal_system_analysis": ".system_analysis",
20
+ "system_parameter_analysis": ".system_analysis",
21
+ "system_stokes_geometry": ".system_analysis",
22
+ "FrobeniusConvergence": ".local_structure",
23
+ "FrobeniusLocalMonodromy": ".local_structure",
24
+ "ResonanceStratum": ".local_analysis",
25
+ "SingularityPointStructure": ".local_structure",
26
+ "SingularityStructure": ".local_structure",
27
+ "WronskianAnalysis": ".wronskian",
28
+ "abel_wronskian": ".wronskian",
29
+ "frobenius_convergence": ".local_structure",
30
+ "frobenius_local_monodromy": ".local_structure",
31
+ "singularity_structure": ".local_structure",
32
+ "wronskian_analysis": ".wronskian",
33
+ "CanonicalBasis": ".analytic_continuation",
34
+ "ConnectionMatrix": ".analytic_continuation",
35
+ "LocalAnalysisStratum": ".local_analysis",
36
+ "LocalMonodromy": ".analytic_continuation",
37
+ "StokesMatrix": ".analytic_continuation",
38
+ "connection_matrix": ".analytic_continuation",
39
+ "hypergeometric_connection_matrix": ".analytic_continuation",
40
+ "kummer_connection_matrices": ".analytic_continuation",
41
+ "local_monodromy": ".analytic_continuation",
42
+ "stokes_matrices": ".analytic_continuation",
43
+ "LiouvilleNormalForm": ".turning",
44
+ "TurningPoint": ".turning",
45
+ "TurningPointAnalysis": ".turning",
46
+ "TurningPointKind": ".turning",
47
+ "UniformWKBReduction": ".turning",
48
+ "WKBExpansion": ".turning",
49
+ "airy_uniformization": ".turning",
50
+ "analyze_turning_points": ".turning",
51
+ "classify_turning_point": ".turning",
52
+ "liouville_normal_form": ".turning",
53
+ "turning_points": ".turning",
54
+ "weber_uniformization": ".turning",
55
+ "wkb_expansion": ".turning",
56
+ "CanonicalEquationFamily": ".canonical",
57
+ "CanonicalEquationRecognition": ".canonical",
58
+ "FirstOrderFactorization": ".factorization",
59
+ "KovacicAnalysis": ".kovacic",
60
+ "KovacicOutcome": ".kovacic",
61
+ "factor_differential_operator": ".factorization",
62
+ "is_reducible_operator": ".factorization",
63
+ "kovacic_analysis": ".kovacic",
64
+ "recognize_canonical_equation": ".canonical",
65
+ "transform_to_canonical": ".canonical",
66
+ "DifferentialNewtonPolygon": ".newton",
67
+ "differential_newton_polygon": ".newton",
68
+ "ApparentSingularityAnalysis": ".fuchsian",
69
+ "FormalODEData": ".interchange",
70
+ "FrobeniusAnalysis": ".frobenius",
71
+ "FirstOrderSystem": ".system",
72
+ "FuchsRelation": ".fuchsian",
73
+ "LeveltStructure": ".levelt",
74
+ "LeveltTurrittinReduction": ".block_decomposition",
75
+ "LinearDifferentialOperator": ".operator",
76
+ "MatrixLaurentSeries": ".matrix_series",
77
+ "ODESingularityAnalysis": ".singularities",
78
+ "ODESingularityKind": ".singularities",
79
+ "ParameterizedLocalAnalysis": ".local_analysis",
80
+ "RiemannScheme": ".fuchsian",
81
+ "SlopeFiltration": ".newton",
82
+ "SolutionDominanceAnalysis": ".dominance",
83
+ "StokesGeometry": ".stokes",
84
+ "analyze_ode_singularities": ".singularities",
85
+ "apparent_singularity_analysis": ".fuchsian",
86
+ "classify_ode_point": ".singularities",
87
+ "classify_solution_dominance": ".dominance",
88
+ "complete_formal_exponential_parts": ".formal",
89
+ "formal_asymptotic_solutions": ".formal",
90
+ "formal_block_diagonalize": ".block_decomposition",
91
+ "formal_exponential_parts": ".irregular",
92
+ "formal_monodromy": ".formal_basis",
93
+ "formal_ode_data": ".interchange",
94
+ "frobenius_analysis": ".frobenius",
95
+ "fuchs_relation": ".fuchsian",
96
+ "is_apparent_singularity": ".fuchsian",
97
+ "katz_rank": ".newton",
98
+ "levelt_structure": ".levelt",
99
+ "levelt_turrittin_reduce": ".block_decomposition",
100
+ "logarithmic_frobenius_basis": ".formal_basis",
101
+ "moser_reduce": "._moser",
102
+ "newton_slopes": ".newton",
103
+ "ode_singular_points": ".singularities",
104
+ "local_parameter_analysis": ".local_analysis",
105
+ "LeadingMatrixRankAnalysis": ".transition_loci",
106
+ "ProjectivePolynomialStratum": ".transition_loci",
107
+ "leading_rank_analysis": ".transition_loci",
108
+ "newton_loci": ".transition_loci",
109
+ "singularity_loci": ".transition_loci",
110
+ "singularity_projective_strata": ".transition_loci",
111
+ "stokes_formal_loci": ".transition_loci",
112
+ "stokes_ray_loci": ".transition_loci",
113
+ "turning_point_projective_strata": ".transition_loci",
114
+ "turning_loci": ".transition_loci",
115
+ "poincare_rank": ".newton",
116
+ "riemann_scheme": ".fuchsian",
117
+ "slope_filtration": ".newton",
118
+ "stokes_geometry": ".stokes",
119
+ "wkb_ansatze": ".irregular",
120
+ "CertifiedContinuationResult": ".certified_continuation",
121
+ "CertifiedMatrixEnclosure": ".certified_continuation",
122
+ "certified_system_continuation": ".certified_continuation",
123
+ "CyclicScalarization": ".interoperability",
124
+ "ScalarSystemCorrespondence": ".interoperability",
125
+ "scalarize_system": ".interoperability",
126
+ "scalar_system_correspondence": ".interoperability",
127
+ "FormalReductionCertificate": ".system_analysis",
128
+ "ParameterizedSystemFormalTypes": ".system_analysis",
129
+ "SystemFormalTypeSignature": ".system_analysis",
130
+ "SystemFormalTypeStratum": ".system_analysis",
131
+ "system_formal_type_stratification": ".system_analysis",
132
+ "ParameterizedTurningAnalysis": ".parameter_wkb",
133
+ "ParameterizedTurningStratum": ".parameter_wkb",
134
+ "parameterized_turning_analysis": ".parameter_wkb",
135
+ }
136
+
137
+ EXPERT_EXPORTS: dict[str, str] = {
138
+ "KovacicCase2Certificate": ".kovacic",
139
+ "KovacicCase3Certificate": ".kovacic",
140
+ "CompleteFormalExponentialPart": ".formal",
141
+ "DifferentialNewtonEdge": ".newton",
142
+ "DifferentialNewtonPoint": ".newton",
143
+ "ExponentialBlockDecomposition": ".block_decomposition",
144
+ "FormalAmplitudeSeries": ".formal",
145
+ "FormalAsymptoticSolution": ".formal",
146
+ "FormalBlockDiagonalization": ".block_decomposition",
147
+ "FormalBlockPartition": ".system",
148
+ "FormalExponentialPart": ".irregular",
149
+ "FormalLogarithmicBasis": ".formal_basis",
150
+ "FormalMonodromy": ".formal_basis",
151
+ "IrregularSingularityInvariants": ".irregular",
152
+ "RamificationStep": ".block_decomposition",
153
+ "RegularSingularLeveltReduction": ".levelt",
154
+ "SparseLaurentSeries": ".series",
155
+ "StokesConnectionPattern": ".stokes",
156
+ "StokesPairGeometry": ".stokes",
157
+ "StokesRay": ".stokes",
158
+ "StokesSector": ".stokes",
159
+ "WKBAnsatz": ".irregular",
160
+ "companion_system": ".system",
161
+ "complete_exponential_bell_polynomial": ".bell",
162
+ "complete_exponential_bell_via_sympy": ".bell",
163
+ "differential_bell_laurent_series": ".formal",
164
+ "differential_bell_polynomials": ".formal",
165
+ "exponential_block_decomposition": ".block_decomposition",
166
+ "formal_amplitude_series": ".formal",
167
+ "formal_block_partition": ".system",
168
+ "formal_logarithmic_basis": ".formal_basis",
169
+ "green_operator_data": ".interchange",
170
+ "irregular_singularity_invariants": ".irregular",
171
+ "levelt_reduce_regular_singular": ".levelt",
172
+ "local_order_and_leading_coefficient": ".newton",
173
+ "localize_operator": ".newton",
174
+ "ramified_pullback": ".block_decomposition",
175
+ "reduce_to_fuchsian": ".levelt",
176
+ "riccati_expression": ".formal",
177
+ "stokes_connection_patterns": ".stokes",
178
+ "stokes_geometry_from_exponential_parts": ".stokes",
179
+ }
@@ -0,0 +1,89 @@
1
+ """Shared assumptions and proof-oriented zero decisions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sympy as sp
6
+ from funcprops import entails, normalize_assumptions
7
+
8
+ try:
9
+ from exprtest import zerotest as _exprtest_zerotest
10
+ except ImportError: # Optional stronger oracle; funcprops remains sufficient.
11
+ _exprtest_zerotest = None
12
+
13
+
14
+ def zero_status(expr: sp.Expr, assumptions: sp.Expr | bool = True) -> bool | None:
15
+ """Prove zero/nonzero when cheap, returning ``None`` when unresolved.
16
+
17
+ Fast structural and SymPy assumption checks run first. ``funcprops`` handles
18
+ bounded logical entailment. If installed, ``exprtest`` is used last and
19
+ only with ``confidence='certified'`` so heuristic nonzero evidence can never
20
+ steer an ODE classification branch.
21
+ """
22
+ assumptions = normalize_assumptions(assumptions)
23
+ value = sp.sympify(expr)
24
+ if value == 0 or value.is_zero is True:
25
+ return True
26
+ if value.is_zero is False:
27
+ return False
28
+ simplified = sp.cancel(sp.together(value)) if sp.count_ops(value) <= 24 else value
29
+ if simplified == 0 or simplified.is_zero is True:
30
+ return True
31
+ if simplified.is_zero is False:
32
+ return False
33
+ if sp.count_ops(simplified) <= 24:
34
+ try:
35
+ decision = entails(sp.Eq(simplified, 0, evaluate=False), assumptions)
36
+ except (RecursionError, TypeError, ValueError):
37
+ decision = None
38
+ if decision is not None:
39
+ return decision
40
+ use_exprtest = _exprtest_zerotest is not None and not simplified.has(
41
+ sp.nan, sp.zoo, sp.oo, -sp.oo
42
+ )
43
+ if simplified.free_symbols and simplified.is_rational_function(
44
+ *simplified.free_symbols
45
+ ):
46
+ use_exprtest = False
47
+ if use_exprtest:
48
+ try:
49
+ return _exprtest_zerotest(
50
+ simplified, assumptions=assumptions, confidence="certified"
51
+ )
52
+ except (RecursionError, TypeError, ValueError):
53
+ return None
54
+ return None
55
+
56
+
57
+ def nonzero_status(expr: sp.Expr, assumptions: sp.Expr | bool = True) -> bool | None:
58
+ z = zero_status(expr, assumptions)
59
+ return None if z is None else not z
60
+
61
+
62
+ def assumption_substitutions(
63
+ assumptions: sp.Expr | bool = True,
64
+ ) -> dict[sp.Symbol, sp.Expr]:
65
+ """Extract direct symbol equalities from a conjunction of assumptions."""
66
+ assumptions = normalize_assumptions(assumptions)
67
+ result: dict[sp.Symbol, sp.Expr] = {}
68
+ for clause in sp.And.make_args(assumptions):
69
+ if not isinstance(clause, sp.Equality):
70
+ continue
71
+ lhs, rhs = clause.lhs, clause.rhs
72
+ if isinstance(lhs, sp.Symbol) and lhs not in rhs.free_symbols:
73
+ result[lhs] = rhs
74
+ elif isinstance(rhs, sp.Symbol) and rhs not in lhs.free_symbols:
75
+ result[rhs] = lhs
76
+ continue
77
+ expr = sp.expand(lhs - rhs)
78
+ for symbol in sorted(expr.free_symbols, key=sp.default_sort_key, reverse=True):
79
+ try:
80
+ poly = sp.Poly(expr, symbol)
81
+ except (sp.PolynomialError, TypeError, ValueError):
82
+ continue
83
+ if poly.degree() == 1:
84
+ aa, bb = poly.all_coeffs()
85
+ value = sp.cancel(-bb / aa)
86
+ if symbol not in value.free_symbols:
87
+ result[symbol] = value
88
+ break
89
+ return result
@@ -0,0 +1,106 @@
1
+ """Shared exact helpers for formal block reduction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+
7
+ import sympy as sp
8
+
9
+ from .matrix_series import MatrixLaurentSeries
10
+
11
+
12
+ class BlockDecompositionError(NotImplementedError):
13
+ """Raised when an exact formal block split cannot be certified."""
14
+
15
+
16
+ def is_zero_matrix(matrix: sp.MatrixBase) -> bool:
17
+ """Return whether every matrix entry simplifies exactly to zero."""
18
+
19
+ return all(sp.simplify(entry) == 0 for entry in matrix)
20
+
21
+
22
+ def is_scalar_matrix(matrix: sp.MatrixBase) -> tuple[bool, sp.Expr]:
23
+ """Return whether ``matrix`` is scalar and, when so, its scalar value."""
24
+
25
+ matrix = sp.Matrix(matrix)
26
+ if matrix.rows != matrix.cols:
27
+ return False, sp.S.Zero
28
+ if matrix.rows == 0:
29
+ return True, sp.S.Zero
30
+ scalar = sp.simplify(matrix[0, 0])
31
+ for i in range(matrix.rows):
32
+ for j in range(matrix.cols):
33
+ expected = scalar if i == j else sp.S.Zero
34
+ if sp.simplify(matrix[i, j] - expected) != 0:
35
+ return False, sp.S.Zero
36
+ return True, scalar
37
+
38
+
39
+ def matrix_block(
40
+ matrix: sp.MatrixBase,
41
+ row_start: int,
42
+ row_stop: int,
43
+ col_start: int,
44
+ col_stop: int,
45
+ ) -> sp.Matrix:
46
+ """Extract a dense matrix block."""
47
+
48
+ return sp.Matrix(matrix)[row_start:row_stop, col_start:col_stop]
49
+
50
+
51
+ def series_block(
52
+ series: MatrixLaurentSeries,
53
+ row_start: int,
54
+ row_stop: int,
55
+ col_start: int,
56
+ col_stop: int,
57
+ ) -> MatrixLaurentSeries:
58
+ """Extract a block from every coefficient of a Laurent matrix series."""
59
+
60
+ rows = row_stop - row_start
61
+ cols = col_stop - col_start
62
+ coefficients: dict[int, sp.Matrix] = {}
63
+ for power, coefficient in series.terms:
64
+ block = matrix_block(coefficient, row_start, row_stop, col_start, col_stop)
65
+ if not is_zero_matrix(block):
66
+ coefficients[power] = block
67
+ return MatrixLaurentSeries.from_mapping(
68
+ series.variable,
69
+ coefficients,
70
+ shape=(rows, cols),
71
+ )
72
+
73
+
74
+ def block_diag_series(
75
+ variable: sp.Symbol,
76
+ series_list: Sequence[MatrixLaurentSeries],
77
+ ) -> MatrixLaurentSeries:
78
+ """Assemble Laurent matrix series as a block diagonal series."""
79
+
80
+ if not series_list:
81
+ return MatrixLaurentSeries.zero(variable, 0, 0)
82
+ if any(series.variable != variable for series in series_list):
83
+ raise ValueError("all block series must use the same variable")
84
+ total_rows = sum(series.rows for series in series_list)
85
+ total_cols = sum(series.cols for series in series_list)
86
+ powers = sorted({power for series in series_list for power, _ in series.terms})
87
+ coefficients: dict[int, sp.Matrix] = {}
88
+ for power in powers:
89
+ blocks = [sp.Matrix(series.coefficient(power)) for series in series_list]
90
+ coefficient = sp.diag(*blocks)
91
+ if not is_zero_matrix(coefficient):
92
+ coefficients[power] = coefficient
93
+ return MatrixLaurentSeries.from_mapping(
94
+ variable,
95
+ coefficients,
96
+ shape=(total_rows, total_cols),
97
+ )
98
+
99
+
100
+ def partition_offsets(dimensions: Sequence[int]) -> tuple[int, ...]:
101
+ """Return cumulative block offsets for ``dimensions``."""
102
+
103
+ offsets = [0]
104
+ for dimension in dimensions:
105
+ offsets.append(offsets[-1] + dimension)
106
+ return tuple(offsets)