sage_pgr 0.1.0__tar.gz
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.
- sage_pgr-0.1.0/LICENSE +21 -0
- sage_pgr-0.1.0/PKG-INFO +32 -0
- sage_pgr-0.1.0/pyproject.toml +21 -0
- sage_pgr-0.1.0/setup.cfg +4 -0
- sage_pgr-0.1.0/src/pgr/PGR.py +450 -0
- sage_pgr-0.1.0/src/pgr/__init__.py +1 -0
- sage_pgr-0.1.0/src/sage_pgr.egg-info/PKG-INFO +32 -0
- sage_pgr-0.1.0/src/sage_pgr.egg-info/SOURCES.txt +8 -0
- sage_pgr-0.1.0/src/sage_pgr.egg-info/dependency_links.txt +1 -0
- sage_pgr-0.1.0/src/sage_pgr.egg-info/top_level.txt +1 -0
sage_pgr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 turnip314
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sage_pgr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sage_pgr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parametric Geometric Resolutions
|
|
5
|
+
Author-email: Andrew Luo <j92luo@uwaterloo.ca>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 turnip314
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: repository, https://github.com/turnip314/sage_pgr
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sage_pgr"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Parametric Geometric Resolutions"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name="Andrew Luo", email="j92luo@uwaterloo.ca" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = { file = "LICENSE" }
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
repository = "https://github.com/turnip314/sage_pgr"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["setuptools>=61.0"]
|
|
18
|
+
build-backend = "setuptools.build_meta"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["src"]
|
sage_pgr-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
from sage.all import QQ, QQbar, log, Ideal, PolynomialRing, xgcd, FractionField, matrix, SR, vector, PowerSeriesRing, TermOrder
|
|
2
|
+
|
|
3
|
+
def debug(*args, verbosity=1):
|
|
4
|
+
threshold = 11
|
|
5
|
+
if verbosity > threshold:
|
|
6
|
+
print(*args)
|
|
7
|
+
|
|
8
|
+
def jacobian(sys, vs):
|
|
9
|
+
return matrix(
|
|
10
|
+
[
|
|
11
|
+
[f.derivative(v) for v in vs]
|
|
12
|
+
for f in sys
|
|
13
|
+
]
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def truncate_base_poly(poly, params, max_degree):
|
|
17
|
+
"""Safely drops terms from a base polynomial exceeding max_degree."""
|
|
18
|
+
if not poly:
|
|
19
|
+
return poly
|
|
20
|
+
|
|
21
|
+
R_base = poly.parent()
|
|
22
|
+
R = PolynomialRing(QQ, len(poly.parent().gens()), list(poly.parent().gens()))
|
|
23
|
+
poly, params = apply_ring_morphism(R, poly, params)
|
|
24
|
+
res = R(0)
|
|
25
|
+
for c, v in poly:
|
|
26
|
+
# exps is a tuple of degrees for each parameter
|
|
27
|
+
if v.degree() < max_degree:
|
|
28
|
+
res += c*v
|
|
29
|
+
|
|
30
|
+
return R_base(res)
|
|
31
|
+
|
|
32
|
+
def expand_fraction(frac, params, max_degree):
|
|
33
|
+
"""Computes the Taylor expansion of N/D up to max_degree."""
|
|
34
|
+
R_base = params[0].parent()
|
|
35
|
+
num = R_base(frac.numerator())
|
|
36
|
+
den = R_base(frac.denominator())
|
|
37
|
+
|
|
38
|
+
# 1. Evaluate the denominator at the origin (params = 0)
|
|
39
|
+
d0_val = den.subs({p: 0 for p in params})
|
|
40
|
+
if d0_val == 0:
|
|
41
|
+
raise ValueError("Division by zero: Denominator vanishes at expansion point.")
|
|
42
|
+
|
|
43
|
+
# 2. Setup 1 / (d0 - M)
|
|
44
|
+
d0 = R_base(d0_val)
|
|
45
|
+
M = d0 - den
|
|
46
|
+
M_over_d0 = M / d0
|
|
47
|
+
|
|
48
|
+
inv_den = R_base(0)
|
|
49
|
+
term = R_base(1) / d0
|
|
50
|
+
|
|
51
|
+
# 3. Accumulate the geometric series
|
|
52
|
+
for _ in range(max_degree):
|
|
53
|
+
inv_den += term
|
|
54
|
+
term = truncate_base_poly(term * M_over_d0, params, max_degree)
|
|
55
|
+
|
|
56
|
+
# Multiply the numerator by the expanded denominator and truncate one last time
|
|
57
|
+
return truncate_base_poly(num * inv_den, params, max_degree)
|
|
58
|
+
|
|
59
|
+
def truncate_coeffs(f, params, degree):
|
|
60
|
+
"""Truncates the fractional coefficients of a polynomial f."""
|
|
61
|
+
res = 0
|
|
62
|
+
for c, v in f:
|
|
63
|
+
c_trunc = expand_fraction(c, params, degree)
|
|
64
|
+
res += c_trunc * v
|
|
65
|
+
return res
|
|
66
|
+
|
|
67
|
+
def mod_by_P(f, P):
|
|
68
|
+
Quo = P.parent().quotient(P)
|
|
69
|
+
return Quo(f).lift()
|
|
70
|
+
|
|
71
|
+
def mod_truncate(f, P, params, degree):
|
|
72
|
+
return truncate_coeffs(mod_by_P(f, P), params, degree)
|
|
73
|
+
|
|
74
|
+
def inv(f, P):
|
|
75
|
+
"""
|
|
76
|
+
Find inverse of f modulo P using extended Euclidean algorithm.
|
|
77
|
+
Returns g such that f*g = 1 mod P.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
Quo = P.parent().quotient(P)
|
|
81
|
+
return Quo(f).inverse().lift()
|
|
82
|
+
|
|
83
|
+
def specialize_system(f_list, param_vars, param_point):
|
|
84
|
+
"""
|
|
85
|
+
Substitute u = p (a concrete tuple) into each polynomial in f_list.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
f_list : list of multivariate polynomials in k[u, x]
|
|
90
|
+
param_vars : sequence of parameter variable objects
|
|
91
|
+
param_point : sequence of field elements (the specialisation p)
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
list of polynomials in k[x]
|
|
96
|
+
"""
|
|
97
|
+
R = f_list[0].parent()
|
|
98
|
+
R_new = PolynomialRing(QQ, [v for v in R.gens() if v not in param_vars])
|
|
99
|
+
R_to_new = R.hom([param_point[i] for i in range(len(param_vars))] + list(R_new.gens()))
|
|
100
|
+
return [R_to_new(f) for f in f_list], list(R_new.gens())
|
|
101
|
+
|
|
102
|
+
def primitive_element(x_vars, coeffs=None):
|
|
103
|
+
"""
|
|
104
|
+
Build the linear form T = sum_i lambda_i * x_i.
|
|
105
|
+
|
|
106
|
+
coeffs : if None, choose random lambda_i in the base field.
|
|
107
|
+
"""
|
|
108
|
+
if coeffs is None:
|
|
109
|
+
# random nonzero coefficients – succeed with high probability
|
|
110
|
+
F = x_vars[0].parent().base_ring()
|
|
111
|
+
coeffs = [F.random_element() for _ in x_vars]
|
|
112
|
+
while 0 in coeffs:
|
|
113
|
+
coeffs = [F.random_element() for _ in x_vars]
|
|
114
|
+
T_expr = sum(c * x for c, x in zip(coeffs, x_vars))
|
|
115
|
+
return T_expr, coeffs
|
|
116
|
+
|
|
117
|
+
def to_shape_lemma(P, Qs, u_):
|
|
118
|
+
Pd = P.derivative(u_)
|
|
119
|
+
Pd_inv = xgcd(Pd, P)[1]
|
|
120
|
+
return [Q*Pd_inv % P for Q in Qs]
|
|
121
|
+
|
|
122
|
+
def to_kronecker(P, Qs, params, u_, prec):
|
|
123
|
+
Pd = P.derivative(u_)
|
|
124
|
+
return [mod_truncate(Q*Pd, P, params, prec) for Q in Qs]
|
|
125
|
+
|
|
126
|
+
def apply_ring_morphism(R, *args):
|
|
127
|
+
res = []
|
|
128
|
+
for fs in args:
|
|
129
|
+
if type(fs) == list:
|
|
130
|
+
res.append([R(f) for f in fs])
|
|
131
|
+
elif type(fs) == tuple:
|
|
132
|
+
res.append((R(f) for f in fs))
|
|
133
|
+
else:
|
|
134
|
+
res.append(R(fs))
|
|
135
|
+
return tuple(res)
|
|
136
|
+
|
|
137
|
+
def construct_rational(gamma, r, params, prec):
|
|
138
|
+
r"""
|
|
139
|
+
Construct rational approximation for power series `r` in variables
|
|
140
|
+
`params` up to specified precision
|
|
141
|
+
"""
|
|
142
|
+
R_base = r.parent()
|
|
143
|
+
|
|
144
|
+
s = SR.var('s')
|
|
145
|
+
ys = list(SR.var('y', len(params)-1)) if len(params) > 1 else []
|
|
146
|
+
|
|
147
|
+
Rs = PolynomialRing(QQ, len(params)+1, [*params, s])
|
|
148
|
+
R_base_to_Rs = R_base.hom(list(Rs.gens())[:-1])
|
|
149
|
+
|
|
150
|
+
r, params = apply_ring_morphism(R_base_to_Rs, r, params)
|
|
151
|
+
r_tilde = Rs(r.subs({v: v*s for v in params[1:]} | {params[0]: s}))
|
|
152
|
+
s = Rs.gens()[-1]
|
|
153
|
+
|
|
154
|
+
R_new_base = PolynomialRing(QQ, len(ys), [*ys]) if ys else QQ
|
|
155
|
+
R_new = PowerSeriesRing(R_new_base, [s])
|
|
156
|
+
R_poly = PolynomialRing(R_new_base, 1, [s])
|
|
157
|
+
flatten_R_new = R_poly.flattening_morphism()
|
|
158
|
+
|
|
159
|
+
# Convert to ring Q[y_2, ..., y_m][s]
|
|
160
|
+
s_new = R_new.gen()
|
|
161
|
+
ys = list(R_new_base.gens())
|
|
162
|
+
r_tilde = R_new(r_tilde.subs({v: y + gamma for v, y, gamma in zip(params[1:],ys, gamma)} | {s: s_new}))
|
|
163
|
+
#debug("rt:", r_tilde)
|
|
164
|
+
|
|
165
|
+
pd = R_new(r_tilde).pade(prec, prec)
|
|
166
|
+
#debug("pd:", pd)
|
|
167
|
+
p, q = pd.numerator(), pd.denominator()
|
|
168
|
+
p, q = R_new(p), R_new(q)
|
|
169
|
+
p = p.truncate(prec)
|
|
170
|
+
q = q.truncate(prec)
|
|
171
|
+
|
|
172
|
+
# Truncate coefficients by prec and convert everything back to polynomial
|
|
173
|
+
p = R_poly(p)
|
|
174
|
+
q = R_poly(q)
|
|
175
|
+
p = sum([c * f for c, f in p if f.degree() < prec])
|
|
176
|
+
q = sum([c * f for c, f in q if f.degree() < prec])
|
|
177
|
+
p, q, s_new = R_poly(p), R_poly(q), R_poly(s_new.polynomial())
|
|
178
|
+
|
|
179
|
+
# Sub back to original variables
|
|
180
|
+
p, q, s_new = apply_ring_morphism(flatten_R_new, p, q, s_new)
|
|
181
|
+
|
|
182
|
+
# 2. Build the exact algebraic inverse map
|
|
183
|
+
# s maps to c (params[0])
|
|
184
|
+
# y_i maps to (v / c) - gamma
|
|
185
|
+
subs_dict = {flatten_R_new(s_new): R_base(params[0])}
|
|
186
|
+
for y, v, g in zip(ys, params[1:], gamma):
|
|
187
|
+
subs_dict[flatten_R_new(y)] = R_base(v) / R_base(params[0]) - R_base(g)
|
|
188
|
+
|
|
189
|
+
# 3. Perform substitution directly.
|
|
190
|
+
# Because the values are in Frac_R, SageMath handles all division and clearing of denominators safely.
|
|
191
|
+
r_final = p.subs(subs_dict) / q.subs(subs_dict)
|
|
192
|
+
|
|
193
|
+
return r_final
|
|
194
|
+
|
|
195
|
+
def rational_reconstruction(gamma, P, kronecker_param, params, prec):
|
|
196
|
+
r"""
|
|
197
|
+
Construct rational approximation for coefficients of `P` and `kronecker_param`
|
|
198
|
+
as rational functions in `params` up to specified precision
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
debug("RECON START")
|
|
202
|
+
|
|
203
|
+
# Convert params to base ring
|
|
204
|
+
R_base = params[0].parent()
|
|
205
|
+
|
|
206
|
+
# Reconstruct coefficients of P as a polynomial in u_ with coefficients in params
|
|
207
|
+
debug()
|
|
208
|
+
debug("Reconstructing P")
|
|
209
|
+
debug(P.parent())
|
|
210
|
+
rat_P = sum(
|
|
211
|
+
[construct_rational(gamma, r, params, prec)*v.change_ring(R_base) for r, v in P]
|
|
212
|
+
)
|
|
213
|
+
debug("RECON P:", P)
|
|
214
|
+
debug()
|
|
215
|
+
|
|
216
|
+
# Same thing with all kronecker terms
|
|
217
|
+
debug("Reconstructing kronecker")
|
|
218
|
+
debug(kronecker_param[0].parent())
|
|
219
|
+
rat_kronecker = [
|
|
220
|
+
sum([construct_rational(gamma, r, params, prec)*v.change_ring(R_base) for r, v in f])
|
|
221
|
+
for f in kronecker_param
|
|
222
|
+
]
|
|
223
|
+
debug()
|
|
224
|
+
|
|
225
|
+
return rat_P, rat_kronecker
|
|
226
|
+
|
|
227
|
+
def newton_lift(F, P, shape_param, vs, u_, params, linear_form, prec):
|
|
228
|
+
r"""
|
|
229
|
+
Lift precision of power series approximation for shape parametrization of `F`
|
|
230
|
+
"""
|
|
231
|
+
# 1. Define the substitution X -> V(U)
|
|
232
|
+
Tsubs = {v: w for v, w in zip(vs, shape_param)}
|
|
233
|
+
debug("prec:", prec)
|
|
234
|
+
|
|
235
|
+
# sys = F in the algorithm by Schost
|
|
236
|
+
T = [v - w for v, w in zip(vs, shape_param)] + [P]
|
|
237
|
+
sys = F + [u_ - linear_form]
|
|
238
|
+
|
|
239
|
+
# 2. Evaluate Jacobians and explicitly substitute X = V(U) early
|
|
240
|
+
# This ensures no 'x' or 'y' variables sneak into the matrix inverses.
|
|
241
|
+
JacF = jacobian(sys, [*vs, u_]).subs(Tsubs)
|
|
242
|
+
JacT = jacobian(T, [*vs, u_]).subs(Tsubs)
|
|
243
|
+
debug("JacF:", JacF)
|
|
244
|
+
|
|
245
|
+
# 3. Invert JacF in the precision-k quotient ring (modulo P and params^prec)
|
|
246
|
+
R = u_.parent()
|
|
247
|
+
R_base = R.base_ring()
|
|
248
|
+
|
|
249
|
+
debug("T:", T)
|
|
250
|
+
debug("sys:", sys)
|
|
251
|
+
|
|
252
|
+
# 5. Evaluate the defect (sys) at X = V(U) to retain terms up to O(params^{2*prec})
|
|
253
|
+
sys_eval = vector([f.subs(Tsubs) for f in sys])
|
|
254
|
+
JacF_inv_times_sys_eval = JacF.solve_right(sys_eval)
|
|
255
|
+
JacF_inv_times_sys_eval = vector(
|
|
256
|
+
[f.numerator() * inv(f.denominator(), P) for f in JacF_inv_times_sys_eval]
|
|
257
|
+
)
|
|
258
|
+
debug("eval:", sys_eval)
|
|
259
|
+
|
|
260
|
+
# 6. Multiply M by the defect and reduce modulo P and params^{2*prec}
|
|
261
|
+
deltas = JacT * JacF_inv_times_sys_eval #M * sys_eval
|
|
262
|
+
debug("deltas:", deltas)
|
|
263
|
+
deltas = [mod_truncate(d, P, params, 2*prec) for d in deltas]
|
|
264
|
+
debug("P:", P)
|
|
265
|
+
debug("deltas:", deltas)
|
|
266
|
+
|
|
267
|
+
# 7. Apply updates: V_new = V_old - delta_V, P_new = P_old + delta_P
|
|
268
|
+
new_shape_param = [truncate_coeffs(w-delta, params, 2*prec) for w, delta in zip(shape_param, deltas[:-1])]
|
|
269
|
+
newP = truncate_coeffs(P+deltas[-1], params, 2*prec)
|
|
270
|
+
debug("oldP", P)
|
|
271
|
+
debug("P_update", deltas[-1])
|
|
272
|
+
debug("test:", P+deltas[-1])
|
|
273
|
+
debug("newP", newP)
|
|
274
|
+
|
|
275
|
+
return newP, new_shape_param
|
|
276
|
+
|
|
277
|
+
def stop_criterion(F, P, params, kronecker_param, test_param, u_, vs):
|
|
278
|
+
r"""
|
|
279
|
+
|
|
280
|
+
"""
|
|
281
|
+
R_base = params[0].parent()
|
|
282
|
+
test_subs = {v:p for v, p in zip(params, test_param)}
|
|
283
|
+
f_subs = {v:q for v, q in zip(vs, kronecker_param)}
|
|
284
|
+
|
|
285
|
+
# Test 1: Check that no denominators vanish at test parameter
|
|
286
|
+
for coeff in P.coefficients():
|
|
287
|
+
if R_base(coeff.denominator()).subs(test_subs) == 0:
|
|
288
|
+
debug("Coefficient vanishes at test point", verbosity=10)
|
|
289
|
+
return False
|
|
290
|
+
for f in kronecker_param:
|
|
291
|
+
for coeff in f.coefficients():
|
|
292
|
+
if R_base(coeff.denominator()).subs(test_subs) == 0:
|
|
293
|
+
debug("Coefficient vanishes at test point", verbosity=10)
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
def specialize_at_param(f, subs):
|
|
297
|
+
return sum(c.subs(subs) * v for c, v in f).change_ring(QQ)
|
|
298
|
+
|
|
299
|
+
# Test 2: Check that P is square-free when specialized at test parameter
|
|
300
|
+
P_specialized = specialize_at_param(P, test_subs)
|
|
301
|
+
if not P_specialized.is_squarefree():
|
|
302
|
+
debug("P is not square-free", verbosity=10)
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
# Test 3: Check that the roots of P when specialized at test parameter
|
|
306
|
+
# give solutions to the original system
|
|
307
|
+
kronecker_specialized = [
|
|
308
|
+
specialize_at_param(f, test_subs)
|
|
309
|
+
for f in kronecker_param
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
R_specialized = P_specialized.parent()
|
|
313
|
+
u_ = R_specialized(u_)
|
|
314
|
+
vs = [R_specialized(v) for v in vs]
|
|
315
|
+
F_specialized = [
|
|
316
|
+
sum(c.subs(test_subs) * v for c, v in f).change_ring(QQ)
|
|
317
|
+
for f in F
|
|
318
|
+
]
|
|
319
|
+
debug("F_specialized", F_specialized, verbosity=10)
|
|
320
|
+
debug("P_Specialized:", P_specialized, verbosity=10)
|
|
321
|
+
debug("kronecker_specialized:", kronecker_specialized, verbosity=10)
|
|
322
|
+
sols = []
|
|
323
|
+
for u in P_specialized.polynomial(u_).roots(QQbar, multiplicities=False):
|
|
324
|
+
sols.append(
|
|
325
|
+
{
|
|
326
|
+
v: QQbar((Q/P_specialized.derivative(u_)).subs({u_:u}))
|
|
327
|
+
for v, Q in zip(vs, kronecker_specialized)
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
debug("sols:", sols, verbosity=10)
|
|
331
|
+
|
|
332
|
+
for f in F_specialized:
|
|
333
|
+
if any(f.subs(sol) != 0 for sol in sols):
|
|
334
|
+
debug(f, verbosity=5)
|
|
335
|
+
debug(sols, verbosity=5)
|
|
336
|
+
for sol in sols:
|
|
337
|
+
debug(f.subs(sol), verbosity=5)
|
|
338
|
+
debug("Solution not satisfied", verbosity=10)
|
|
339
|
+
return False
|
|
340
|
+
|
|
341
|
+
det = jacobian(F_specialized, vs).determinant()
|
|
342
|
+
if any(det.subs(sol) == 0 for sol in sols):
|
|
343
|
+
debug("Singular Jacobian", verbosity=10)
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
def parametric_geometric_resolution(F, num_params, param_point=None, test_param=None, gamma=None):
|
|
349
|
+
r"""
|
|
350
|
+
Comput a parametric geometric resolution for system of polynomials `F = (f_1,\ldots,f_n)` in
|
|
351
|
+
`\mathbb{Q}[p_1,\ldots,p_m,x_1,\ldots,x_n]` where ``m = num_params``.
|
|
352
|
+
"""
|
|
353
|
+
# Separate input by parameters and variables
|
|
354
|
+
R_original = F[0].parent()
|
|
355
|
+
params = list(R_original.gens())[:num_params]
|
|
356
|
+
vs = list(R_original.gens())[num_params:]
|
|
357
|
+
|
|
358
|
+
# Specialize F at random parameter point
|
|
359
|
+
if param_point is None:
|
|
360
|
+
param_point = [QQ.random_element(10) for _ in params]
|
|
361
|
+
if test_param is None:
|
|
362
|
+
test_param = [QQ.random_element(10) for _ in params]
|
|
363
|
+
if gamma is None:
|
|
364
|
+
gamma = [QQ.random_element(10) for _ in params[:-1]]
|
|
365
|
+
|
|
366
|
+
# Shift F over by param point, so expansions are done around 0
|
|
367
|
+
F = [f.subs({v:v+p for v, p in zip(params, param_point)}) for f in F]
|
|
368
|
+
|
|
369
|
+
#linear_form, lam = primitive_element(vs)
|
|
370
|
+
F_p, vsp = specialize_system(F, params, [0 for _ in params])
|
|
371
|
+
|
|
372
|
+
# Compute kronecker and shape representations at specialized point
|
|
373
|
+
from sage_acsv import ACSVSettings as AS
|
|
374
|
+
AS.set_default_kronecker_backend(AS.Kronecker.MSOLVE)
|
|
375
|
+
from sage_acsv.kronecker import kronecker_representation
|
|
376
|
+
P, kronecker_param, linear_form = kronecker_representation(F_p, vsp, return_linear_form=True)
|
|
377
|
+
u_ = P.parent().gen()
|
|
378
|
+
shape_param = to_shape_lemma(P, kronecker_param, u_)
|
|
379
|
+
|
|
380
|
+
# Generate rings that will be used throughout
|
|
381
|
+
R_base = PolynomialRing(QQ, len(params), params).fraction_field()
|
|
382
|
+
params = [R_base(p) for p in params]
|
|
383
|
+
R = PolynomialRing(R_base, vs + [u_])
|
|
384
|
+
original_to_R = R_original.hom(
|
|
385
|
+
list(R_base.gens()) + list(R.gens())[:-1]
|
|
386
|
+
)
|
|
387
|
+
F, vs = apply_ring_morphism(original_to_R, F, vs)
|
|
388
|
+
|
|
389
|
+
u_to_R = u_.parent().hom([R.gens()[-1]])
|
|
390
|
+
u_, P, kronecker_param, shape_param = apply_ring_morphism(
|
|
391
|
+
u_to_R, u_, P, kronecker_param, shape_param
|
|
392
|
+
)
|
|
393
|
+
debug("Original:", verbosity=10)
|
|
394
|
+
debug("linear_form:", linear_form, verbosity=10)
|
|
395
|
+
debug("P:", P, verbosity=10)
|
|
396
|
+
debug("kronecker:", kronecker_param, verbosity=10)
|
|
397
|
+
debug("shape:", shape_param, verbosity=10)
|
|
398
|
+
debug(verbosity=10)
|
|
399
|
+
|
|
400
|
+
D = max(f.degree() for f in F)
|
|
401
|
+
n = len(F)
|
|
402
|
+
MAX_ITER = int(log(2*D**n+1, 2))+1
|
|
403
|
+
|
|
404
|
+
prec = 1
|
|
405
|
+
for kappa in range(MAX_ITER):
|
|
406
|
+
# Convert shape param to kronecker
|
|
407
|
+
debug("ITER:", kappa, verbosity=10)
|
|
408
|
+
kronecker_param = to_kronecker(P, shape_param, params, u_, prec)
|
|
409
|
+
debug("TO KRONECKER", verbosity=10)
|
|
410
|
+
debug(P, verbosity=10)
|
|
411
|
+
debug(kronecker_param, verbosity=10)
|
|
412
|
+
debug(verbosity=10)
|
|
413
|
+
|
|
414
|
+
if prec >= 2:
|
|
415
|
+
# Reconstruct rational coefficients and check if done
|
|
416
|
+
rational_P, rational_params = rational_reconstruction(gamma, P, kronecker_param, params, prec//2)
|
|
417
|
+
debug("RECONSTRUCTED KRONECKER:", verbosity=10)
|
|
418
|
+
debug(rational_P, verbosity=10)
|
|
419
|
+
debug(rational_params, verbosity=10)
|
|
420
|
+
debug(verbosity=10)
|
|
421
|
+
debug("STOP CRITERION:", verbosity=10)
|
|
422
|
+
if stop_criterion(F, rational_P, params, rational_params, test_param, u_, vs):
|
|
423
|
+
# Shift params back
|
|
424
|
+
rational_P = sum(
|
|
425
|
+
c.subs({v:v-p for v, p in zip(params, param_point)}) * v for c, v in rational_P
|
|
426
|
+
)
|
|
427
|
+
rational_params = [
|
|
428
|
+
sum(
|
|
429
|
+
c.subs({v:v-p for v, p in zip(params, param_point)}) * v for c, v in f
|
|
430
|
+
)
|
|
431
|
+
for f in rational_params
|
|
432
|
+
]
|
|
433
|
+
debug("DONE", verbosity=10)
|
|
434
|
+
return rational_P, rational_params
|
|
435
|
+
else:
|
|
436
|
+
debug("CRITERION FAILED", verbosity=10)
|
|
437
|
+
debug(verbosity=10)
|
|
438
|
+
|
|
439
|
+
# Lift shape param to higher order
|
|
440
|
+
P, shape_param = newton_lift(F, P, shape_param, vs, u_, params, linear_form, prec)
|
|
441
|
+
debug("LIFTED", verbosity=10)
|
|
442
|
+
debug(P, verbosity=10)
|
|
443
|
+
debug(shape_param, verbosity=10)
|
|
444
|
+
debug(verbosity=10)
|
|
445
|
+
|
|
446
|
+
prec *= 2
|
|
447
|
+
debug(verbosity=10)
|
|
448
|
+
debug("------------------------------------", verbosity=10)
|
|
449
|
+
|
|
450
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from pgr.PGR import *
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sage_pgr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parametric Geometric Resolutions
|
|
5
|
+
Author-email: Andrew Luo <j92luo@uwaterloo.ca>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 turnip314
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: repository, https://github.com/turnip314/sage_pgr
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Dynamic: license-file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pgr
|