|
# EGPTPrimeComposite — T5 Engineering Test Suite (Option B)
`EGPTPrimeComposite` extends `EGPTReal` to carry the full prime provenance of a rational
through multiplication and division — **without ever reducing**. It stores every prime factor
of every multiplication step as an explicit `{ prime, location }` record so that callers can
recover the full signed-prime structure even after operations that would normally cancel.
This matters for the BIPP (Boolean Interpretation Prime-Probability) CNF↔polynomial bijection:
the literal polarity of a SAT variable is recovered from whether its prime lands in the
`numerator` or `denominator` of the product composite — so the records must never be collapsed.
This suite verifies five acceptance properties (T5 Engineering #1, Option B):
| Phase | Property |
|---|---|
| 1 — Construction | `fromPrimeRecords` round-trip preserves the multiset of records exactly. |
| 2 — Factoring | `fromEGPTReal` round-trip for positive/negative integers and reciprocals. |
| 3 — AP4 acceptance | `(Y − 3) × (Y − 1/3)` constant term keeps both `{3 in numerator}` and `{3 in denominator}` records — the AP4 anti-pattern is not re-introduced. |
| 4 — Commutativity | Multiply is commutative at the multiset level; divide flips locations correctly. |
| 5 — Evaluation parity | `evaluateAt` on the no-reduce polynomial agrees with the reduced path. |
| 6 — Dispatch | `EGPTPolynomial.multiply` auto-detects all-composite inputs and routes to `multiplyNoReduce`. |
| 8 — Congruence vs equality | `EGPTMath.equals` is value equality; `EGPTMath.congruent` is structural identity — distinguishes `3×(1/3)` from `1` even though their values agree. |
All computation uses the `math` builtin — no URL imports.
|
## Setup — test harness and shared helpers
A minimal inline harness accumulates pass/fail counts for all phase cells to consume.
`TestFramework` from the original source is not on the SDK surface, so it is inlined here.
|
const { math } = caps;
const {
EGPTReal,
EGPTMath,
EGPTPolynomial,
EGPTPrimeComposite
} = math;
// Shared constructors.
const intN = (n) => EGPTReal.fromBigInt(BigInt(n));
const frac = (n, d) => EGPTReal.fromRational(BigInt(n), BigInt(d));
// Inline test harness (mirrors TestFramework from EGPTTestSuite.js).
// Accumulates across phase cells; the final summary cell reads getResults().
let passed = 0, failed = 0;
const failures = [];
const categoryMap = {}; // category -> { passed, total }
function test(description, category, fn) {
if (!categoryMap[category]) categoryMap[category] = { passed: 0, total: 0 };
categoryMap[category].total++;
try {
const result = fn();
if (result === false) throw new Error('returned false');
passed++;
categoryMap[category].passed++;
console.log(' ok — ' + description);
} catch (err) {
failed++;
failures.push({ description, category, error: err.message });
console.log(' FAIL — ' + description + ' (' + err.message + ')');
}
}
function getResults() {
return { passed, failed, failures, categoryMap };
}
return { suite: { test, getResults, EGPTReal, EGPTMath, EGPTPolynomial, EGPTPrimeComposite, intN, frac } };
|
## Phase 1 — `fromPrimeRecords` round-trip (Construction)
`EGPTPrimeComposite.fromPrimeRecords(records)` takes an explicit list of
`{ sign, prime, location }` records and constructs a composite whose value is the product of
all the primes (positive in the numerator, negative in the denominator). The key guarantee is
that `getSignedPrimes()` returns the same multiset of records back — no normalization, no
reduction.
The canonical multiset key (`recordsMultisetKey`) is a static helper on `EGPTPrimeComposite`
itself; it sorts and serializes the records into a canonical string so that set equality can be
checked with `===`.
|
const { test, getResults, EGPTReal, EGPTPrimeComposite } = inputs.suite;
// Alias for terseness — matches the source's local alias.
const recordsMultisetKey = EGPTPrimeComposite.recordsMultisetKey;
console.log('=== Phase 1 — fromPrimeRecords round-trip ===');
test('fromPrimeRecords: empty records -> sign 1, value 1', 'Construction', () => {
const c = EGPTPrimeComposite.fromPrimeRecords([]);
return c.getSignedPrimes().length === 0 &&
c.getSign() === 1 &&
c.equals(EGPTReal.fromBigInt(1n));
});
test('fromPrimeRecords: [{1,3,num}] -> 3', 'Construction', () => {
const c = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' }
]);
return c.equals(EGPTReal.fromBigInt(3n)) &&
c.getSignedPrimes().length === 1 &&
c.getSignedPrimes()[0].prime === 3n &&
c.getSignedPrimes()[0].location === 'numerator';
});
test('fromPrimeRecords round-trip preserves multiset', 'Construction', () => {
const records = [
{ sign: 1, prime: 2n, location: 'numerator' },
{ sign: 1, prime: 5n, location: 'denominator' },
{ sign: 1, prime: 7n, location: 'numerator' }
];
const c = EGPTPrimeComposite.fromPrimeRecords(records);
const recovered = c.getSignedPrimes().map(r => ({
sign: r.sign, prime: r.prime, location: r.location
}));
return recordsMultisetKey(recovered) === recordsMultisetKey(records);
});
return { p1: getResults() };
|
## Phase 2 — `fromEGPTReal` round-trip (Factoring)
`fromEGPTReal` trial-divides an `EGPTReal`'s un-reduced numerator and denominator into prime
records. The test cases cover the four sign/location combinations and a multi-prime integer:
- Positive integer `3` → one numerator record `{3}`.
- Reciprocal `1/3` → one denominator record `{3}`.
- Negative integer `-3` → sign = −1, one numerator record `{3}`.
- Negative reciprocal `−1/3` → sign = −1, one denominator record `{3}`.
- Composite `12 = 2² × 3` → numerator records `{2:2, 3:1}`.
|
const { test, getResults, EGPTReal, EGPTPrimeComposite } = inputs.suite;
console.log('=== Phase 2 — fromEGPTReal round-trip ===');
test('fromEGPTReal(3n) -> numerator {3:1}', 'Factoring', () => {
const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(3n));
const num = c.getPrimesInNumerator();
const den = c.getPrimesInDenominator();
return c.getSign() === 1 &&
num.size === 1 && num.get(3n) === 1n &&
den.size === 0;
});
test('fromEGPTReal(1/3) -> denominator {3:1}', 'Factoring', () => {
const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(1n, 3n));
const num = c.getPrimesInNumerator();
const den = c.getPrimesInDenominator();
return c.getSign() === 1 &&
num.size === 0 &&
den.size === 1 && den.get(3n) === 1n;
});
test('fromEGPTReal(-3n) -> sign -1, numerator {3:1}', 'Factoring', () => {
const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n));
const num = c.getPrimesInNumerator();
return c.getSign() === -1 &&
num.size === 1 && num.get(3n) === 1n &&
c.equals(EGPTReal.fromBigInt(-3n));
});
test('fromEGPTReal(-1/3) -> sign -1, denominator {3:1}', 'Factoring', () => {
const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n));
const den = c.getPrimesInDenominator();
return c.getSign() === -1 &&
den.size === 1 && den.get(3n) === 1n &&
c.equals(EGPTReal.fromRational(-1n, 3n));
});
test('fromEGPTReal(12n) -> {2:2, 3:1}', 'Factoring', () => {
const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(12n));
const num = c.getPrimesInNumerator();
return num.get(2n) === 2n && num.get(3n) === 1n && num.size === 2;
});
return { p2: getResults() };
|
## Phase 3 — AP4 acceptance: constant term preserves both `{3:num}` and `{3:den}`
The **AP4 anti-pattern** is the bug where multiplying `(Y − 3) × (Y − 1/3)` reduces the
constant term `(−3) × (−1/3) = 1` immediately, discarding the information that prime `3`
appeared in both the numerator (from the factor `−3`) and the denominator (from `−1/3`).
For the BIPP bijection this is fatal: the SAT witness recovery step reads back which primes
appeared in which location to determine literal polarity. A reduced `1` carries no polarity
information.
`EGPTPolynomial.multiplyNoReduce` multiplies two `EGPTPrimeComposite[]` coefficient arrays
without ever reducing the coefficients — the constant term of the product must carry both
`{prime: 3, location: numerator}` AND `{prime: 3, location: denominator}` as distinct records,
even though the value is `1`. The sibling test for primes `5` confirms the pattern generalizes.
|
const { test, getResults, EGPTReal, EGPTPolynomial, EGPTPrimeComposite } = inputs.suite;
console.log('=== Phase 3 — AP4 acceptance ===');
test('AP4: (Y - 3) * (Y - 1/3) constant term keeps {3 in num, 3 in den}', 'AP4 Acceptance', () => {
const negThree = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n));
const negOneThird = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n));
const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
const polyA = [negThree, onePos]; // (Y - 3)
const polyB = [negOneThird, onePos]; // (Y - 1/3)
const product = EGPTPolynomial.multiplyNoReduce(polyA, polyB);
const constTerm = product[0];
if (!(constTerm instanceof EGPTPrimeComposite)) {
throw new Error('Constant term is not EGPTPrimeComposite.');
}
const records = constTerm.getSignedPrimes();
const has3InNum = records.some(r => r.prime === 3n && r.location === 'numerator');
const has3InDen = records.some(r => r.prime === 3n && r.location === 'denominator');
if (!has3InNum) throw new Error('Missing record {prime: 3, location: numerator}.');
if (!has3InDen) throw new Error('Missing record {prime: 3, location: denominator}.');
if (!constTerm.equals(EGPTReal.fromBigInt(1n))) {
throw new Error('Constant term value should equal 1.');
}
return true;
});
test('AP4 sibling: (Y - 5) * (Y - 1/5) constant term keeps {5 in num, 5 in den}', 'AP4 Acceptance', () => {
const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
const polyA = [
EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-5n)),
onePos
];
const polyB = [
EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 5n)),
onePos
];
const product = EGPTPolynomial.multiplyNoReduce(polyA, polyB);
const records = product[0].getSignedPrimes();
const has5InNum = records.some(r => r.prime === 5n && r.location === 'numerator');
const has5InDen = records.some(r => r.prime === 5n && r.location === 'denominator');
return has5InNum && has5InDen && product[0].equals(EGPTReal.fromBigInt(1n));
});
return { p3: getResults() };
|
## Phase 4 — Commutativity and division location-flip
Multiplication must be commutative at the **multiset** level: `ab` and `ba` must have the same
multiset of records and the same sign. The test uses `recordsMultisetKey` to compare the
sorted canonical string representations.
Division is multiplication by the reciprocal: the divisor's records have their `location` field
flipped (`numerator ↔ denominator`) before concatenation. So `3 ÷ 3` should produce a composite
whose records include both `{prime: 3, location: numerator}` (from the dividend) and
`{prime: 3, location: denominator}` (from the flipped divisor), while the value is exactly `1`.
|
const { test, getResults, EGPTReal, EGPTPrimeComposite } = inputs.suite;
const recordsMultisetKey = EGPTPrimeComposite.recordsMultisetKey;
console.log('=== Phase 4 — Commutativity and division location-flip ===');
test('multiply commutativity (record multiset equality)', 'Commutativity', () => {
const a = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 2n, location: 'numerator' },
{ sign: 1, prime: 7n, location: 'denominator' }
]);
const b = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' },
{ sign: 1, prime: 11n, location: 'numerator' }
]);
const ab = a.multiply(b);
const ba = b.multiply(a);
return recordsMultisetKey(ab.getSignedPrimes()) === recordsMultisetKey(ba.getSignedPrimes()) &&
ab.getSign() === ba.getSign();
});
test('multiply with sign: (-3) * (-1/3) records contain both {3:num} and {3:den}', 'Commutativity', () => {
const a = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n));
const b = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n));
const ab = a.multiply(b);
const ba = b.multiply(a);
return recordsMultisetKey(ab.getSignedPrimes()) === recordsMultisetKey(ba.getSignedPrimes()) &&
ab.getSign() === 1 && ba.getSign() === 1;
});
test('divide flips locations: (3/1) / (3/1) -> {3:num, 3:den}', 'Division', () => {
const a = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(3n));
const b = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(3n));
const q = a.divide(b);
const records = q.getSignedPrimes();
const has3InNum = records.some(r => r.prime === 3n && r.location === 'numerator');
const has3InDen = records.some(r => r.prime === 3n && r.location === 'denominator');
return has3InNum && has3InDen && q.equals(EGPTReal.fromBigInt(1n));
});
return { p4: getResults() };
|
## Phase 5 — `evaluateAt` parity with the reduced path
The AP4 constant-term check (Phase 3) confirms that the no-reduce product *structurally*
preserves prime records. This phase checks that it is also *numerically* correct: evaluating
the no-reduce polynomial at any point `x` must agree with evaluating the fully-reduced
polynomial at the same point.
The test builds `(Y − 3)(Y − 1/3)` two ways:
- **Reduced:** `EGPTPolynomial.multiply` on plain `EGPTReal` coefficients — the standard path
that combines terms via `EGPTMath.add`.
- **No-reduce:** `EGPTPolynomial.multiplyNoReduce` on `EGPTPrimeComposite` coefficients — the
provenance-preserving path.
Both polynomials are evaluated at `x = 7`. They must return the same value.
A second test verifies a concrete numeric result: `(Y − 2)(Y − 5)` at `x = 7` should equal
`(7 − 2)(7 − 5) = 10`.
|
const { test, getResults, EGPTReal, EGPTPolynomial, EGPTPrimeComposite } = inputs.suite;
console.log('=== Phase 5 — evaluateAt parity ===');
test('evaluateAt(reduced poly, x) === evaluateAt(no-reduce poly, x) on a monomial expansion', 'Evaluation', () => {
const negThree_en = EGPTReal.fromBigInt(-3n);
const negOneThird_en = EGPTReal.fromRational(-1n, 3n);
const one_en = EGPTReal.fromBigInt(1n);
const reducedPoly = EGPTPolynomial.multiply(
[negThree_en, one_en],
[negOneThird_en, one_en]
);
const noReducePoly = EGPTPolynomial.multiplyNoReduce(
[
EGPTPrimeComposite.fromEGPTReal(negThree_en),
EGPTPrimeComposite.fromEGPTReal(one_en)
],
[
EGPTPrimeComposite.fromEGPTReal(negOneThird_en),
EGPTPrimeComposite.fromEGPTReal(one_en)
]
);
const x = EGPTReal.fromBigInt(7n);
const reducedValue = EGPTPolynomial.evaluateAt(reducedPoly, x);
const noReduceValue = EGPTPolynomial.evaluateAt(noReducePoly, x);
return reducedValue.equals(noReduceValue);
});
test('evaluateAt: (Y - 2)(Y - 5) at x=7 equals (7-2)(7-5) = 10 (no-reduce)', 'Evaluation', () => {
const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
const polyA = [
EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-2n)),
onePos
];
const polyB = [
EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-5n)),
onePos
];
const product = EGPTPolynomial.multiplyNoReduce(polyA, polyB);
const x = EGPTReal.fromBigInt(7n);
const v = EGPTPolynomial.evaluateAt(product, x);
return v.equals(EGPTReal.fromBigInt(10n));
});
return { p5: getResults() };
|
## Phase 6 — Auto-dispatch via `EGPTPolynomial.multiply`
`EGPTPolynomial.multiply` detects whether all coefficients are `EGPTPrimeComposite` instances
and, if so, routes to `multiplyNoReduce` automatically. This means callers can use the single
`multiply` entry point — the dispatch is transparent.
A regression guard confirms that plain `EGPTReal` inputs still take the reducing path:
`[1, 2] × [3, 4] = [3, 10, 8]` (the canonical polynomial multiplication check).
|
const { test, getResults, EGPTReal, EGPTPolynomial, EGPTPrimeComposite } = inputs.suite;
console.log('=== Phase 6 — auto-dispatch via EGPTPolynomial.multiply ===');
test('EGPTPolynomial.multiply auto-dispatches to no-reduce when all coeffs are composites', 'Dispatch', () => {
const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
const polyA = [
EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n)),
onePos
];
const polyB = [
EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n)),
onePos
];
const product = EGPTPolynomial.multiply(polyA, polyB);
if (!(product[0] instanceof EGPTPrimeComposite)) {
throw new Error('Expected EGPTPrimeComposite constant term via auto-dispatch.');
}
const records = product[0].getSignedPrimes();
const has3InNum = records.some(r => r.prime === 3n && r.location === 'numerator');
const has3InDen = records.some(r => r.prime === 3n && r.location === 'denominator');
return has3InNum && has3InDen;
});
test('EGPTPolynomial.multiply on plain EGPTReal coefficients still reduces (regression guard)', 'Dispatch', () => {
// [1, 2] * [3, 4] = [3, 10, 8]
const poly1 = [EGPTReal.fromBigInt(1n), EGPTReal.fromBigInt(2n)];
const poly2 = [EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(4n)];
const result = EGPTPolynomial.multiply(poly1, poly2);
const expected = [EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(10n), EGPTReal.fromBigInt(8n)];
return EGPTPolynomial.equals(result, expected);
});
return { p6: getResults() };
|
## Phase 8 — Congruence vs value equality
`EGPTMath.equals` asks whether two values are *numerically* equal — `3 × (1/3)` and `1` both
have value `1`, so `equals` is `true`. `EGPTMath.congruent` asks whether two values have the
*same canonical lift* — the same multiset of prime records. A composite built from
`{3:numerator, 3:denominator}` records has a different lift than a plain `EGPTReal(1)` (which
has no records), so `congruent` is `false` even though `equals` is `true`.
This distinction is first-class in `EGPTMath` (not only on `EGPTPrimeComposite`) as of T5.
The `congruentTo` instance method on `EGPTPrimeComposite` is the per-instance form of the same
predicate.
Key rules:
- `equals` is always value equality (the reduced rational comparison).
- `congruent(a, b)` where both are plain `EGPTReal` degenerates to `equals` (both have the
trivial lift).
- `congruent` is sign-sensitive: `{+3:num}` and `{-3:num}` are not congruent.
- `congruent(a, a)` is always `true` (self-congruence).
|
const { test, getResults, EGPTReal, EGPTMath, EGPTPrimeComposite } = inputs.suite;
console.log('=== Phase 8 — congruent vs equals ===');
test('equals is true for 3 * (1/3) and 1 (value equality)', 'Congruence', () => {
const lhs = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' },
{ sign: 1, prime: 3n, location: 'denominator' }
]);
const rhs = EGPTReal.fromBigInt(1n);
return EGPTMath.equals(lhs, rhs);
});
test('congruent is FALSE for 3 * (1/3) and 1 (different lifts)', 'Congruence', () => {
const lhs = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' },
{ sign: 1, prime: 3n, location: 'denominator' }
]);
const rhs = EGPTReal.fromBigInt(1n);
return !EGPTMath.congruent(lhs, rhs);
});
test('congruent is true for two equally-built composites (order-invariant)', 'Congruence', () => {
const a = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' },
{ sign: 1, prime: 3n, location: 'denominator' }
]);
const b = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'denominator' },
{ sign: 1, prime: 3n, location: 'numerator' } // order swapped
]);
return EGPTMath.congruent(a, b);
});
test('congruent is false when sign differs even if records match', 'Congruence', () => {
const a = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' }
]);
const b = EGPTPrimeComposite.fromPrimeRecords([
{ sign: -1, prime: 3n, location: 'numerator' }
]);
return !EGPTMath.congruent(a, b);
});
test('congruent collapses to equals on plain EGPTReal pair', 'Congruence', () => {
const a = EGPTReal.fromBigInt(3n);
const b = EGPTReal.fromBigInt(3n);
const c = EGPTReal.fromBigInt(4n);
return EGPTMath.congruent(a, b) && !EGPTMath.congruent(a, c);
});
test('congruentTo instance method matches EGPTMath.congruent', 'Congruence', () => {
const composite = EGPTPrimeComposite.fromPrimeRecords([
{ sign: 1, prime: 3n, location: 'numerator' },
{ sign: 1, prime: 3n, location: 'denominator' }
]);
const plain = EGPTReal.fromBigInt(1n);
return composite.congruentTo(composite) // self-congruent
&& !composite.congruentTo(plain) // mixed lifts differ
&& composite.equals(plain); // values agree
});
return { p8: getResults() };
|
## Summary — total pass / fail across all phases
|
// p8 holds the cumulative getResults() snapshot (all phases accumulate into the shared harness).
const { passed, failed, failures, categoryMap } = inputs.p8;
const total = passed + failed;
console.log('');
console.log('='.repeat(60));
console.log('TEST SUMMARY');
console.log('='.repeat(60));
for (const [cat, { passed: cp, total: ct }] of Object.entries(categoryMap)) {
console.log(` ${cat}: ${cp}/${ct} passed`);
}
console.log('-'.repeat(60));
console.log(`TOTAL: ${passed}/${total} tests passed`);
console.log(`SUCCESS RATE: ${((passed / total) * 100).toFixed(1)}%`);
if (failures.length > 0) {
console.log('');
console.log('FAILED TESTS:');
failures.forEach(f => {
console.log(` ${f.category}: ${f.description}`);
if (f.error) console.log(` Error: ${f.error}`);
});
}
console.log('='.repeat(60));
if (failed > 0) throw new Error('[EGPTPrimeCompositeTest] ' + failed + ' test(s) failed');
|