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>
296 lines
10 KiB
TypeScript
296 lines
10 KiB
TypeScript
/**
|
||
* 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)]);
|
||
}
|