Skip to content

Commit 4bb8acc

Browse files
authored
Merge pull request #440 from SciML/field_equality_early_termination
Minor changes in simplification
2 parents 8296cb3 + 39d645a commit 4bb8acc

File tree

1 file changed

+109
-69
lines changed

1 file changed

+109
-69
lines changed

src/RationalFunctionFields/RationalFunctionField.jl

Lines changed: 109 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,22 @@ end
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+
)
265270
end
266271

267272
function 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))
281289
end
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+
)
368391
end
369392

370393
function 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))
395422
end
396423

397424
function 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
481509
end
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 """
760800
Final 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

Comments
 (0)