1+ @doc Markdown. doc"""
2+ rational_curve_parametrization(I::Ideal{T} where T <: MPolyRingElem, <keyword arguments>)
3+
4+ Given a **radical** ideal `I` with solution set X being of dimension 1 over the complex numbers,
5+ return a rational curve parametrization of the one-dimensional irreducible components of X.
6+
7+ In the output, the variables `x`,`y` of the parametrization correspond to the last two
8+ entries of the `vars` attribute, in that order.
9+
10+ **Note**: At the moment only QQ is supported as ground field. If the dimension of the ideal
11+ is not one an ErrorException is thrown.
12+
13+ # Arguments
14+ - `I::Ideal{T} where T <: QQMPolyRingElem`: input generators.
15+ - `info_level::Int=0`: info level printout: off (`0`, default), summary (`1`), detailed (`2`).
16+ - `use_lfs::Bool=false`: add new variables (_Z2, _Z1) + 2 generic linear forms
17+ - `cfs_lfs::Vector{Vector{ZZRingElem}} = []`: coefficients for the above linear forms
18+ - `nr_thrds::Int=1`: number of threads for msolve
19+ - `check_gen::Bool = true`: perform some genericity position checks on the last two variables
20+
21+ # Examples
22+ ```jldoctest
23+ julia> using AlgebraicSolving
24+
25+ julia> R, (x1,x2,x3) = polynomial_ring(QQ, ["x1","x2","x3"])
26+ (Multivariate polynomial ring in 3 variables over QQ, QQMPolyRingElem[x1, x2, x3])
27+
28+ julia> I = Ideal([x1+2*x2+2*x3-1, x1^2+2*x2^2+2*x3^2-x1])
29+ QQMPolyRingElem[x1 + 2*x2 + 2*x3 - 1, x1^2 - x1 + 2*x2^2 + 2*x3^2]
30+
31+ julia> rational_curve_parametrization(I)
32+ AlgebraicSolving.RationalCurveParametrization([:x1, :x2, :x3], Vector{ZZRingElem}[], x^2 + 4//3*x*y - 1//3*x + y^2 - 1//3*y, 4//3*x + 2*y - 1//3, QQMPolyRingElem[4//3*x^2 - 4//3*x*y + 2//3*x + 4//3*y - 1//3])
33+
34+ julia> rational_curve_parametrization(I, cfs_lfs=map.(Ref(ZZ),[[-8,2,2,-1,-8], [8,-7,-5,8,-7]]))
35+ AlgebraicSolving.RationalCurveParametrization([:x1, :x2, :x3, :_Z2, :_Z1], Vector{ZZRingElem}[[-8, 2, 2, -1, -8], [8, -7, -5, 8, -7]], 4963//30508*x^2 - 6134//7627*x*y - 647//7627*x + y^2 + 1640//7627*y + 88//7627, -6134//7627*x + 2*y + 1640//7627, QQMPolyRingElem[8662//22881*x^2 - 21442//22881*x*y - 2014//7627*x + 9458//22881*y + 1016//22881, -2769//30508*x^2 + 4047//15254*x*y - 875//7627*x + 3224//7627*y + 344//7627, -9017//91524*x^2 + 9301//45762*x*y - 1185//7627*x + 8480//22881*y + 920//22881])
36+ ```
37+ """
38+ function rational_curve_parametrization (
39+ I:: Ideal{P} where P<: QQMPolyRingElem ; # input generators
40+ info_level:: Int = 0 , # info level for print outs
41+ use_lfs:: Bool = false , # add generic variables
42+ cfs_lfs:: Vector{Vector{ZZRingElem}} = Vector{ZZRingElem}[], # coeffs of linear forms
43+ nr_thrds:: Int = 1 , # number of threads (msolve)
44+ check_gen:: Bool = true # perform genericity check
45+ )
46+ info_level> 0 && println (" Compute ideal data" * (check_gen ? " and genericity check" : " " ))
47+ lucky_prime = _generate_lucky_primes (I. gens, one (ZZ)<< 30 , one (ZZ)<< 31 - 1 , 1 ) |> first
48+ Itest = Ideal (change_base_ring .(Ref (GF (lucky_prime)), I. gens))
49+ Itest. dim = I. dim
50+ dimension (Itest)
51+ if Itest. dim == - 1
52+ T = polynomial_ring (QQ, [:x ,:y ])[1 ]
53+ I. dim = - 1
54+ I. rat_param = RationalCurveParametrization (Symbol[], Vector{ZZRingElem}[], T (- 1 ), T (- 1 ), QQMPolyRingElem[])
55+ return I. rat_param
56+ end
57+ @assert (Itest. dim== 1 , " I must define a curve or an empty set" )
58+ if nvars (parent (I)) == 1
59+ T, (x,y) = polynomial_ring (QQ, [:x ,:y ])
60+ I. dim = 1
61+ I. rat_param = RationalCurveParametrization (Symbol[:x , :y ], Vector{ZZRingElem}[[0 ,1 ]], y, T (1 ), QQMPolyRingElem[])
62+ return I. rat_param
63+ end
64+
65+ DEG = hilbert_degree (Itest)
66+ # If generic variables must be added
67+ F, cfs_lfs = use_lfs ? _add_genvars (I. gens, max (2 , length (cfs_lfs)), cfs_lfs) :
68+ ! isempty (cfs_lfs) ? _add_genvars (I. gens, length (cfs_lfs), cfs_lfs) :
69+ (I. gens, Vector{ZZRingElem}[])
70+ R = parent (first (F))
71+ N = nvars (R)
72+ if check_gen let
73+ val = [ZZ (), ZZ ()]
74+ # Bound on bifurcation set degree (e.g. Jelonek & Kurdyka, 2005)
75+ bif_bound = ZZ (1 ) << ( N * floor (Int, log2 (maximum (total_degree .(F)))) + 1 )
76+ while any (is_divisible_by .(val, Ref (lucky_prime)))
77+ val = rand (- bif_bound: bif_bound, 2 )
78+ end
79+ for ivar in [N- 1 , N]
80+ Fnew = vcat (F, val[1 ]* gens (R)[ivar] + val[2 ])
81+ new_lucky_prime = _generate_lucky_primes (Fnew, one (ZZ)<< 30 , one (ZZ)<< 31 - 1 , 1 ) |> first
82+ local INEW = Ideal (change_base_ring .(Ref (GF (new_lucky_prime)), Fnew))
83+ @assert (dimension (INEW) == 0 && hilbert_degree (INEW) == DEG, " The curve is not in generic position" )
84+ end
85+ end end
86+
87+ # Compute DEG+2 evaluations of x in the param (whose total deg is bounded by DEG)
88+ PARAM = Vector {Vector{QQPolyRingElem}} (undef, DEG+ 2 )
89+ _values = Vector {ZZRingElem} (undef, DEG+ 2 )
90+ i = 1
91+ free_ind = collect (1 : DEG+ 2 )
92+ used_ind = zeros (Bool, DEG+ 2 )
93+ lc = nothing
94+ while length (free_ind) > 0
95+ if i > 2 * (DEG+ 2 )
96+ error (" Too many bad specializations. Check radicality, else permute variables or use_lfs=true" )
97+ end
98+ # Evaluation of the generator at values x s.t. 0 <= |x|-i <= length(free_ind)/2
99+ # plus one point at -(length(free_ind)+1)/2 if the length if odd.
100+ # This reduces a bit the bitsize of the evaluation
101+ curr_values = ZZ .([- (i- 1 + (length (free_ind)+ 1 )÷ 2 ): - i;i: (i- 1 + length (free_ind)÷ 2 )])
102+ LFeval = Ideal .(_evalvar (F, N- 1 , curr_values))
103+ # Compute parametrization of each evaluation
104+ Lr = Vector {RationalParametrization} (undef, length (free_ind))
105+ for j in 1 : length (free_ind)
106+ info_level> 0 && print (" Evaluated parametrizations: $(j+ DEG+ 2 - length (free_ind)) /$(DEG+ 2 ) " , " \r " )
107+ Lr[j] = rational_parametrization (LFeval[j], nr_thrds= nr_thrds)
108+ end
109+ info_level> 0 && println ()
110+ for j in 1 : length (free_ind)
111+ # Specialization checks: same vars order, generic degree
112+ if Lr[j]. vars == [symbols (R)[1 : N- 2 ]; symbols (R)[N]] && degree (Lr[j]. elim) == DEG
113+ if isnothing (lc)
114+ lc = leading_coefficient (Lr[j]. elim)
115+ rr = [ p for p in vcat (Lr[j]. elim, Lr[j]. denom, Lr[j]. param) ]
116+ else
117+ # Adjust when the rat_param is multiplied by some constant factor
118+ fact = lc / leading_coefficient (Lr[j]. elim)
119+ rr = [ p* fact for p in vcat (Lr[j]. elim, Lr[j]. denom, Lr[j]. param) ]
120+ end
121+ PARAM[j] = rr
122+ _values[j] = curr_values[j]
123+ used_ind[j] = true
124+ else
125+ info_level> 0 && println (" bad specialization: " , i+ j- 1 )
126+ end
127+ end
128+ i += length (free_ind)
129+ free_ind = [ free_ind[j] for j in eachindex (free_ind) if ! used_ind[j] ]
130+ used_ind = zeros (Bool, length (free_ind))
131+ end
132+
133+ # Interpolate each coefficient of each poly in the param
134+ T = polynomial_ring (QQ, [:x ,:y ])[1 ]
135+ A = polynomial_ring (QQ)[1 ]
136+
137+ POLY_PARAM = Vector {QQMPolyRingElem} (undef,N)
138+ for count in 1 : N
139+ info_level> 0 && print (" Interpolate parametrizations: $count /$N \r " )
140+ COEFFS = Vector {QQPolyRingElem} (undef, DEG+ 1 )
141+ for deg in 0 : DEG
142+ _evals = [coeff (PARAM[i][count], deg) for i in eachindex (PARAM)]
143+ # Remove denominators for faster interpolation with FLINT
144+ den = foldl (lcm, denominator .(_evals))
145+ scaled_evals = [ZZ (_evals[i] * den) for i in eachindex (_evals)]
146+ COEFFS[deg+ 1 ] = interpolate (A, _values, scaled_evals) / (lc* den)
147+ end
148+ ctx = MPolyBuildCtx (T)
149+ for (i, c) in enumerate (COEFFS)
150+ for (j, coeff) in enumerate (coefficients (c))
151+ ! iszero (coeff) && push_term! (ctx, coeff, [j- 1 , i- 1 ])
152+ end
153+ end
154+ POLY_PARAM[count] = finish (ctx)
155+ end
156+ info_level> 0 && println ()
157+ # Output: [vars, linear forms, elim, denom, [nums_param]]
158+ I. dim = 1 # If we reached here, I has necessarily dimension 1
159+ I. rat_param = RationalCurveParametrization ( symbols (R), cfs_lfs, POLY_PARAM[1 ],
160+ POLY_PARAM[2 ], POLY_PARAM[3 : end ] )
161+ return I. rat_param
162+ end
163+
164+
165+ # Inject polynomials in F in a polynomial ring with ngenvars new variables
166+ # return these polynomials
167+ # + newvars linear forms provided by coefficients in cfs_lfs + random ones
168+ function _add_genvars (
169+ F:: Vector{P} where P<: MPolyRingElem ,
170+ ngenvars:: Int ,
171+ cfs_lfs:: Vector{Vector{ZZRingElem}} = Vector{ZZRingElem}[]
172+ )
173+ if length (cfs_lfs) > ngenvars
174+ error (" Too many linear forms provided ($(length (cfs_lfs)) >$(ngenvars) )" )
175+ end
176+ R = parent (first (F))
177+ N = nvars (R)
178+ # Add new variables (reverse alphabetical order)
179+ newS = vcat (symbols (R), Symbol .([" _Z$i " for i in ngenvars: - 1 : 1 ]))
180+ R_ext, all_vars = polynomial_ring (base_ring (R), newS)
181+ # Inject F in this new ring
182+ Fnew = Vector {MPolyRingElem} (undef, length (F))
183+ ctx = MPolyBuildCtx (R_ext)
184+ for i in eachindex (F)
185+ for (e, c) in zip (exponent_vectors (F[i]), coefficients (F[i]))
186+ push_term! (ctx, c, vcat (e, zeros (Int,ngenvars)))
187+ end
188+ Fnew[i] = finish (ctx)
189+ end
190+
191+ # Complete possible incomplete provided linear forms
192+ for i in eachindex (cfs_lfs)
193+ if N+ ngenvars < length (cfs_lfs[i])
194+ error (" Too many coeffs ($(length (cfs_lfs[i])) >$(N+ ngenvars) ) for the $(i) th linear form" )
195+ else
196+ append! (cfs_lfs[i], rand (ZZ .(setdiff (- 100 : 100 ,0 )), N+ ngenvars - length (cfs_lfs[i])))
197+ end
198+ end
199+ # Add missing linear forms if needed
200+ append! (cfs_lfs, [rand (ZZ .(setdiff (- 100 : 100 ,0 )), N+ ngenvars) for _ in 1 : ngenvars- length (cfs_lfs)])
201+ # Construct and append linear forms
202+ append! (Fnew, [transpose (cfs_lf) * all_vars for cfs_lf in cfs_lfs])
203+
204+ return Fnew, cfs_lfs
205+ end
206+
207+ # for each a in La, evaluate each poly in F in x_i=a
208+ function _evalvar (
209+ F:: Vector{P} where P<: MPolyRingElem ,
210+ i:: Int ,
211+ La:: Vector{T} where T<: RingElem
212+ )
213+ R = parent (first (F))
214+ indnewvars = setdiff (1 : nvars (R), i)
215+ C, = polynomial_ring (base_ring (R), symbols (R)[indnewvars])
216+ LFeval = Vector{typeof (zero (C))}[]
217+ ctx = MPolyBuildCtx (C)
218+
219+ for a in La
220+ powa = Dict (0 => one (parent (a)), 1 => a) # no recompute powers
221+ push! (LFeval, typeof (zero (C))[])
222+ for f in F
223+ for (e,c) in zip (exponent_vectors (f), coefficients (f))
224+ aei = get! (powa, e[i]) do
225+ a^ e[i]
226+ end
227+ push_term! (ctx, c* aei, [e[j] for j in indnewvars ])
228+ end
229+ push! (LFeval[end ], finish (ctx))
230+ end
231+ end
232+ return LFeval
233+ end
234+
235+ # Generate N primes > start that do not divide any numerator/denominator
236+ # of any coefficient in polynomials from LP
237+ function _generate_lucky_primes (
238+ LF:: Vector{P} where P<: MPolyRingElem ,
239+ low:: ZZRingElem ,
240+ up:: ZZRingElem ,
241+ N:: Int64
242+ )
243+ # Avoid repetitive enumeration and redundant divisibility check
244+ CF = ZZRingElem[]
245+ for f in LF, c in coefficients (f), part in (numerator (c), denominator (c))
246+ if ! isone (part)
247+ push! (CF, part)
248+ end
249+ end
250+ sort! (CF, rev= true )
251+ unique! (CF)
252+
253+ # Test primes
254+ Lprim = ZZRingElem[]
255+ while length (Lprim) < N
256+ cur_prim = next_prime (rand (low: up))
257+ is_lucky = ! (cur_prim in Lprim)
258+ i = firstindex (CF)
259+ # Exploit decreasing order of CF
260+ while is_lucky && i <= lastindex (CF) && CF[i] > cur_prim
261+ is_lucky = ! is_divisible_by (CF[i], cur_prim)
262+ i += 1
263+ end
264+ is_lucky && push! (Lprim, cur_prim)
265+ end
266+ return Lprim
267+ end
0 commit comments