Skip to content

feat(seed): chiave seed YAML per render riproducibile (punto 4 di #76) #81

@DMGiulioRomano

Description

@DMGiulioRomano

Contesto

Deriva dal punto 4 di #76 (docstring seed). I punti 1-3 (audit + correzione docstring + README) sono chiusi in e6aed74. Questo è il punto 4: feature opzionale, non bug — introdurre vera riproducibilità fra processi tramite chiave seed nello YAML.

Oggi due meccanismi stocastici distinti, entrambi non riproducibili fra run:

  1. RNG locale seminato con hash() (voice strategy) — random.Random(hash(stream_id + str(voice_index))). Stabile entro un run, non fra processi (hash randomization, PYTHONHASHSEED mai fissato).
  2. random globale mai seminato (grani stocastici) — random.seed() compare solo nei test.

Obiettivo: non bit-identità (le tendency mask sono stocastiche per natura), ma rendere riproducibile lo stesso run quando l'utente fissa un seed. Assente il seed → comportamento attuale invariato.

API proposta

# top-level, sibling di `streams`
seed: 42        # opzionale; assente → comportamento attuale (non riproducibile)

Impact analysis

Meccanismo 1 — RNG locale voci (hash()hashlib)

  • src/strategies/voice_pitch_strategy.py:226
  • src/strategies/voice_pan_strategy.py:167
  • src/strategies/voice_onset_strategy.py:131
  • src/strategies/voice_pointer_strategy.py:117

Sostituire hash(stream_id + str(vi)) con seed derivato via hashlib.sha256(f"{seed}:{stream_id}:{vi}".encode()).hexdigest() → int per random.Random. hashlib è deterministico per costruzione: non serve toccare PYTHONHASHSEED. Se seed is None → fallback hash() attuale (retrocompat).

Meccanismo 2 — random globale (grani)

  • src/shared/probability_gate.py:68,86
  • src/shared/utils.py:26
  • src/shared/distribution_strategy.py:80,119
  • src/controllers/density_controller.py:120
  • src/controllers/window_selection_strategy.py:121,174,256
  • src/strategies/variation_strategy.py:80

Coperti da un singolo random.seed(seed) a inizio render.

Catena di propagazione

  • src/engine/generator.py:65 (load_yaml) → estrarre self.seed = self.data.get('seed')
  • src/main.py:118 (main) → se seed is not None: random.seed(seed) prima di create_elements()
  • Propagazione seed fino alle 4 *StrategyFactory.create(): catena Stream → VoiceManager → Factory → Strategy. Param seed=None con default → retrocompat firme.

Rischi

  1. Cambio firma factory (4×): aggiungere seed=None può toccare i test che istanziano direttamente (esempi docstring a voice_pointer:154, voice_onset:170, voice_pan:266). Default None mitiga.
  2. Csound vs NumPy: random.seed() semina solo il random Python (renderer NumPy). Csound ha RNG proprio → i due renderer restano non bit-identici col seed. Documentare il limite (coerente con README già corretto in docs: docstring dichiarano 'seed deterministico / riproducibile tra sessioni' ma il random non è riproducibile fra run #76).
  3. Propagazione seed = parte più invasiva. In subordine si può fare solo random.seed() globale (meccanismo 2) e lasciare le voci con hash() — ma resta incompleto: le voci non sarebbero riproducibili fra processi. Scelta consigliata: completo (entrambi i meccanismi).

Piano TDD (red → green, una strategy alla volta)

  1. Test rosso meccanismo 2: stesso YAML con seed: 42, due render NumPy → sequenze grani stocastici identiche (densità/dispersione per-grano). Verde: random.seed() in main/render entry + parse in load_yaml.
  2. Test rosso voci: StochasticPitchStrategy con seed esplicito → due istanze in processi simulati danno stesso offset. Verde: hashlib derivation + param seed.
  3. Ripetere per onset, pan, pointer.
  4. Test retrocompat: YAML senza seed → comportamento invariato (offset stabile entro run, factory firme reggono).
  5. Test edge: seed: 0, seed negativo, seed stringa (decidere se accettare → random.seed() accetta str/int).

File toccati (riepilogo)

  • src/engine/generator.py, src/main.py
  • src/strategies/voice_{pitch,pan,onset,pointer}_strategy.py + factory
  • src/controllers/voice_manager.py, src/core/stream.py (propagazione)

Test da aggiornare/aggiungere

  • tests/strategies/test_voice_{pitch,onset,pointer,pan}_strategy.py
  • tests/strategies/test_strategies.py
  • tests/engine/test_generator.py
  • nuovo: tests/engine/test_seed_reproducibility.py (e2e per meccanismo 2)

Documentazione

  • docs/reference/yaml.md → chiave seed top-level (Scope/Sintassi/Bounds/Esempi)
  • Nota esplicita: seed → riproducibilità renderer NumPy; Csound non bit-identico.

Definition of done

  • seed opzionale: assente = comportamento attuale; presente = run riproducibile (NumPy)
  • make tests verde
  • doc yaml.md aggiornata + make docs-lint verde

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNuova funzionalita'

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions