217217
218218# ------------------------------------------------------------------------------
219219
220+ # checks containment under assumption that rat_funcs are algebraic over the field
221+ function field_contains_algebraic_mod_p(field, rat_funcs, prime)
222+ ff = Nemo. Native. GF(prime)
223+ mqs_generators = field. mqs_membership
224+ reduce_mod_p!(mqs_generators, ff)
225+
226+ param_ring = ParamPunPam. parent_params(mqs_generators)
227+ point = ParamPunPam. distinct_nonzero_points(ff, nvars(param_ring))
228+
229+ gens_specialized = ParamPunPam. specialize_mod_p(mqs_generators, point)
230+ ratfuncs_mqs_specialized = specialize_fracs_to_mqs(mqs_generators, rat_funcs, point)
231+ @assert parent(first(gens_specialized)) == parent(first(ratfuncs_mqs_specialized))
232+ gb = groebner(gens_specialized)
233+ return map(iszero, normalform(gb, ratfuncs_mqs_specialized))
234+ end
235+
220236"""
221237 field_contains_mod_p(field, rat_funcs, prime)
222238
@@ -247,21 +263,10 @@ Output:
247263 end
248264 ratfuncs_algebraic = ratfuncs[algebraicity]
249265
250- ff = Nemo. Native. GF(prime)
251- mqs_generators = field. mqs_membership
252- reduce_mod_p!(mqs_generators, ff)
253-
254- param_ring = ParamPunPam. parent_params(mqs_generators)
255- point = ParamPunPam. distinct_nonzero_points(ff, nvars(param_ring))
256-
257- gens_specialized = ParamPunPam. specialize_mod_p(mqs_generators, point)
258- ratfuncs_mqs_specialized =
259- specialize_fracs_to_mqs(mqs_generators, ratfuncs_algebraic, point)
260- @assert parent(first(gens_specialized)) == parent(first(ratfuncs_mqs_specialized))
261- gb = groebner(gens_specialized)
262- d_nf = normalform(gb, ratfuncs_mqs_specialized)
263- result = map(iszero, d_nf)
264- return merge_results(algebraicity, result)
266+ return merge_results(
267+ algebraicity,
268+ field_contains_algebraic_mod_p(field, ratfuncs_algebraic, prime),
269+ )
265270end
266271
267272function field_contains_mod_p(
@@ -277,60 +282,34 @@ function issubfield_mod_p(
277282 E:: RationalFunctionField{T} ,
278283 prime = 2 ^ 31 - 1 ,
279284) where {T}
280- return all(field_contains_mod_p(E, F. dennums, prime))
285+ if ! all(check_algebraicity_modp(E, generators(F), prime))
286+ return false
287+ end
288+ return all(field_contains_algebraic_mod_p(E, generators(F), prime))
281289end
282290
283291# ------------------------------------------------------------------------------
284292
285- """
286- field_contains(field, ratfuncs, prob_threshold)
287-
288- Checks whether given rational function field `field` contains given rational
289- functions `ratfuncs`. The result is correct with probability at least `prob_threshold`
290-
291- Inputs:
292- - `field` - a rational function field
293- - `ratfuncs` - a list of rational functions
294- - `prob_threshold` real number from (0, 1)
295-
296- Output:
297- - a list `L[i]` of bools of length `length(rat_funcs)` such that `L[i]` is true iff
298- the i-th function belongs to `field`
299- """
300- @timeit _to function field_contains(
301- field:: RationalFunctionField{T} ,
302- ratfuncs:: Vector{Generic.FracFieldElem{T}} ,
303- prob_threshold,
304- ) where {T}
305- if isempty(ratfuncs)
306- return Bool[]
307- end
308-
309- half_p = 1 - (1 - prob_threshold) / 2
310-
311- algebraicity = check_algebraicity(field, ratfuncs, half_p)
312- ratfuncs_algebraic = ratfuncs[algebraicity]
313- if isempty(ratfuncs_algebraic)
314- return algebraicity
315- end
316-
293+ # checks containment under assumption that rat_funcs are algebraic over the field
294+ function field_contains_algebraic(field, ratfuncs, prob_threshold)
295+ start_time = time_ns()
317296 @debug " Estimating the sampling bound"
318297
319298 # uses Theorem 3.3 from https://arxiv.org/pdf/2111.00991.pdf
320299 # the comments below use the notation from the theorem
321- ratfuncs_algebraic = [
300+ ratfuncs = [
322301 (iszero(f) || (total_degree(numerator(f)) > total_degree(denominator(f)))) ? f :
323- 1 // f for f in ratfuncs_algebraic
302+ 1 // f for f in ratfuncs
324303 ]
325- denoms = map(denominator, ratfuncs_algebraic )
326- ring = parent(numerator(first(ratfuncs_algebraic )))
304+ denoms = map(denominator, ratfuncs )
305+ ring = parent(numerator(first(ratfuncs )))
327306 den_lcm = reduce(lcm, field. mqs. dens_to_sat_orig, init = reduce(lcm, denoms))
328307 @debug " Common lcm is $den_lcm "
329308
330309 # this is deg(g) + 1
331310 degree = total_degree(den_lcm) + 1
332311 # computing maximum of deg(f) for different f's to be tested
333- for (i, f) in enumerate(ratfuncs_algebraic )
312+ for (i, f) in enumerate(ratfuncs )
334313 extra_degree = total_degree(den_lcm) - total_degree(denominator(f))
335314 degree = max(degree, extra_degree + total_degree(numerator(f)))
336315 end
@@ -349,7 +328,7 @@ Output:
349328 sampling_bound = BigInt(
350329 3 *
351330 BigInt(degree)^ (length(total_vars) + 3 ) *
352- (length(ratfuncs_algebraic )) *
331+ (length(ratfuncs )) *
353332 ceil(1 / (1 - prob_threshold)),
354333 )
355334
@@ -359,12 +338,56 @@ Output:
359338 point = map(v -> Nemo. QQ(rand((- sampling_bound): sampling_bound)), gens(param_ring))
360339 mqs_specialized = specialize(mqs, point)
361340 @debug " Computing Groebner basis ($(length(mqs_specialized)) equations)"
362- mqs_ratfuncs = specialize_fracs_to_mqs(mqs, ratfuncs_algebraic , point)
341+ mqs_ratfuncs = specialize_fracs_to_mqs(mqs, ratfuncs , point)
363342 @assert parent(first(mqs_specialized)) == parent(first(mqs_ratfuncs))
364343 @debug " Starting the groebner basis computation"
365344 gb = groebner(mqs_specialized)
366- result_global = map(iszero, normalform(gb, mqs_ratfuncs))
367- return merge_results(algebraicity, result_global)
345+ @debug " Computing GB in $((time_ns() - start_time) / 1e9 ) seconds"
346+ start_time = time_ns()
347+ res = map(iszero, normalform(gb, mqs_ratfuncs))
348+ @debug " Normal forms computed in $((time_ns() - start_time) / 1e9 ) seconds"
349+ return res
350+ end
351+
352+ """
353+ field_contains(field, ratfuncs, prob_threshold)
354+
355+ Checks whether given rational function field `field` contains given rational
356+ functions `ratfuncs`. The result is correct with probability at least `prob_threshold`
357+
358+ Inputs:
359+ - `field` - a rational function field
360+ - `ratfuncs` - a list of rational functions
361+ - `prob_threshold` real number from (0, 1)
362+
363+ Output:
364+ - a list `L[i]` of bools of length `length(rat_funcs)` such that `L[i]` is true iff
365+ the i-th function belongs to `field`
366+ """
367+ @timeit _to function field_contains(
368+ field:: RationalFunctionField{T} ,
369+ ratfuncs:: Vector{Generic.FracFieldElem{T}} ,
370+ prob_threshold,
371+ ) where {T}
372+ if isempty(ratfuncs)
373+ return Bool[]
374+ end
375+
376+ half_p = 1 - (1 - prob_threshold) / 100
377+
378+ start_time = time_ns()
379+ algebraicity = check_algebraicity(field, ratfuncs, half_p)
380+ ratfuncs_algebraic = ratfuncs[algebraicity]
381+ @debug " Algebraicity checked in $((time_ns() - start_time)/ 1e9 ) seconds"
382+ if isempty(ratfuncs_algebraic)
383+ return algebraicity
384+ end
385+
386+ half_p = 1 - (1 - prob_threshold) * 99.0 / 100
387+ return merge_results(
388+ algebraicity,
389+ field_contains_algebraic(field, ratfuncs_algebraic, half_p),
390+ )
368391end
369392
370393function field_contains(
@@ -391,7 +414,11 @@ function issubfield(
391414 E:: RationalFunctionField{T} ,
392415 prob_threshold,
393416) where {T}
394- return all(field_contains(E, F. dennums, prob_threshold))
417+ half_p = 1 - (1 - prob_threshold) / 2
418+ if ! all(check_algebraicity(E, generators(F), half_p))
419+ return false
420+ end
421+ return all(field_contains_algebraic(E, generators(F), prob_threshold))
395422end
396423
397424function fields_equal(
@@ -437,7 +464,7 @@ Applies the following passes:
437464 sort!(fracs_priority, lt = rational_function_cmp)
438465 sort!(fracs_rest, lt = rational_function_cmp)
439466 fracs = vcat(fracs_priority, fracs_rest)
440- @debug " The pool of fractions: \n $(join(map(repr, fracs), " ,\n " )) "
467+ @debug " The pool of fractions of size $(length(fracs)) \n $(join(map(repr, fracs), " ,\n " )) "
441468 if reversed_order
442469 non_redundant = collect(1 : length(fracs))
443470 for i in length(fracs): - 1 : 1
@@ -477,6 +504,7 @@ Applies the following passes:
477504 sort!(fracs, lt = (f, g) -> rational_function_cmp(f, g))
478505 spring_cleaning_pass!(fracs)
479506 _runtime_logger[:id_beautifulization] += (time_ns() - time_start) / 1e9
507+ @debug " Generators beautified in $(_runtime_logger[:id_beautifulization]) seconds"
480508 return fracs
481509end
482510
@@ -560,15 +588,16 @@ end
560588 @debug " Generators up to degrees $(current_degrees) are $fracs "
561589 @debug " Checking two-sided inclusion modulo a prime"
562590 time_start = time_ns()
563- # Check inclusion: <simplified generators> in <original generators>
564591 new_rff = RationalFunctionField(fracs)
565- inclusion = issubfield_mod_p(new_rff, rff)
566- two_sided_inclusion = two_sided_inclusion || inclusion
567- # Check inclusion: <original generators> in <simplified generators>
568- inclusion = issubfield_mod_p(rff, new_rff)
592+ # the ordering of the checks is not arbitrary - the first one can terminate earlier
593+ # via the algebraicity check
594+ if ! issubfield_mod_p(rff, new_rff)
595+ two_sided_inclusion = false
596+ else
597+ two_sided_inclusion = issubfield_mod_p(new_rff, rff)
598+ end
569599 runtime = (time_ns() - time_start) / 1e9
570600 _runtime_logger[:id_inclusion_check_mod_p] += runtime
571- two_sided_inclusion = two_sided_inclusion && inclusion
572601 @debug " Inclusion checked in $(runtime) seconds. Result: $two_sided_inclusion "
573602 current_degrees = current_degrees .* (2 , 2 )
574603 end
@@ -753,8 +782,19 @@ Result is correct (in the Monte-Carlo sense) with probability at least `prob_thr
753782 for (ord, rff_gens) in fan
754783 append!(new_fracs, rff_gens)
755784 end
756- # retaining the original generators
757- append!(new_fracs, generators(rff))
785+
786+ # retaining the original generators (but not all!)
787+ sort!(new_fracs, lt = rational_function_cmp)
788+ original_fracs = generators(rff)
789+ sort!(original_fracs, lt = rational_function_cmp)
790+ merge_index = findfirst(f -> rational_function_cmp(new_fracs[end ], f), original_fracs)
791+ if isnothing(merge_index)
792+ merge_index = length(original_fracs)
793+ else
794+ merge_index -= 1
795+ end
796+ @debug " Retaining $(merge_index) out of $(length(original_fracs)) original generators"
797+ append!(new_fracs, original_fracs[1 : merge_index])
758798 new_fracs_unique = unique(new_fracs)
759799 @debug """
760800Final cleaning and simplification of generators.
@@ -766,8 +806,8 @@ Out of $(length(new_fracs)) fractions $(length(new_fracs_unique)) are syntactica
766806 )
767807 @info " Selecting generators in $((time_ns() - start_time) / 1e9 ) "
768808 @debug " Checking inclusion with probability $prob_threshold "
769- runtime =
770- @elapsed result = issubfield (rff, RationalFunctionField(new_fracs), prob_threshold)
809+ runtime = @elapsed result =
810+ fields_equal (rff, RationalFunctionField(new_fracs), prob_threshold)
771811 _runtime_logger[:id_inclusion_check] = runtime
772812 if ! result
773813 @warn " Field membership check failed. Error will follow."
0 commit comments