harb/tools/push3-evolution/seed-generator.ts

312 lines
11 KiB
TypeScript
Raw Permalink Normal View History

/**
* 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).
*
* capitalInefficiency is intentionally fixed at 0 across all variants the
* current seed (optimizer_v3.push3) uses CI=0 in all branches, and varying it
* is left to the mutation operators once evolution starts.
*/
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).
*
* capitalInefficiency is intentionally fixed at 0 for the same reason as BULL_VARIANTS.
*/
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.
*
* STAKED_THRESHOLDS is the outermost loop so that even-stride sampling in
* selectVariants(n) naturally covers all staked% values: each block of
* (4 penalty × 4 bull × 4 bear × 3 tax) = 192 entries maps to one threshold.
*
* 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 stakedThreshold of STAKED_THRESHOLDS) {
for (const penaltyThreshold of PENALTY_THRESHOLDS) {
for (const bull of BULL_VARIANTS) {
for (const bear of BEAR_VARIANTS) {
for (const taxDistribution of TAX_DISTRIBUTIONS) {
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 across the enumeration so
* each axis of variation is represented. Because STAKED_THRESHOLDS is the
* outermost loop, selectVariants(6) picks one representative per staked%
* threshold (stride = 1152/6 = 192, one block per threshold value).
*
* When n > total, returns all 1152 combinations.
*/
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)]);
}