1D Optogenetic Hill Circuit

Minimal single-species optogenetic gene circuit. One fluorescent protein X driven by a continuous light input U [0, 1] through a Hill production rate and linear degradation. Intended as a transparent sandbox for system-identification experiments.

Reaction network

Two Gillespie reactions:

  • Production: X with rate k_prod · U^n / (K^n + U^n)

  • Degradation: X with rate k_deg · X

Steady state under constant U: ⟨X⟩_ss = k_prod · hill(U, K, n) / k_deg.

Action and observation

  • Action: Box(low=0.0, high=1.0, shape=()) — continuous light intensity.

  • Observation: shape (1,)X / X_obs_normalizer.

Physics

Stochastic dynamics using the Gillespie algorithm for exact simulation.

Pure stateless physics for the 1D optogenetic circuit (opto_hill_1d).

Minimal single-species stochastic gene circuit driven by a continuous light input. Intended as a transparent sandbox for system-identification experiments — everything about the dynamics is controlled by four kinetic parameters and one continuous input.

System Description:

Light Input (U ∈ [0, 1]) → X (fluorescent protein copy number)

Two Chemical Reactions: 1. Light-driven production: ∅ → X (rate: k_prod · hill(U, K, n)) 2. Protein degradation: X → ∅ (rate: k_deg · X)

Steady state at constant U: ⟨X⟩_ss = k_prod · hill(U, K, n) / k_deg

class myriad.envs.bio.opto_hill_1d.physics.PhysicsState(time, X, next_reaction_time)[source]

Bases: NamedTuple

Pure physical state of the 1D optogenetic circuit.

Variables:
  • time (jax.jaxlib._jax.Array) – Current simulation time (minutes)

  • X (jax.jaxlib._jax.Array) – Fluorescent protein copy number (molecules, integer-valued)

  • next_reaction_time (jax.jaxlib._jax.Array) – Scheduled time of next reaction (minutes). Set to inf when no reaction is pending (sample fresh). Preserved across RL step boundaries for physical accuracy.

time: Array

Alias for field number 0

X: Array

Alias for field number 1

next_reaction_time: Array

Alias for field number 2

to_array()[source]

Convert to flat array for NN-based agents.

Note: next_reaction_time is excluded as it’s internal bookkeeping.

Returns:

Array of shape (2,) with [time, X]

Return type:

Array

classmethod from_array(arr)[source]

Create state from flat array.

Parameters:

arr (Array) – Array of shape (2,) with [time, X]

Returns:

PhysicsState instance (next_reaction_time defaults to inf)

Return type:

PhysicsState

classmethod create(time, X, next_reaction_time=None)[source]

Factory method to create PhysicsState with default next_reaction_time.

class myriad.envs.bio.opto_hill_1d.physics.PhysicsConfig(timestep_minutes=5.0, max_gillespie_steps=10000)[source]

Bases: object

Static structural constants for the 1D optogenetic circuit.

Passed as static_argnames to jit. Kinetic parameters (k_prod, K, n, k_deg) belong in PhysicsParams because they vary between cells and are the targets of system identification.

timestep_minutes: float = 5.0
max_gillespie_steps: int = 10000
__init__(timestep_minutes=5.0, max_gillespie_steps=10000)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.opto_hill_1d.physics.PhysicsParams(k_prod=5.0, K=0.5, n=2.0, k_deg=0.05)[source]

Bases: object

Dynamic kinetic parameters — vmappable over parameter space.

Defaults yield a steady state near X ≈ k_prod / k_deg = 100 molecules at full light (U=1) and half-max response at U=0.5.

k_prod: float | Array = 5.0
K: float | Array = 0.5
n: float | Array = 2.0
k_deg: float | Array = 0.05
__init__(k_prod=5.0, K=0.5, n=2.0, k_deg=0.05)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.opto_hill_1d.physics.PhysicsParamsPrior(k_prod_loc=1.6094379124341003, k_prod_scale=0.0, K_loc=-0.6931471805599453, K_scale=0.0, n_loc=0.6931471805599453, n_scale=0.0, k_deg_loc=-2.995732273553991, k_deg_scale=0.0)[source]

Bases: object

Log-normal prior over kinetic parameters.

Each parameter p is sampled as p ~ exp(Normal(loc, scale)). With scale=0 the distribution collapses to a point mass at exp(loc), so the default (all scales zero) is fully deterministic and backward compatible.

k_prod_loc: float | Array = 1.6094379124341003
k_prod_scale: float | Array = 0.0
K_loc: float | Array = -0.6931471805599453
K_scale: float | Array = 0.0
n_loc: float | Array = 0.6931471805599453
n_scale: float | Array = 0.0
k_deg_loc: float | Array = -2.995732273553991
k_deg_scale: float | Array = 0.0
sample(key)[source]
__init__(k_prod_loc=1.6094379124341003, k_prod_scale=0.0, K_loc=-0.6931471805599453, K_scale=0.0, n_loc=0.6931471805599453, n_scale=0.0, k_deg_loc=-2.995732273553991, k_deg_scale=0.0)
replace(**updates)

Returns a new object replacing the specified fields with new values.

myriad.envs.bio.opto_hill_1d.physics.compute_propensities(state, action, params)[source]

Compute reaction propensities for the two reactions.

Parameters:
  • state (PhysicsState) – Current physical state (time, X)

  • action (Array) – Continuous light intensity U ∈ [0, 1]

  • params (PhysicsParams) – Kinetic parameters — vmappable

Returns:

Array of shape (2,) with propensities [r_production, r_degradation]

Return type:

Array

myriad.envs.bio.opto_hill_1d.physics.apply_reaction(state, reaction_idx)[source]

Apply a single reaction to update the state.

Uses jax.lax.switch for JAX-compatible control flow.

Parameters:
  • state (PhysicsState) – Current physical state

  • reaction_idx (Array) – Index of reaction to apply (0 = production, 1 = degradation)

Returns:

Updated physical state after reaction

Return type:

PhysicsState

myriad.envs.bio.opto_hill_1d.physics.step_physics(key, state, action, params, config, previous_action, interval_start)[source]

Pure physics step using the Gillespie SSA.

Runs Gillespie simulation from current time until the end of the current interval (interval_start + timestep_minutes).

Parameters:
  • key (Array) – RNG key for stochastic simulation

  • state (PhysicsState) – Current physical state

  • action (Array) – Continuous light intensity U ∈ [0, 1]

  • params (PhysicsParams) – Dynamic parameters

  • config (PhysicsConfig) – Static physics constants

  • previous_action (Array) – Action from previous timestep. If different from action, the pending reaction time is invalidated (propensities changed).

  • interval_start (Array) – Start time of current interval (t * timestep_minutes).

Returns:

Next physical state after simulating until interval end

Return type:

PhysicsState

System Identification Task

Zero-reward task. Parameters θ* = (k_prod, K, n, k_deg) live in SysIdTaskParams and persist across resets; only the cell state X resets between episodes. The inference objective is computed outside the environment.

System identification task for the 1D optogenetic circuit (opto_hill_1d).

The circuit runs with unknown kinetic parameters θ* = (k_prod, K, n, k_deg) stored in SysIdTaskParams. The agent observes the fluorescent protein copy number X(t) and selects continuous light intensities U(t) ∈ [0, 1]. Between episodes the cell state resets (X=0), but θ* is fixed — it is a property of the circuit, not the cell.

Reward is zero; the inference algorithm is the agent, and its objective (information gain or posterior entropy) is computed outside the environment.

class myriad.envs.bio.opto_hill_1d.tasks.sysid.SysIdTaskState(physics, t, U)[source]

Bases: NamedTuple

State for the opto_hill_1d system identification task.

Variables:
  • physics (myriad.envs.bio.opto_hill_1d.physics.PhysicsState) – Underlying stochastic physics state (time, X, next_reaction_time)

  • t (jax.jaxlib._jax.Array) – Timestep counter within the current episode (0 to max_steps)

  • U (jax.jaxlib._jax.Array) – Previous continuous light input — used by step_physics for pending-reaction invalidation when the action changes.

physics: PhysicsState

Alias for field number 0

t: Array

Alias for field number 1

U: Array

Alias for field number 2

class myriad.envs.bio.opto_hill_1d.tasks.sysid.SysIdTaskConfig(physics=<factory>, max_steps=288, X_obs_normalizer=100.0)[source]

Bases: object

Static configuration for the opto_hill_1d SysID task.

physics: PhysicsConfig
max_steps: int = 288
X_obs_normalizer: float = 100.0
__init__(physics=<factory>, max_steps=288, X_obs_normalizer=100.0)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.opto_hill_1d.tasks.sysid.SysIdTaskParams(physics=<factory>)[source]

Bases: object

Dynamic parameters for the SysID task — the unknown circuit parameters θ*.

These are vmappable: pass different SysIdTaskParams per-env to simulate a population of cells each with its own kinetic parameters.

Variables:

physics (myriad.envs.bio.opto_hill_1d.physics.PhysicsParams) – Kinetic parameters (k_prod, K, n, k_deg) — targets of inference

physics: PhysicsParams
__init__(physics=<factory>)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.opto_hill_1d.tasks.sysid.SysIdTaskParamsPrior(physics=<factory>)[source]

Bases: object

Prior distribution over SysID task parameters.

physics: PhysicsParamsPrior
sample(key)[source]
__init__(physics=<factory>)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.opto_hill_1d.tasks.sysid.SysIdObs(X_normalized)[source]

Bases: NamedTuple

Observation for the opto_hill_1d SysID task.

Only the fluorescent protein copy number is observable.

Variables:

X_normalized (jax.jaxlib._jax.Array) – X count divided by X_obs_normalizer

X_normalized: Array

Alias for field number 0

to_array()[source]
classmethod from_array(arr)[source]
myriad.envs.bio.opto_hill_1d.tasks.sysid.get_obs(state, config)[source]
myriad.envs.bio.opto_hill_1d.tasks.sysid.step(key, state, action, params, config)

Step the circuit forward one interval using θ* from params.

Parameters:
  • key (Array) – RNG key for Gillespie simulation

  • state (SysIdTaskState) – Current task state

  • action (Array) – Continuous light intensity U ∈ [0, 1] (scalar)

  • params (SysIdTaskParams) – Task parameters carrying θ*

  • config (SysIdTaskConfig) – Static task configuration

Returns:

obs, next_state, reward (always 0.0), done, info

Return type:

tuple[SysIdObs, SysIdTaskState, Array, Array, dict[str, Any]]

myriad.envs.bio.opto_hill_1d.tasks.sysid.reset(key, params, config)

Reset to initial cell state (X=0). θ* is unchanged — it lives in params.

myriad.envs.bio.opto_hill_1d.tasks.sysid.get_obs_shape(config)[source]
myriad.envs.bio.opto_hill_1d.tasks.sysid.get_action_space(config)[source]

Continuous light intensity U ∈ [0, 1].

myriad.envs.bio.opto_hill_1d.tasks.sysid.make_env(config=None, params=None, params_prior=None, **kwargs)[source]

Create an opto_hill_1d system identification environment.

Parameters:
  • config (SysIdTaskConfig | None) – Static task config. If None, built from kwargs.

  • params (SysIdTaskParams | None) – Task params carrying θ*. If None, uses PhysicsParams defaults.

  • params_prior (SysIdTaskParamsPrior | None) – Optional prior for domain randomization. If set, env.sample_params_fn will sample distinct θ* per parallel env. Can also be triggered via flat kwargs (e.g. k_prod_scale=0.3).

  • **kwargs – Forwarded to SysIdTaskConfig / PhysicsConfig / PhysicsParams / PhysicsParamsPrior via filter_kwargs.

Returns:

Environment instance for the SysID task.

Return type:

Environment[SysIdTaskState, SysIdTaskConfig, SysIdTaskParams, SysIdObs]

Example

>>> env = make_env()
>>> env = make_env(k_prod_scale=0.3, K_scale=0.2)