Skip to content

PowerOperator::Degree(VariableGroup) sums per-variable degrees instead of the total degree — inflates DegreeBound(), which feeds AMP #397

Description

@ofloveandhate

The bug

PowerOperator::Degree(VariableGroup const&) computes the total degree by summing the
per-variable degrees
:

auto multideg = MultiDegree(vars);
auto deg = 0;
std::for_each(multideg.begin(), multideg.end(), [&](int n){
        if (n < 0) deg = -1; else deg += n; });
return deg;

Summing is valid only for a monomial. For (x+y)^2 the degree in x is 2 and the
degree in y is 2, but the total degree is 2, not 4. The overcount scales with the
number of variables appearing in the base:

expression true degree reported
x^4 4 4
(x+y)^2 2 4
(x^2+y^2)^2 4 8
(x^2+y^2+z^2+3)^2 4 12

Why it stayed hidden

The same expression takes two different paths. An integer exponent written in C++ or Python
builds an IntegerPowerOperator, whose Degree(VariableGroup) is
base_deg * exponent and has always been right. The classic-input parser builds the
generic PowerOperator, which has this bug.

Both print identically, and both give the correct ungrouped Degrees(). Only
Degrees(Variables()) differs — and that is exactly what System::DegreeBound() calls.

S = System with f0 = (x+y)^2 built via Python's **
P = bertini.parse.system("variable_group x, y;\nfunction f0;\nf0 = (x+y)^2;\n")

str(S) == str(P)                  # both print  f_0 = (x+y)^2
list(S.degrees()) == list(P.degrees())     # both [2]
list(S.degrees(vg)), list(P.degrees(vg))   # [2] vs [4]     <-- here

Why it matters

DegreeBound() feeds AMP. So a system round-tripped through classic input tracks with a
different degree bound than the one it was built from — silently, with no error, in the
direction of demanding more precision than necessary. Any records/replay layer that stores
systems as classic-input text therefore replays them under different adaptive-precision
decisions than the original run, which undermines reproducibility.

Fix

Give Degree(VariableGroup) the same shape as PowerOperator::Degree(std::shared_ptr<Variable>)
directly above it, which is already correct: analyse the exponent, and use the base's
group degree in place of its degree in one variable —
return base_deg * exponent for a non-negative integer exponent, -1 for a variable or
negative or non-integer exponent, 0 when the base is constant with respect to the group.

Fixed on fix/classic-input-roundtrip alongside #395 and #396, which is how it was found:
the classic-input round trip produced degreebound: 4 -> degreebound: 12 for
(x^2+y^2+z^2+3)^2-16*(x^2+y^2).

Version: 3.5.0.dev0 (develop @ 8f0cd0f).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions