harb/tools/push3-evolution/seed-generator.ts
openhands 850131b74f fix: feat: Push3 evolution — diverse seed population (#638)
Add seed-generator.ts module and seed-gen-cli.ts CLI that produce
parametric Push3 variants for initial population seeding.

Variants systematically cover:
  - Staked% thresholds: 80, 85, 88, 91, 94, 97
  - Penalty thresholds: 30, 50, 70, 100
  - Bull params: 4 presets (aggressive → mild)
  - Bear params: 4 presets (standard → very mild)
  - Tax distributions: exponential (seed), linear, sqrt

Total combination space: 6×4×4×4×3 = 1152 variants.
selectVariants(n) samples evenly so every axis is represented.

evolve.sh gains --diverse-seeds flag: when set, gen_0 is seeded with
parametric variants instead of N copies of the same mutated seed.
Remaining slots (if population > generated variants) fall back to
mutations of the base seed.

All generated programs pass transpiler stack validation (33 new tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 04:48:04 +00:00

296 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* seed-generator.ts — Generate diverse seed variants for Push3 evolution (#638).
*
* Produces parametric Push3 programs that implement the same binary bear/bull
* logic as optimizer_seed.push3, with systematically varied constants:
* - stakedThreshold: the staked% cutoff for entering the penalty branch
* - penaltyThreshold: the penalty value below which bull params are used
* - bull/bear params: capitalInefficiency, anchorShare, anchorWidth, discoveryDepth
* - taxDistribution: spacing of the 30-bucket tax-rate lookup thresholds
*
* All generated programs:
* 1. Consume 8 inputs (same DYADIC stack interface as the seed)
* 2. Produce 4 outputs in valid ranges [0..1e18] for fractions, positive int for anchorWidth
* 3. Have identical structure to optimizer_seed.push3 (only constants differ)
*/
const ONE_E18 = 1_000_000_000_000_000_000n;
export interface BullBearParams {
capitalInefficiency: bigint; // 0..1e18
anchorShare: bigint; // 0..1e18
anchorWidth: number; // tick units (positive integer)
discoveryDepth: bigint; // 0..1e18
}
export type TaxDistribution = 'exponential' | 'linear' | 'sqrt';
export interface SeedVariantParams {
stakedThreshold: number; // integer 0-100: staked% above which penalty branch is entered
penaltyThreshold: number; // penalty < N → bull output; else → bear output
bull: BullBearParams;
bear: BullBearParams;
taxDistribution: TaxDistribution;
}
// ---- Parameter sets --------------------------------------------------------
/** Staked% thresholds: when staked > threshold, enter penalty computation. */
export const STAKED_THRESHOLDS: readonly number[] = [80, 85, 88, 91, 94, 97];
/** Penalty thresholds: penalty < N → bull; else → bear. */
export const PENALTY_THRESHOLDS: readonly number[] = [30, 50, 70, 100];
/** Bull output parameter variants (higher confidence / tighter ranges). */
export const BULL_VARIANTS: readonly BullBearParams[] = [
// Aggressive bull (current seed): maximum confidence, tight anchor
{ capitalInefficiency: 0n, anchorShare: ONE_E18, anchorWidth: 20, discoveryDepth: ONE_E18 },
// Tight bull: slightly lower confidence, narrower anchor
{ capitalInefficiency: 0n, anchorShare: 900_000_000_000_000_000n, anchorWidth: 15, discoveryDepth: 900_000_000_000_000_000n },
// Moderate bull: wider anchor, moderate confidence
{ capitalInefficiency: 0n, anchorShare: 800_000_000_000_000_000n, anchorWidth: 30, discoveryDepth: 800_000_000_000_000_000n },
// Mild bull: broad discovery, lower confidence
{ capitalInefficiency: 0n, anchorShare: 700_000_000_000_000_000n, anchorWidth: 50, discoveryDepth: 700_000_000_000_000_000n },
];
/** Bear output parameter variants (defensive / wide ranges). */
export const BEAR_VARIANTS: readonly BullBearParams[] = [
// Standard bear (current seed)
{ capitalInefficiency: 0n, anchorShare: 300_000_000_000_000_000n, anchorWidth: 100, discoveryDepth: 300_000_000_000_000_000n },
// Strong bear: very defensive, very wide anchor
{ capitalInefficiency: 0n, anchorShare: 200_000_000_000_000_000n, anchorWidth: 150, discoveryDepth: 200_000_000_000_000_000n },
// Mild bear: slightly wider than neutral
{ capitalInefficiency: 0n, anchorShare: 400_000_000_000_000_000n, anchorWidth: 80, discoveryDepth: 400_000_000_000_000_000n },
// Very mild bear: approaching neutral
{ capitalInefficiency: 0n, anchorShare: 500_000_000_000_000_000n, anchorWidth: 60, discoveryDepth: 500_000_000_000_000_000n },
];
export const TAX_DISTRIBUTIONS: readonly TaxDistribution[] = [
'exponential',
'linear',
'sqrt',
];
// ---- Tax threshold distributions -------------------------------------------
/**
* Current seed thresholds — exponentially spaced from ~2e14 to ~8.9e17.
* These are the exact values from optimizer_seed.push3.
*/
function exponentialThresholds(): bigint[] {
return [
206185567010309n,
412371134020618n,
618556701030927n,
1030927835051546n,
1546391752577319n,
2164948453608247n,
2783505154639175n,
3608247422680412n,
4639175257731958n,
5670103092783505n,
7216494845360824n,
9278350515463917n,
11855670103092783n,
15979381443298969n,
22164948453608247n,
29381443298969072n,
38144329896907216n,
49484536082474226n,
63917525773195876n,
83505154639175257n,
109278350515463917n,
144329896907216494n,
185567010309278350n,
237113402061855670n,
309278350515463917n,
402061855670103092n,
520618556701030927n,
680412371134020618n,
886597938144329896n,
];
}
/**
* Linear thresholds — 29 boundaries evenly spaced across [0, 1e18].
* Gives equal bucket widths: each bucket covers 1/30 of the tax rate range.
*/
function linearThresholds(): bigint[] {
return Array.from({ length: 29 }, (_, i) =>
(BigInt(i + 1) * ONE_E18) / 30n,
);
}
/**
* Square-root thresholds — more buckets at low tax rates.
* Threshold[i] = sqrt((i+1)/30) * 1e18.
* Compresses the high-tax range and spreads the low-tax range.
*/
function sqrtThresholds(): bigint[] {
return Array.from({ length: 29 }, (_, i) => {
// Work in units of 1e9 for float precision, then scale with BigInt
const scaled = Math.sqrt((i + 1) / 30) * 1e9;
return BigInt(Math.round(scaled)) * 1_000_000_000n;
});
}
/** Return the 29 boundary values for the given tax distribution. */
export function getTaxThresholds(dist: TaxDistribution): bigint[] {
switch (dist) {
case 'exponential': return exponentialThresholds();
case 'linear': return linearThresholds();
case 'sqrt': return sqrtThresholds();
}
}
// ---- Code generation -------------------------------------------------------
/**
* Generate the output-push fragment for a bear/bull param set.
* Pushes 4 values bottom-first so stack top = CI, bottom = DD.
*/
function outputBlock(p: BullBearParams): string {
return `${p.discoveryDepth} ${p.anchorWidth} ${p.anchorShare} ${p.capitalInefficiency}`;
}
/**
* Generate the 30-way nested EXEC.IF tax-rate lookup.
*
* Returns inline Push3 tokens that, when placed inside a list, leave one
* integer (0..29) on the DYADIC stack representing the tax rate bucket index.
*
* Structure (right-recursive):
* TAXRATE t0 DYADIC.<= EXEC.IF 0 ( TAXRATE t1 DYADIC.<= EXEC.IF 1 ( ... ) )
*/
function generateTaxLookup(thresholds: bigint[]): string {
if (thresholds.length !== 29) {
throw new Error(`Expected 29 tax thresholds, got ${thresholds.length}`);
}
function nested(i: number): string {
if (i === 28) {
// Innermost: threshold[28] → index 28 or 29
return `TAXRATE ${thresholds[i]} DYADIC.<= EXEC.IF ${i} ${i + 1}`;
}
return `TAXRATE ${thresholds[i]} DYADIC.<= EXEC.IF ${i} ( ${nested(i + 1)} )`;
}
return nested(0);
}
/**
* Generate Push3 source text for one seed variant.
*
* The generated program has the same structure as optimizer_seed.push3:
* 1. Bind inputs 0-1; discard 2-7
* 2. Scale percentageStaked → integer 0-100
* 3. If staked > stakedThreshold: compute penalty, choose bull or bear
* 4. Else: bear outputs
*/
export function generateSeedVariant(params: SeedVariantParams): string {
const { stakedThreshold, penaltyThreshold, bull, bear, taxDistribution } = params;
const thresholds = getTaxThresholds(taxDistribution);
const taxLookup = generateTaxLookup(thresholds);
const bullOut = outputBlock(bull);
const bearOut = outputBlock(bear);
return [
`;; Generated seed variant (#638)`,
`;; staked_threshold=${stakedThreshold} penalty_threshold=${penaltyThreshold}`,
`;; bull: CI=${bull.capitalInefficiency} AS=${bull.anchorShare} AW=${bull.anchorWidth} DD=${bull.discoveryDepth}`,
`;; bear: CI=${bear.capitalInefficiency} AS=${bear.anchorShare} AW=${bear.anchorWidth} DD=${bear.discoveryDepth}`,
`;; tax_distribution=${taxDistribution}`,
`(`,
` PERCENTAGESTAKED DYADIC.DEFINE`,
` TAXRATE DYADIC.DEFINE`,
` DYADIC.POP`,
` DYADIC.POP`,
` DYADIC.POP`,
` DYADIC.POP`,
` DYADIC.POP`,
` DYADIC.POP`,
` PERCENTAGESTAKED`,
` 100 DYADIC.*`,
` 1000000000000000000 DYADIC./`,
` STAKED DYADIC.DEFINE`,
` STAKED ${stakedThreshold} DYADIC.>`,
` EXEC.IF`,
` (`,
` 100 STAKED DYADIC.-`,
` DELTAS DYADIC.DEFINE`,
` ${taxLookup}`,
` DYADIC.DUP 14 DYADIC.>=`,
` EXEC.IF`,
` (`,
` 1 DYADIC.+`,
` DYADIC.DUP 29 DYADIC.>`,
` EXEC.IF`,
` ( DYADIC.POP 29 )`,
` ( )`,
` )`,
` ( )`,
` EFFIDX DYADIC.DEFINE`,
` DELTAS DELTAS DYADIC.*`,
` DELTAS DYADIC.*`,
` EFFIDX DYADIC.*`,
` 20 DYADIC./`,
` ${penaltyThreshold} DYADIC.<`,
` EXEC.IF`,
` ( ${bullOut} )`,
` ( ${bearOut} )`,
` )`,
` ( ${bearOut} )`,
`)`,
].join('\n');
}
// ---- Variant enumeration ---------------------------------------------------
/**
* Enumerate all parameter combinations in a deterministic order.
*
* Total = |STAKED_THRESHOLDS| × |PENALTY_THRESHOLDS| × |BULL_VARIANTS|
* × |BEAR_VARIANTS| × |TAX_DISTRIBUTIONS|
* = 6 × 4 × 4 × 4 × 3 = 1152
*/
export function enumerateVariants(): SeedVariantParams[] {
const result: SeedVariantParams[] = [];
for (const taxDistribution of TAX_DISTRIBUTIONS) {
for (const stakedThreshold of STAKED_THRESHOLDS) {
for (const penaltyThreshold of PENALTY_THRESHOLDS) {
for (const bull of BULL_VARIANTS) {
for (const bear of BEAR_VARIANTS) {
result.push({
stakedThreshold,
penaltyThreshold,
bull,
bear,
taxDistribution,
});
}
}
}
}
}
return result;
}
/**
* Select `n` diverse variants from the full parameter space.
*
* When n ≤ total combinations (1152), samples evenly so each axis of variation
* is represented. When n > total, returns all combinations.
*
* The first variant is always the current seed configuration
* (stakedThreshold=91, penaltyThreshold=50, bull/bear=defaults, exponential).
*/
export function selectVariants(n: number): SeedVariantParams[] {
if (n < 1) throw new RangeError('n must be at least 1');
const all = enumerateVariants();
if (n >= all.length) return all;
// Even-stride sampling across the full enumeration
const stride = all.length / n;
return Array.from({ length: n }, (_, i) => all[Math.floor(i * stride)]);
}