You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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).
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.
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).
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
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.
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)
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.
Test rosso voci: StochasticPitchStrategy con seed esplicito → due istanze in processi simulati danno stesso offset. Verde: hashlib derivation + param seed.
Ripetere per onset, pan, pointer.
Test retrocompat: YAML senza seed → comportamento invariato (offset stabile entro run, factory firme reggono).
Test edge: seed: 0, seed negativo, seed stringa (decidere se accettare → random.seed() accetta str/int).
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 chiaveseednello YAML.Oggi due meccanismi stocastici distinti, entrambi non riproducibili fra run:
hash()(voice strategy) —random.Random(hash(stream_id + str(voice_index))). Stabile entro un run, non fra processi (hash randomization,PYTHONHASHSEEDmai fissato).randomglobale 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
Impact analysis
Meccanismo 1 — RNG locale voci (
hash()→hashlib)src/strategies/voice_pitch_strategy.py:226src/strategies/voice_pan_strategy.py:167src/strategies/voice_onset_strategy.py:131src/strategies/voice_pointer_strategy.py:117Sostituire
hash(stream_id + str(vi))con seed derivato viahashlib.sha256(f"{seed}:{stream_id}:{vi}".encode()).hexdigest()→ int perrandom.Random.hashlibè deterministico per costruzione: non serve toccarePYTHONHASHSEED. Seseed is None→ fallbackhash()attuale (retrocompat).Meccanismo 2 —
randomglobale (grani)src/shared/probability_gate.py:68,86src/shared/utils.py:26src/shared/distribution_strategy.py:80,119src/controllers/density_controller.py:120src/controllers/window_selection_strategy.py:121,174,256src/strategies/variation_strategy.py:80Coperti da un singolo
random.seed(seed)a inizio render.Catena di propagazione
src/engine/generator.py:65(load_yaml) → estrarreself.seed = self.data.get('seed')src/main.py:118(main) → seseed is not None:random.seed(seed)prima dicreate_elements()seedfino alle 4*StrategyFactory.create(): catenaStream → VoiceManager → Factory → Strategy. Paramseed=Nonecon default → retrocompat firme.Rischi
seed=Nonepuò toccare i test che istanziano direttamente (esempi docstring avoice_pointer:154,voice_onset:170,voice_pan:266). DefaultNonemitiga.random.seed()semina solo ilrandomPython (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).random.seed()globale (meccanismo 2) e lasciare le voci conhash()— ma resta incompleto: le voci non sarebbero riproducibili fra processi. Scelta consigliata: completo (entrambi i meccanismi).Piano TDD (red → green, una strategy alla volta)
seed: 42, due render NumPy → sequenze grani stocastici identiche (densità/dispersione per-grano). Verde:random.seed()inmain/render entry + parse inload_yaml.StochasticPitchStrategyconseedesplicito → due istanze in processi simulati danno stesso offset. Verde:hashlibderivation + paramseed.onset,pan,pointer.seed→ comportamento invariato (offset stabile entro run, factory firme reggono).seed: 0, seed negativo, seed stringa (decidere se accettare →random.seed()accetta str/int).File toccati (riepilogo)
src/engine/generator.py,src/main.pysrc/strategies/voice_{pitch,pan,onset,pointer}_strategy.py+ factorysrc/controllers/voice_manager.py,src/core/stream.py(propagazione)Test da aggiornare/aggiungere
tests/strategies/test_voice_{pitch,onset,pointer,pan}_strategy.pytests/strategies/test_strategies.pytests/engine/test_generator.pytests/engine/test_seed_reproducibility.py(e2e per meccanismo 2)Documentazione
docs/reference/yaml.md→ chiaveseedtop-level (Scope/Sintassi/Bounds/Esempi)Definition of done
seedopzionale: assente = comportamento attuale; presente = run riproducibile (NumPy)make testsverdeyaml.mdaggiornata +make docs-lintverde