Skip to content

Commit ff8b23e

Browse files
Refactor files and fix linter errors
1 parent 3ef45c7 commit ff8b23e

6 files changed

Lines changed: 225 additions & 733 deletions

File tree

docs/superpowers/specs/2026-06-05-course-outliner-agent-design.md

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'''Script that serializes Timecourse (timecourse_df + Jacobians) for all BioModels.'''
2+
3+
import src.constants as cn # type: ignore
4+
from src.biomodels_iterator import BiomodelsIterator # type: ignore
5+
from src.model import Model # type: ignore
6+
from src.timecourse import Timecourse # type: ignore
7+
8+
import os
9+
from typing import List
10+
11+
EXCLUDED_MODELS: List[str] = [
12+
"BIOMD0000000268",
13+
"BIOMD0000000625",
14+
]
15+
16+
17+
def main(
18+
is_report: bool = True,
19+
first_model_num: int = 0,
20+
last_model_num: int = int(1e9),
21+
excluded_models: List[str] = EXCLUDED_MODELS,
22+
) -> None:
23+
'''Serialize timecourses for all BioModels.
24+
25+
Parameters
26+
----------
27+
is_report : bool
28+
Whether to print progress.
29+
first_model_num : int
30+
First model number to include (inclusive).
31+
last_model_num : int
32+
Last model number to include (inclusive).
33+
excluded_models : List[str]
34+
Model names to skip.
35+
'''
36+
os.makedirs(cn.TIMECOURSE_SERIALIZATION_DIR, exist_ok=True)
37+
for item in BiomodelsIterator(
38+
excluded_models=excluded_models,
39+
is_report=is_report,
40+
first_model_num=first_model_num,
41+
last_model_num=last_model_num):
42+
model_name = item.model_name
43+
if not item.sbml_paths:
44+
continue
45+
pkl_path = os.path.join(cn.TIMECOURSE_SERIALIZATION_DIR,
46+
f"{model_name}_timecourse.pkl")
47+
if os.path.isfile(pkl_path):
48+
continue
49+
try:
50+
model = Model.makeBiomodel(model_name)
51+
timecourse = Timecourse(model=model, end_time=item.end_time)
52+
_ = timecourse.jacobian_collection_arr # Force calculations
53+
path = timecourse.serialize()
54+
except Exception as e:
55+
print(f"Error processing {model_name}: {e}")
56+
57+
58+
if __name__ == "__main__":
59+
main()

scripts/pwla.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343

4444
from __future__ import annotations
4545

46-
import numpy as np
47-
from scipy.integrate import solve_ivp
48-
from scipy.linalg import expm
49-
import matplotlib.pyplot as plt
50-
import matplotlib.gridspec as gridspec
46+
import numpy as np # type: ignore
47+
from scipy.integrate import solve_ivp # type: ignore
48+
from scipy.linalg import expm # type: ignore
49+
import matplotlib.pyplot as plt # type: ignore
50+
import matplotlib.gridspec as gridspec # type: ignore
5151
from typing import Callable
5252

5353

@@ -163,7 +163,7 @@ def _is_new_point(self, x: np.ndarray) -> bool:
163163
if not self.points:
164164
return True
165165
dists = [np.linalg.norm(x - s) for s in self.points]
166-
return min(dists) > self.delta
166+
return bool(np.min(dists) > self.delta)
167167

168168
def _add_point(self, x: np.ndarray):
169169
A = self.jac(x) # Jacobian at x
@@ -420,13 +420,13 @@ def sa(ax, title, xl="", yl=""):
420420
# ============================================================
421421

422422
def run_demo(system, system_name, x0_train, x0_test, t_end,
423-
delta_train, state_labels, n_steps=800, output_path=None):
423+
delta_train, state_labels, n_steps=800, output_path=None):
424424
"""
425425
Full TPWL workflow:
426-
1. Train on a representative trajectory from x0_train.
427-
2. Simulate TPWL (nearest + Gaussian) from x0_test.
428-
3. Simulate true nonlinear ODE from x0_test for comparison.
429-
4. Plot and report errors.
426+
1. Train on a representative trajectory from x0_train.
427+
2. Simulate TPWL (nearest + Gaussian) from x0_test.
428+
3. Simulate true nonlinear ODE from x0_test for comparison.
429+
4. Plot and report errors.
430430
"""
431431
print(f"\n{'='*60}")
432432
print(f" {system_name}")
@@ -438,12 +438,12 @@ def run_demo(system, system_name, x0_train, x0_test, t_end,
438438

439439
# ---- 1. True nonlinear solution (training trajectory) ----
440440
sol_train = solve_ivp(lambda t, x: system.f(x), t_span,
441-
x0_train, t_eval=t_eval, **ivp_kw)
441+
x0_train, t_eval=t_eval, **ivp_kw) # type: ignore
442442
assert sol_train.success, sol_train.message
443443

444444
# ---- 2. Train TPWL (nearest) ----
445445
model_near = TPWL(system.f, system.jac, delta=delta_train,
446-
weighting="nearest")
446+
weighting="nearest")
447447
model_near.train(x0_train, t_span, t_eval, **ivp_kw)
448448
model_near.print_summary()
449449

@@ -454,7 +454,7 @@ def run_demo(system, system_name, x0_train, x0_test, t_end,
454454

455455
# ---- 4. True nonlinear solution from test initial condition ----
456456
sol_true = solve_ivp(lambda t, x: system.f(x), t_span,
457-
x0_test, t_eval=t_eval, **ivp_kw)
457+
x0_test, t_eval=t_eval, **ivp_kw) # type: ignore
458458
assert sol_true.success, sol_true.message
459459

460460
# ---- 5. TPWL simulations from test IC ----
@@ -464,8 +464,8 @@ def run_demo(system, system_name, x0_train, x0_test, t_end,
464464
method="RK45", rtol=1e-8, atol=1e-10)
465465

466466
x_true = sol_true.y.T
467-
x_near = sol_near.y.T if sol_near.success else np.full_like(x_true, np.nan)
468-
x_gauss = sol_gauss.y.T if sol_gauss.success else np.full_like(x_true, np.nan)
467+
x_near = sol_near.y.T if sol_near.success else np.full_like(x_true, np.nan) # type: ignore
468+
x_gauss = sol_gauss.y.T if sol_gauss.success else np.full_like(x_true, np.nan) # type: ignore
469469

470470
err_near = np.linalg.norm(x_near - x_true, axis=1)
471471
err_gauss = np.linalg.norm(x_gauss - x_true, axis=1)
@@ -513,7 +513,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
513513
ivp_kw = dict(method="RK45", rtol=1e-9, atol=1e-11)
514514

515515
sol_true = solve_ivp(lambda t, x: system.f(x), t_span, x0_test,
516-
t_eval=t_eval, **ivp_kw)
516+
t_eval=t_eval, **ivp_kw) # type: ignore
517517
x_true = sol_true.y.T
518518

519519
results = []
@@ -522,7 +522,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
522522
m.train(x0_train, t_span, t_eval, **ivp_kw)
523523
sol = m.simulate(x0_test, t_span, t_eval, method="RK45",
524524
rtol=1e-8, atol=1e-10)
525-
x_approx = sol.y.T if sol.success else np.full_like(x_true, np.nan)
525+
x_approx = sol.y.T if sol.success else np.full_like(x_true, np.nan) # type: ignore
526526
err = np.linalg.norm(x_approx - x_true, axis=1).mean()
527527
results.append((delta, len(m.points), err))
528528
print(f" delta={delta:.3f}{len(m.points):3d} pts "
@@ -553,7 +553,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
553553

554554
fig.suptitle(f"TPWL: delta sensitivity — {system_name}",
555555
color="#e6edf3", fontsize=12, fontweight="bold")
556-
plt.tight_layout(rect=[0, 0, 1, 0.95])
556+
plt.tight_layout(rect=[0, 0, 1, 0.95]) # type: ignore
557557
if output_path:
558558
fig.savefig(output_path, dpi=150, bbox_inches="tight",
559559
facecolor="#0f1117")
@@ -578,7 +578,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
578578
delta_train=0.4,
579579
state_labels=["x₁", "x₂"],
580580
n_steps=800,
581-
output_path="/mnt/user-data/outputs/tpwl_vanderpol.png",
581+
output_path="tpwl_vanderpol.png",
582582
)
583583

584584
delta_sensitivity(
@@ -589,7 +589,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
589589
t_end=15.0,
590590
deltas=[2.0, 1.0, 0.6, 0.4, 0.25, 0.15, 0.08],
591591
state_labels=["x₁", "x₂"],
592-
output_path="/mnt/user-data/outputs/tpwl_delta_sensitivity.png",
592+
output_path="tpwl_delta_sensitivity.png",
593593
)
594594

595595
# ---- Example 2: Lorenz (chaotic) ----
@@ -603,7 +603,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
603603
delta_train=1.5,
604604
state_labels=["x", "y", "z"],
605605
n_steps=800,
606-
output_path="/mnt/user-data/outputs/tpwl_lorenz.png",
606+
output_path="tpwl_lorenz.png",
607607
)
608608

609609
# ---- Example 3: Duffing oscillator (lightly damped, double-well) ----
@@ -619,7 +619,7 @@ def delta_sensitivity(system, system_name, x0_train, x0_test,
619619
delta_train=0.35,
620620
state_labels=["x₁ (displacement)", "x₂ (velocity)"],
621621
n_steps=800,
622-
output_path="/mnt/user-data/outputs/tpwl_duffing.png",
622+
output_path="tpwl_duffing.png",
623623
)
624624

625-
print("\nAll done. Output files written to /mnt/user-data/outputs/")
625+
print("\nAll done. Output files written to user-data/outputs/")

src/crn_builder.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'''Builds a CRN from a system of Quadratic ODEs.'''
2+
3+
from collections import namedtuple
4+
import numpy as np # type: ignore
5+
import pandas as pd # type: ignore
6+
from typing import List, Optional, Tuple, Dict
7+
8+
9+
Reaction = namedtuple("Reaction", ["reactants", "products", "rate_constant",
10+
"monomial"])
11+
12+
13+
14+
15+
class CRNBuilder:
16+
'''Builds a CRN from a system of Quadratic ODEs.'''
17+
def __init__(self, system_df: pd.DataFrame) -> None:
18+
"""
19+
Args:
20+
system_df (pd.DataFrame): Output from NetworkDiscovery.summary()
21+
"""
22+
self.system_df = system_df
23+
self.species_names = [n[1:-3] for n in system_df.columns]
24+
self.system_df.columns = self.species_names
25+
self.monomials = self.system_df.index.tolist()
26+
27+
def build(self) -> List[Reaction]:
28+
'''Builds a CRN from the system_df.'''
29+
reactions: List[Reaction] = []
30+
for monomial in self.monomials:
31+
stoichiometry_dct : Dict[str, float] = {}
32+
# Find the smallest nonzero coefficient across species for this monomial, to use as the rate constant.
33+
values = self.system_df.loc[monomial, [sp for sp in self.species_names]].abs().values
34+
min_coeff = np.min(values[values != 0])
35+
# Check that all other coefficients are an integer multiple
36+
for sp in self.species_names:
37+
coeff = self.system_df.loc[monomial, f"d{sp}/dt"]
38+
stoichiometry_dct[sp] = coeff / min_coeff
39+
if coeff != 0 and not np.isclose(stoichiometry_dct[sp], round(stoichiometry_dct[sp])):
40+
raise ValueError(f"Coefficient {coeff} for species {sp} is not an integer multiple of the minimum coefficient {min_coeff} for monomial {monomial}.")
41+
# Construction the reaction. Negative coefficients indicate reactants;
42+
# positive coefficients indicate products.
43+
reactants = [sp for sp in self.species_names if self.system_df.loc[monomial, sp ] < 0]
44+
products = [sp for sp in self.species_names if float(self.system_df.loc[monomial, sp]) > 0]
45+
# Calculate stoichiometric coefficients as integer multiples of the minimum coefficient.
46+
reactions.append(Reaction(reactants, products, min_coeff, monomial))
47+
return reactions

0 commit comments

Comments
 (0)