"""Utilities that capture the "Iterative Math-Build Loop" workflow. This module turns a simple mathematical seed—the logistic map—into a tiny experiment that can be iterated forever. The workflow mirrors Block 43: Step 1 (Seed) Use the logistic function :math:`x_{n+1} = r x_n (1 - x_n)` as the pattern. Step 2 (Three translations) Physics Population dynamics where energy input (sunlight, nutrients) is limited. Code A feedback loop that maps the state into itself with a tunable gain. Hardware A single-transistor logistic oscillator driven by a biasing envelope. Step 3 (Build a toy) Simulate the logistic loop over a configurable number of pulses. Step 4 (Measure & rename) Rename raw variables once we observe their behaviour: ``population`` becomes ``pulse_level`` and ``r`` becomes ``gain`` to match the oscillation view. Step 5 (Archive & fork) Export snapshots that carry timestamped tags ready for storage. """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timezone from statistics import fmean from typing import Iterable, List @dataclass(frozen=True) class LoopSnapshot: """Stores a measured sequence from the logistic loop. Attributes ---------- tag: Timestamped identifier that makes it easy to fork future experiments. gain: The effective amplification factor observed during the run. pulse_levels: Measured levels after each iteration of the loop. phase_drift: The final-step change, useful for spotting bifurcations. mean_level: Average pulse level over the capture window. """ tag: str gain: float pulse_levels: List[float] phase_drift: float mean_level: float def iterate_logistic_loop(*, gain: float, seed_level: float, pulses: int) -> List[float]: """Run the logistic map for the requested number of pulses. Parameters ---------- gain: Feedback intensity of the loop (commonly ``r`` in the logistic map). seed_level: Initial pulse level. Must be in ``(0, 1)`` for the canonical map. pulses: Number of iterations to execute. Returns ------- list of float Observed pulse levels after each iteration. """ if pulses <= 0: raise ValueError("pulses must be positive") if not 0.0 < seed_level < 1.0: raise ValueError("seed_level must be strictly between 0 and 1") level = seed_level pulse_levels: List[float] = [] for _ in range(pulses): level = gain * level * (1.0 - level) pulse_levels.append(level) return pulse_levels def capture_snapshot( *, gain: float = 3.72, seed_level: float = 0.21, pulses: int = 128, tag_prefix: str = "symmetry_break", ) -> LoopSnapshot: """Simulate the loop and bundle the measurement in a :class:`LoopSnapshot`. The timestamp embedded into ``tag`` makes it trivial to archive runs and reference them the next time the "Next!" impulse hits. """ pulse_levels = iterate_logistic_loop(gain=gain, seed_level=seed_level, pulses=pulses) phase_drift = pulse_levels[-1] - pulse_levels[-2] mean_level = fmean(pulse_levels) timestamp = datetime.now(timezone.utc).strftime("%Y_%m_%dT%H%M%SZ") tag = f"{tag_prefix}_{timestamp}" return LoopSnapshot( tag=tag, gain=gain, pulse_levels=pulse_levels, phase_drift=phase_drift, mean_level=mean_level, ) def export_snapshot(snapshot: LoopSnapshot) -> str: """Serialise a snapshot into a minimal archival string. The format favours human parsing (CSV-like) so it can be dropped into a notebook, pasted into a README, or stored alongside lab photos. """ header = "tag,gain,mean_level,phase_drift,pulse_levels" levels = " ".join(f"{level:.6f}" for level in snapshot.pulse_levels) body = f"{snapshot.tag},{snapshot.gain:.6f},{snapshot.mean_level:.6f},{snapshot.phase_drift:.6f},{levels}" return f"{header}\n{body}\n" def sweep_gains( *, gains: Iterable[float], seed_level: float, pulses: int, ) -> List[LoopSnapshot]: """Generate snapshots for a sequence of gains. This helper makes it easy to scan for bifurcations and immediately archive the most interesting regimes. """ return [ capture_snapshot(gain=gain, seed_level=seed_level, pulses=pulses, tag_prefix=f"gain_{gain:.3f}") for gain in gains ] __all__ = [ "LoopSnapshot", "capture_snapshot", "export_snapshot", "iterate_logistic_loop", "sweep_gains", ]