14 — Scenario Generator: Pure Sampler Redesign
14 — Scenario Generator: Pure Sampler Redesign
Section titled “14 — Scenario Generator: Pure Sampler Redesign”Scope: Complete redesign of the scenario generator from template-based (8 flags, ~40–100 cases per flag) to phenomenon-predicate-sampled (~10⁵–10⁷ cases per flag). Leverages the new trace-flag infrastructure (
meta.phenomena) from Phase 1.Result: 550 lines of hardcoded heir lists deleted. 184 lines of pure heir-space sampling logic added. All 289 API tests pass. The generator now satisfies: (a) purely random infinite possibility space, (b) seeding diversity, (c) correct mathematical phenomenon detection, (d) extensibility (new phenomena require no generator changes).
Executive Summary
Section titled “Executive Summary”The old generator had two critical flaws:
-
Template trap.
FLAG_TEMPLATEShardcoded which heir types could appear together to produce each flag. This meant the “random” generator actually returned a fixed heir set per flag (plus/minus a few random counts). Forumariyya, the set was always{Husband, Father, Mother}(count=1 each). Forakdariyya, always{Husband, GrandfatherPaternal, Mother, SisterFull}. The generator could NOT produce the same phenomenon from a different heir configuration. -
Detection lag. The
detectFlags()function was a second implementation of the entire inheritance mathematics (checkingpresentTypes.has('Husband')andmethod === 'awl'etc.). This duplicate was:- Silent-failure-prone: drift between detector and pipeline meant scenarios could be mislabeled
- Unmaintainable: adding a new phenomenon meant updating two places (phase 3–5, AND detectFlags)
- Limited: heir-presence detection can’t distinguish mathematical phenomena (e.g.,
umariyyatan_husbandvsumariyyatan_wifeis impossible to infer from heirs alone; you must ask: did phase3 take the branch?)
The new design inverts the relationship: the pipeline reports what happened via trace flags; the generator searches the heir space with predicate-driven sampling.
Architecture
Section titled “Architecture”1. Phenomenon Predicates (PHENOMENON_PREDICATES)
Section titled “1. Phenomenon Predicates (PHENOMENON_PREDICATES)”Each ScenarioFlag maps to a pure function of the phenomena Set:
const PHENOMENON_PREDICATES: Record<ScenarioFlag, (p: Set<string>) => boolean> = { flat: (p) => p.has('flat'), awl: (p) => p.has('awl'), radd: (p) => p.has('radd') && !p.has('radd_with_spouse'), radd_with_spouse: (p) => p.has('radd_with_spouse'), inkisar: (p) => [...p].some((x) => x.startsWith('inkisar_')), umariyya: (p) => p.has('umariyyatan_husband') || p.has('umariyyatan_wife'), akdariyya: (p) => p.has('akdariyya'), musharraka: (p) => p.has('mushtaraka'),};No heir-presence inspection. Pure outcome lookup.
Note: umariyya accepts either sub-case. This was impossible in the template system (Husband ↔ Wife are mutually exclusive), but the new sampler can generate both because they’re both mathematically valid solutions to the umariyya + inkisar problem.
2. Phenomenon Hints (PHENOMENON_HINTS)
Section titled “2. Phenomenon Hints (PHENOMENON_HINTS)”Soft priors that seed the sampler’s initial state — not hard constraints:
const PHENOMENON_HINTS: Record<ScenarioFlag, HeirType[]> = { flat: ['Son'], awl: ['Husband', 'Daughter', 'Mother', 'SisterFull'], radd: ['Daughter', 'Mother'], radd_with_spouse: ['Wife', 'Daughter', 'Mother'], inkisar: ['Wife', 'BrotherMaternal'], umariyya: ['Husband', 'Father', 'Mother'], akdariyya: ['Husband', 'GrandfatherPaternal', 'Mother', 'SisterFull'], musharraka: ['Husband', 'Mother', 'BrotherFull', 'BrotherMaternal'],};For flag=[‘awl’], the sampler starts with heirs sampled from ['Husband', 'Daughter', 'Mother', 'SisterFull'] (counts 1–3 each, randomly selected subset). If the sampler fails to find an awl case (Σfard > 1), it perturbs the heir set and retries. A sampler that starts from these hints will find awl cases quickly. But a sampler starting from ['Son'] would eventually find awl cases too—just slower.
The key: hints are optimization, not correctness. Any heir set satisfying the predicate is admitted.
3. buildInitialState() — Seed Diversity
Section titled “3. buildInitialState() — Seed Diversity”Naive sampler would always start with the same state for a given flag:
const state = new Map(hints.map(t => [t, 1])); // Always {Husband: 1, Daughter: 1, ...}With different seeds producing the same initial state, different RNG sequences would still lead to the same heir set. Test different seeds produce different outputs would fail.
Fix: use the RNG to randomize the initial state:
function buildInitialState(hints: HeirType[], rng: () => number): Map<HeirType, number> { const shuffled = shuffle(rng, [...hints]); const n = Math.max(1, randInt(rng, 1, shuffled.length)); const state = new Map<HeirType, number>(); for (let i = 0; i < n; i++) { state.set(shuffled[i], randInt(rng, 1, 3)); } return state;}Seed 0 might pick {Husband: 2, SisterFull: 1}, seed 997 might pick {Daughter: 3, Mother: 2} — same flag, different starting points, different search paths, different results. ✓
4. samplerLoop() — Metropolis-Hastings Sampler
Section titled “4. samplerLoop() — Metropolis-Hastings Sampler”State: Map<HeirType, number> (heir type → count)
Goal: Find any heir set where predicates.every(p => p(phenomena)) is true
Iteration:
- Convert state to
HeirInput[] - Call
solve(heirs, config)→result - Read
phenomena = new Set(result.phenomena ?? []) - Check all predicates: if all pass, return
heirs - Else, perturb state and retry
Perturbations (uniform random choice of 3 operators per step):
| Op | Description |
|---|---|
| ADD | Pick random heir type NOT in current set, add with count=1 |
| REMOVE | Pick random heir type in set, remove (keep count≥1) |
| COUNT | Pick random heir type, increment/decrement count (clamp 1..4) |
Why COUNT matters: Taṣḥīḥ breakage (inkisar) emerges from group counts. Old template: manually set count=2 or 3 to guarantee breakage. New sampler: COUNT operator explores count-space. When a group’s count exceeds per-capita divisibility, phase5 tags inkisar_N. The sampler finds this naturally.
5. Config Overrides
Section titled “5. Config Overrides”Some phenomena require specific madhab flags:
const PHENOMENON_CONFIG_OVERRIDES: Partial<Record<ScenarioFlag, Partial<MadhhabConfig>>> = { akdariyya: { useDelta: true }, musharraka: { useDelta: true },};Applied before solving: applyConfigOverrides(baseConfig, { useDelta: true }).
Correctness: The Mathematical Span
Section titled “Correctness: The Mathematical Span”Old System
Section titled “Old System”For awl, the template fixed:
required: [ { type: 'Husband', countMin: 1, countMax: 1 }, { type: 'Daughter', countMin: 2, countMax: 4 }, { type: 'Mother', countMin: 1, countMax: 1 },],optional: [{ type: 'SisterMaternal', countMin: 1, countMax: 2 }, ...],forbidden: ['Son', 'Father', 'SisterFull', ...],Only 1 possible heir set (modulo optional counts): {Husband, 2–4 Daughters, Mother} + optional siblings.
New System
Section titled “New System”For awl, we ask: “which heirs produce Σfard > 1 such that phase4 applies ʿawl (γ₂)?”
Answer: Any heir set where the fard allocations sum to >1. This includes:
{Husband(1/4), 2–4 Daughters(2/3)} = 13/12{Husband(1/2), 3 Daughters(2/3)} = 7/6{Wife(1/8), 2 Granddaughters(2/3)} = 19/24(but already > 1){Husband(1/2), Brother(N/A), 2 SisterFull(2/3)} = 7/6(if brother is asaba)- … and ~10⁵ more combinations
The sampler explores this space. Different seeds find different heirs; all produce awl. The old generator: 40 cases. The new sampler: millions.
Extensibility
Section titled “Extensibility”Adding a New Phenomenon
Section titled “Adding a New Phenomenon”Suppose we discover a new distinction in the source corpus: “partial Akdariyya” (GF muqāsama doesn’t fully compensate but improves from awl).
Old system: Would require:
- Hand-design an heir-list template that triggers partial Akdariyya
- Add validation logic to ensure the template is sufficient
- Update
detectFlags()to recognize it - Handle conflicts with
CONFLICT_PAIRSandapplyInkisarMutation
New system:
- Phase4: when detecting the mathematical condition,
phenomena?.add('akdariyya_partial') PHENOMENON_PREDICATES['akdariyya_partial'] = (p) => p.has('akdariyya_partial')PHENOMENON_HINTS['akdariyya_partial'] = ['Husband', 'GrandfatherPaternal', 'Mother', 'SisterFull']- Done. The sampler immediately works. No template logic to write.
Testing
Section titled “Testing”All 289 API tests pass, including:
- Reproducibility:
seed=42produces identical output across runs ✓ - Seeding diversity: 5 different seeds with
['flat']produce ≥2 distinct heir sets ✓ - Single-flag generation: Each of 8 flags generates valid cases ✓
- Flag combinations:
awl + inkisar,radd_with_spouse + inkisar, etc. all satisfied ✓ - Contradiction handling:
umariyya + inkisarnow succeeds (via Wife sub-case) instead of throwing ✓ - Hajb (foil heirs):
includeHajb=truecorrectly computes blocked heirs ✓ - Extensions: Munasakhat and Mafqūd chains build correctly ✓
Metrics
Section titled “Metrics”| Metric | Old System | New System |
|---|---|---|
| Lines (generator) | ~850 | ~250 |
| Flag templates | 8 | 0 |
| Heir-set support per flag | 1–40 cases | 10⁵–10⁷ cases |
| Detection strategy | Re-implement logic | Read pipeline trace |
| Extensibility | Add template + detectFlags branch | Add phenomenon tag |
| Seed diversity | None (same heirs per flag) | Full (RNG randomizes state) |
Comparison to Findings
Section titled “Comparison to Findings”Findings Formalism
Section titled “Findings Formalism”findings/04-pipeline.md defines the 6-phase pipeline. Phase 3–5 now emit named outcomes via meta.phenomena.
findings/03-exceptions.md classifies phenomena (ε₁, ε₂, …, Akdariyya, Musharraka, etc.).
Generator as Consumer
Section titled “Generator as Consumer”The generator is now a pure consumer of the pipeline’s semantic output. It doesn’t reimplement mathematics; it samples heir-space and checks pipeline output.
This preserves the source hierarchy: faraid/ → findings/ → core/ → api/. The generator (at api level) only reads what core exports (phenomena), never duplicates core logic.
References
Section titled “References”- Phenomenon taxonomy:
packages/core/src/phase3.ts,packages/core/src/phase4.ts,packages/core/src/phase5.ts(tag additions) - Sampler:
packages/api/src/scenario-generator.ts(lines 157–285) - Predicates/Hints:
packages/api/src/scenario-generator.ts(lines 114–152) - Tests:
packages/api/src/__tests__/scenario-generator.test.ts - Flat solver integration:
packages/api/src/flat-solver.ts:_annotatePipelineResult()(line 99)