Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions docs/research/power-polynomial-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Compare a power invoice over the whole exponent interval

An inconclusive concave-order result does not prove that a power cost changes
sign. When all changed ranks are powers of one integer, this tool gives an exact
answer for **every** exponent `0 < t < 1`:

```sh
python3 -B scripts/compare_power_polynomial.py OLD.json NEW.json
```

The inputs use the [recursive invoice schema](recursive-invoice-preflight.md).
Their contracts must match. The tool first cancels equal rank terms in
`(new paid-new targets)-(old paid-old targets)`. It then finds an integer base
`b > 1` such that each remaining rank is `b^j`. Rank one has exponent zero. A
constant or zero difference uses base two by convention. Unsupported ranks fail
with exit code2; the tool does not round them to a common base.

The report binds the exact input bytes by SHA256. It gives the signed polynomial
coefficients in ascending order, endpoint multiplicities, exact root counts and
a rational sample. All arithmetic uses integers and fractions. No grid sampling,
floating-point roots or precision choice determines the result.

| Classification | Meaning for the new cost minus the old cost |
|---|---|
| `uniformly_lower` | Strictly negative for every `0 < t < 1`. |
| `uniformly_higher` | Strictly positive for every `0 < t < 1`. |
| `nonpositive_with_equality` | Never positive, with at least one interior zero. |
| `nonnegative_with_equality` | Never negative, with at least one interior zero. |
| `crossing` | Both signs occur in the open exponent interval. |
| `equal` | The signed rank difference is zero. |

For example, old paid jobs `{"2":2}` and new jobs `{"1":1,"4":1}` give
`P(x)=(x-1)^2`. General concave order is inconclusive, but the new power cost is
strictly higher for every `0 < t < 1`. By contrast, old jobs `{"2":12}` and new
jobs `{"1":9,"4":4}` give `(2x-3)^2`: the result has one interior equality and
is not strict throughout the interval. Old jobs `{"1":3}` and new jobs `{"2":2}`
give `2x-3`, so the cost crosses zero. These are declared arithmetic examples,
not new legal circuit replacements.

## Exact reduction and proof

Let `d_r` be the signed difference after paid and target terms are combined.
Write each remaining rank as `r=b^j`, and set

`P(x) = sum_j d_(b^j) x^j`.

The map `x=b^t` is a continuous, strictly increasing bijection from `(0,1)` to
`(1,b)`. Thus `sum_r d_r r^t = P(b^t)`, with the same signs and zeros. Factoring
the first nonunit rank and dividing its prime exponents by their greatest common
divisor gives its primitive integer base. Repeated exact division checks every
other rank. If a common integer base exists, that primitive base also works.

The zero polynomial gives `equal`. For a nonzero polynomial, first divide out
every factor `x-1` and `x-b`. These zeros lie outside the open domain. Record
their multiplicities; do not count them as interior equalities. All later GCDs
divide this reduced polynomial, so neither endpoint can become a root again.

For any remaining nonconstant polynomial `G`, compute `D=gcd(G,G')` and the
square-free quotient `S=G/D`. Its roots are the distinct roots of `G`. Form the
Sturm sequence `S,S',-rem(S,S'),...`, stopping at a nonzero constant. At an
endpoint, omit zero values before counting adjacent sign changes. The difference
between the variation counts at1 and b is the number of distinct interior roots.
All divisions are exact operations in Q. Clearing denominators and dividing by
positive content preserve the signs needed by Sturm's theorem.

To obtain multiplicity parity, repeat with `G <- gcd(G,G')`. Let `n_j` be the
distinct interior root count at step j, starting with j=0. A root of multiplicity
m appears in exactly the first m counts. Hence its contribution to

`n_0 - n_1 + n_2 - n_3 + ...`

is one if m is odd and zero if m is even. This sum counts odd-multiplicity
interior roots. Each such root changes the sign of a real polynomial. Its
presence proves `crossing`. If none exists, the sign is constant away from the
interior roots. One nonzero rational sample determines that sign. A polynomial
of degree d cannot vanish at d+1 distinct sample points, so the bounded sample
selection must succeed. With no interior roots the order is strict. With only
even-multiplicity interior roots the order is weak, with equality at those roots.

This uses classical Sturm theory, not a new root-count theorem. See Philippe
Pebay, J. Maurice Rojas and David C. Thompson,
[Sturm's Theorem with Endpoints](https://arxiv.org/abs/2208.07904). This tool
removes endpoint factors before the count to make its open-domain convention
explicit. The small polynomial implementation uses standard-library fractions;
the shared invoice tools and CI do not require a computer-algebra installation.

## Verification and limits

The independent low-degree oracle checks all343 quadratics with coefficients in
`{-3,...,3}` by their exact extrema and monotone intervals. A second oracle checks
288 products with known endpoint, repeated interior and nonreal roots. Further
checks cover irrational interior roots, degree29, target credits, cancellation,
large coefficients, unsupported ranks and the isolated optimized CLI.
An optional independent check uses SymPy1.14.0 root isolation on64 further
polynomials, including24 dense degree29 cases with coefficients near the input
limit. That check is skipped when SymPy is not installed; the other tests and
the tool need only the standard library.

```sh
python3 -B -m unittest discover -s tests -p 'test_research_power_polynomial.py' -v
```

The invoice rank limit10^9 bounds the polynomial degree by29. The existing count,
input-size and contract limits still apply. Only uncancelled ranks need a common
base. This can help reject or price a proposed rank exchange before a full
construction replay. It does not authenticate a circuit, detect an omitted
source charge, establish ownership or native legality, or admit a compiler or
kappa claim. Even `uniformly_lower` concerns only the declared common tariff.

The [pointwise power comparator PR5732](https://github.com/CrocSwap/integer-mult-bounds/pull/5732)
handles arbitrary supported ranks at one rational exponent. The
[concave witnesses PR5731](https://github.com/CrocSwap/integer-mult-bounds/pull/5731)
explain the broader tariff screen. Those tools are complementary; this tool
does not depend on their unmerged code. The source invoice interface retains
zxv's comparator, mightyOil's T16 audit and all credited predecessors.

## Open next questions

- Can an actual proposed exchange with an inconclusive concave screen use this
certificate? Supply the complete changed paid and target terms and the same
consumer contract before applying the tool.
- Can noncommensurate rank differences receive a uniform sign certificate?
This polynomial reduction does not answer that question. A finite exponent
sample must not be treated as a proof over an interval.

New reduction, implementation and checks: Ali Romman with substantial OpenAI
Codex assistance. Apache-2.0. No independent human or proof-kernel review is claimed.
5 changes: 5 additions & 0 deletions docs/research/recursive-invoice-preflight.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,8 @@ fail with a diagnostic and exit code 2. Valid comparisons, including inconclusiv
ones, exit 0: this is an arithmetic report, not an automatic acceptance gate.

Prepared for zxv with substantial OpenAI Codex assistance. Apache-2.0.

For a difference whose uncancelled ranks are powers of one integer, the
[polynomial comparison](power-polynomial-comparison.md) classifies the power
cost on the whole open interval `0 < t < 1`. It distinguishes strict order,
interior equality and actual crossings without a numerical exponent grid.
192 changes: 192 additions & 0 deletions scripts/compare_power_polynomial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Classify a common-base invoice difference for every power 0 < t < 1.

Use classical Euclid and Sturm arithmetic over Q. No numerical roots are used.
The invoice interface retains zxv's comparator and predecessor credits.
Ali Romman with substantial OpenAI Codex assistance. See the linked proof guide.
"""

import argparse
from fractions import Fraction as Q
from functools import reduce
import json
from math import gcd, lcm
from pathlib import Path
import runpy
import sys

INVOICES = runpy.run_path(str(Path(__file__).with_name("compare_recursive_invoices.py")))
require = INVOICES["require"]


def trim(poly):
values = list(poly)
while values and not values[-1]:
values.pop()
return tuple(values)


def primitive(poly):
"""Remove positive rational content; retain the sign at every real point."""
poly = trim(poly)
if not poly:
return ()
denominator = lcm(*(Q(c).denominator for c in poly))
integers = [int(c * denominator) for c in poly]
content = gcd(*integers)
return tuple(c // content for c in integers)


def divide(a, b):
a, b = list(map(Q, trim(a))), trim(b)
require(bool(b), "Polynomial division by zero")
quotient = [Q(0)] * max(0, len(a) - len(b) + 1)
while a and len(a) >= len(b):
offset, factor = len(a) - len(b), a[-1] / b[-1]
quotient[offset] = factor
for i, coefficient in enumerate(b):
a[offset + i] -= factor * coefficient
a = list(trim(a))
return trim(quotient), trim(a)


def derivative(poly):
return trim(i * c for i, c in enumerate(poly) if i)


def value(poly, x):
result = Q(0)
for coefficient in reversed(poly):
result = result * x + coefficient
return result


def poly_gcd(a, b):
a, b = primitive(a), primitive(b)
while b:
a, b = b, primitive(divide(a, b)[1])
return a


def variations(sequence, x):
signs = [1 if v > 0 else -1 for p in sequence if (v := value(p, x))]
return sum(a != b for a, b in zip(signs, signs[1:]))


def distinct_roots(poly, left, right):
"""Count roots after square-free reduction; endpoints must not be roots."""
common = poly_gcd(poly, derivative(poly))
square_free, remainder = divide(poly, common)
require(not remainder, "Inexact square-free quotient")
square_free = primitive(square_free)
require(value(square_free, left) and value(square_free, right), "Root at Sturm endpoint")
sequence = [square_free]
if len(square_free) > 1:
sequence.append(derivative(square_free))
while len(sequence[-1]) > 1:
remainder = divide(sequence[-2], sequence[-1])[1]
require(bool(remainder), "Unexpected repeated root after square-free reduction")
sequence.append(primitive(-c for c in remainder))
a, b = variations(sequence, left), variations(sequence, right)
return {"square_free": list(square_free), "left_variations": a,
"right_variations": b, "distinct_roots": a - b}


def common_base(ranks):
"""Find the primitive integer base of the first rank, then check all ranks."""
number = next((r for r in ranks if r > 1), 2)
factors, divisor = {}, 2
while divisor * divisor <= number:
while number % divisor == 0:
factors[divisor] = factors.get(divisor, 0) + 1
number //= divisor
divisor = 3 if divisor == 2 else divisor + 2
if number > 1:
factors[number] = 1
divisor = gcd(*factors.values())
base = reduce(lambda a, item: a * item[0] ** (item[1] // divisor), factors.items(), 1)
exponents = {}
for rank in ranks:
remaining, power = rank, 0
while remaining > 1 and remaining % base == 0:
remaining //= base
power += 1
require(remaining == 1, "Uncancelled ranks are not powers of one integer base")
exponents[rank] = power
return base, exponents


def classify_polynomial(poly, base):
"""Keep endpoint zeros separate from the open-interval order."""
original = trim(poly)
if not original:
return {"classification": "equal", "interior_roots": 0, "odd_interior_roots": 0,
"endpoint_multiplicities": {"1": 0, str(base): 0}, "root_counts": []}
current = primitive(original)
endpoints = {}
for endpoint in (1, base):
multiplicity = 0
while not value(current, endpoint):
current, remainder = divide(current, (-endpoint, 1))
require(not remainder, "Inexact endpoint quotient")
current = primitive(current)
multiplicity += 1
endpoints[str(endpoint)] = multiplicity
counts = []
while len(current) > 1:
counts.append(distinct_roots(current, 1, base))
current = poly_gcd(current, derivative(current))
roots = counts[0]["distinct_roots"] if counts else 0
odd = sum((-1) ** j * row["distinct_roots"] for j, row in enumerate(counts))
require(0 <= odd <= roots, "Invalid root multiplicity count")
# A degree-d nonzero polynomial cannot vanish at d+1 distinct sample points.
for denominator in range(2, len(original) + 2):
sample = 1 + Q(base - 1, denominator)
sample_value = value(original, sample)
if sample_value:
break
require(bool(sample_value), "No nonzero polynomial sample")
classification = ("crossing" if odd else
"nonnegative_with_equality" if roots and sample_value > 0 else
"nonpositive_with_equality" if roots else
"uniformly_higher" if sample_value > 0 else "uniformly_lower")
return {"classification": classification, "interior_roots": roots,
"odd_interior_roots": odd, "endpoint_multiplicities": endpoints,
"root_counts": counts, "sample": {"x": str(sample), "value": str(sample_value)}}


def compare(old, new):
invoice = INVOICES["compare_invoices"](old, new)
delta = {int(r): n for r, n in invoice["signed_net_delta"].items()}
base, exponents = common_base(delta)
poly = [0] * (max(exponents.values(), default=0) + 1)
for rank, count in delta.items():
poly[exponents[rank]] = count
require(len(poly) <= 30, "Polynomial degree exceeds the invoice rank limit")
return {"schema": "integer-mult-power-polynomial/v1", "contract": invoice["contract"],
"quantity": invoice["quantity"], "signed_net_delta": invoice["signed_net_delta"],
"base": base, "polynomial_ascending": poly, "exponent_domain": "0 < t < 1",
"substitution": "x = base^t; 1 < x < base", **classify_polynomial(poly, base),
"scope": "Declared invoice arithmetic only. No circuit, ownership, compiler or kappa admission."}


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("old")
parser.add_argument("new")
args = parser.parse_args()
try:
old, old_pin = INVOICES["load_invoice"](args.old)
new, new_pin = INVOICES["load_invoice"](args.new)
report = compare(old, new)
report["inputs"] = {"old": old_pin, "new": new_pin}
print(json.dumps(report, indent=2, sort_keys=True))
except (OSError, ValueError, TypeError, RecursionError) as error:
print("Polynomial comparison failed: " + str(error), file=sys.stderr)
return 2
return 0


if __name__ == "__main__":
sys.exit(main())
Loading