PyAntiGen 1.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. framework/AntimonyGen.py +48 -0
  2. framework/RxnDict_to_antimony.py +594 -0
  3. framework/TelluriumGen.py +16 -0
  4. framework/__init__.py +0 -0
  5. framework/antimony_utils.py +294 -0
  6. framework/cli.py +229 -0
  7. framework/data_interpolation.py +340 -0
  8. framework/isotopomer_tools.py +41 -0
  9. framework/model_generation.py +46 -0
  10. framework/models.py +189 -0
  11. framework/module_base.py +42 -0
  12. framework/pyantigen.py +51 -0
  13. framework/rate_laws.py +101 -0
  14. framework/reaction_creation.py +43 -0
  15. framework/template/Example/AntiGen_paths.py +23 -0
  16. framework/template/Example/Engine/Anchor_cache.py +193 -0
  17. framework/template/Example/Engine/Deadline.py +535 -0
  18. framework/template/Example/Engine/Evaluator.py +1176 -0
  19. framework/template/Example/Engine/Event_times.py +491 -0
  20. framework/template/Example/Engine/Fast_profile.py +701 -0
  21. framework/template/Example/Engine/Fit_cache.py +329 -0
  22. framework/template/Example/Engine/Identifiability.py +698 -0
  23. framework/template/Example/Engine/Model_optimize.py +1483 -0
  24. framework/template/Example/Engine/Model_simulate.py +124 -0
  25. framework/template/Example/Engine/Nuisance_sensitivity.py +298 -0
  26. framework/template/Example/Engine/Optimize.py +6862 -0
  27. framework/template/Example/Engine/Petab_export.py +398 -0
  28. framework/template/Example/Engine/Preequil_cache.py +361 -0
  29. framework/template/Example/Engine/Profile_checkpoint.py +399 -0
  30. framework/template/Example/Engine/Results.py +395 -0
  31. framework/template/Example/Engine/Sensitivity_analysis.py +320 -0
  32. framework/template/Example/Engine/Simulate.py +617 -0
  33. framework/template/Example/Flipflop_reference.py +401 -0
  34. framework/template/Example/Model_generate.py +37 -0
  35. framework/template/Example/Model_run.py +261 -0
  36. framework/template/Example/Modules/Data.py +63 -0
  37. framework/template/Example/Modules/Events.py +14 -0
  38. framework/template/Example/Modules/Experiment.py +194 -0
  39. framework/template/Example/Modules/Loss_config.py +61 -0
  40. framework/template/Example/Modules/Observed_species.py +3 -0
  41. framework/template/Example/Modules/Optimizer_settings.py +258 -0
  42. framework/template/Example/Modules/Plots.py +89 -0
  43. framework/template/Example/Modules/Solver_settings.py +16 -0
  44. framework/template/Example/Modules/Update_opt_parameters.py +24 -0
  45. framework/template/Example/Modules/Update_parameters.py +49 -0
  46. framework/template/data/ADneg.csv +27 -0
  47. framework/template/data/ADpos.csv +27 -0
  48. framework/template/data/Flipflop.csv +29 -0
  49. framework/template/data/make_flipflop_data.py +174 -0
  50. pyantigen-1.0.9.dist-info/METADATA +129 -0
  51. pyantigen-1.0.9.dist-info/RECORD +55 -0
  52. pyantigen-1.0.9.dist-info/WHEEL +5 -0
  53. pyantigen-1.0.9.dist-info/entry_points.txt +2 -0
  54. pyantigen-1.0.9.dist-info/licenses/LICENSE +21 -0
  55. pyantigen-1.0.9.dist-info/top_level.txt +1 -0
@@ -0,0 +1,124 @@
1
+
2
+ import os
3
+ import sys
4
+ import pandas as pd
5
+ import AntiGen_paths
6
+
7
+ REPO_ROOT = AntiGen_paths.REPO_ROOT
8
+
9
+ from framework.AntimonyGen import AntimonyGen
10
+ from framework.TelluriumGen import TelluriumGen
11
+
12
+ from Modules.Experiment import *
13
+ from Modules.Plots import *
14
+ from Engine.Event_times import attach_event_times
15
+ from Engine.Simulate import simulate
16
+
17
+ def run_steady_state(model_text, paths, settings):
18
+
19
+ rss = TelluriumGen(model_text, paths, settings)
20
+ if settings["Verbose"]:
21
+ print("Steady state: ", rss.steadyState())
22
+ print("getFloatingSpeciesIds: ", rss.getFloatingSpeciesIds())
23
+ print("getBoundarySpeciesIds: ", rss.getBoundarySpeciesIds())
24
+ print("getAssignmentRuleIds: ", rss.getAssignmentRuleIds())
25
+
26
+
27
+ if os.path.exists(paths["models_path"]):
28
+ df_ic = pd.read_csv(paths["models_path"])
29
+ if 'Species' in df_ic.columns:
30
+ max_val = 0.0
31
+ vals = {}
32
+ for idx, row in df_ic.iterrows():
33
+ species = row['Species']
34
+ try:
35
+ # using roadrunner's dict-like access which is robust
36
+ val = rss[species]
37
+ vals[idx] = val
38
+ if val > max_val:
39
+ max_val = val
40
+ except RuntimeError:
41
+ pass
42
+ for idx, val in vals.items():
43
+ if val < 1e-10 * max_val:
44
+ val = 0.0
45
+ df_ic.at[idx, 'InitialCondition'] = val
46
+ df_ic.to_csv(paths["models_path"], index=False)
47
+ print(f"Updated InitialConditions in {paths['models_path']}")
48
+ else:
49
+ print(f"No 'Species' column found in {paths['models_path']}")
50
+ return
51
+ else:
52
+ print(f"No InitialConditions file found in {paths['ic_path']}")
53
+ return
54
+
55
+ Species = rss.getFloatingSpeciesIds()
56
+ for s in Species:
57
+ print(s, rss.getValue(s))
58
+
59
+
60
+ def run_simulation(model_text, paths, settings, EXPERIMENT_dict, parameter_overrides=None):
61
+
62
+ data_path = paths["data_path"]
63
+ repo_root = paths["repo_root"]
64
+ MODEL_NAME = paths["MODEL_NAME"]
65
+ print("run_simulation", MODEL_NAME)
66
+ save_path = os.path.join(repo_root, "generated", MODEL_NAME, MODEL_NAME + "_events.txt")
67
+
68
+ results_dict = {}
69
+ experiment = EXPERIMENT_dict['EXPERIMENT']
70
+ for label, replicate in experiment.replicates.items():
71
+ print("Label", label)
72
+ df_dict = replicate["Data"](replicate, data_path)
73
+ events = replicate["Events"](replicate,df_dict)
74
+
75
+ with open(save_path, "w") as f:
76
+ f.write(events)
77
+
78
+ full_model_text = model_text + "\n" + events
79
+
80
+ r = TelluriumGen(full_model_text, paths, settings)
81
+
82
+ replicate["Update_parameters"](r, replicate)
83
+ if parameter_overrides:
84
+ for p_name, p_val in parameter_overrides.items():
85
+ try:
86
+ r[p_name] = p_val
87
+ except Exception:
88
+ pass
89
+
90
+ # After the overrides: a trigger built on an overridden parameter has to
91
+ # resolve against the value this run will actually integrate with.
92
+ attach_event_times(replicate, r, verbose=True)
93
+
94
+ solver_settings = replicate["Solver_settings"](replicate)
95
+ observed_species = replicate["Observed_species"](r)
96
+ results = simulate(r, solver_settings, observed_species)
97
+
98
+
99
+ results_dict[replicate["Label"]] = {
100
+ "results": results,
101
+ "replicate": replicate,
102
+ "data": df_dict,
103
+ "observed_species": observed_species,
104
+ "solver_settings": solver_settings,
105
+ "events": events
106
+ }
107
+ EXPERIMENT_dict["plot"](paths,results_dict)
108
+ return results_dict
109
+
110
+ def setup_simulation(settings, EXPERIMENT_dict, parameter_overrides=None):
111
+ if settings.get("MODEL_NAME"):
112
+ MODEL_NAME = settings["MODEL_NAME"]
113
+ else:
114
+ MODEL_NAME = AntiGen_paths.MODEL_NAME
115
+ print("setup_simulation", MODEL_NAME)
116
+
117
+ model_text, paths = AntimonyGen(MODEL_NAME, repo_root=REPO_ROOT)
118
+
119
+ if settings["run_steady_state_first"]:
120
+ run_steady_state(model_text, paths, settings)
121
+
122
+ results_dict = run_simulation(model_text, paths, settings, EXPERIMENT_dict, parameter_overrides=parameter_overrides)
123
+
124
+
@@ -0,0 +1,298 @@
1
+ """
2
+ Nuisance parameter sensitivity analysis via Latin Hypercube Sampling.
3
+
4
+ Forward-only (no re-optimisation): hold identifiable params at their current
5
+ optimal values, vary the non-identifiable nuisance params across their plausible
6
+ range, and collect the resulting plasma-PK trajectories.
7
+
8
+ The compile-once / reset-reuse pattern mirrors Engine/Optimize.py:run_all().
9
+ r.reset() restores species to initial conditions; parameter values set by
10
+ Update_parameters persist, so only the nuisance overrides need to be applied
11
+ each iteration.
12
+ """
13
+
14
+ import os
15
+ import numpy as np
16
+ import AntiGen_paths
17
+
18
+ REPO_ROOT = AntiGen_paths.REPO_ROOT
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # LHS sampling
23
+ # ---------------------------------------------------------------------------
24
+
25
+ def _lhs_sample(nuisance_settings, n_samples, seed=42):
26
+ """
27
+ Return (param_names, samples) where samples has shape (n_samples, n_params).
28
+ FR is sampled uniformly; all other params log-uniformly.
29
+ """
30
+ from scipy.stats.qmc import LatinHypercube
31
+
32
+ names = list(nuisance_settings.keys())
33
+ sampler = LatinHypercube(d=len(names), seed=seed)
34
+ unit = sampler.random(n=n_samples) # (n_samples, n_params) in [0, 1]
35
+
36
+ samples = np.zeros_like(unit)
37
+ for i, name in enumerate(names):
38
+ lo, hi = nuisance_settings[name]['bounds']
39
+ if name == 'FR':
40
+ samples[:, i] = lo + unit[:, i] * (hi - lo)
41
+ else:
42
+ samples[:, i] = 10 ** (
43
+ np.log10(lo) + unit[:, i] * (np.log10(hi) - np.log10(lo))
44
+ )
45
+ return names, samples
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # PRCC
50
+ # ---------------------------------------------------------------------------
51
+
52
+ def _compute_prcc(X, y):
53
+ """
54
+ Partial rank correlation coefficient of each column of X against y.
55
+ Removes linear effects of all other columns before computing correlation.
56
+ """
57
+ from scipy.stats import rankdata, pearsonr
58
+
59
+ n, p = X.shape
60
+ X_r = np.apply_along_axis(rankdata, 0, X).astype(float)
61
+ y_r = rankdata(y).astype(float)
62
+
63
+ prcc = np.zeros(p)
64
+ for i in range(p):
65
+ other = np.delete(X_r, i, axis=1)
66
+ A = np.column_stack([np.ones(n), other])
67
+ cx = np.linalg.lstsq(A, X_r[:, i], rcond=None)[0]
68
+ cy = np.linalg.lstsq(A, y_r, rcond=None)[0]
69
+ prcc[i], _ = pearsonr(X_r[:, i] - A @ cx, y_r - A @ cy)
70
+ return prcc
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Total plasma antibody helper
75
+ # ---------------------------------------------------------------------------
76
+
77
+ def _total_plasma(results):
78
+ """Sum free + all Aβ-bound Antibody_Plasma species."""
79
+ from Modules.utils.centiloid_utils import get_column_index
80
+
81
+ total = None
82
+ for col in ('[Antibody_Plasma]',
83
+ '[AB38__Antibody_Plasma]',
84
+ '[AB40__Antibody_Plasma]',
85
+ '[AB42__Antibody_Plasma]'):
86
+ idx = get_column_index(results, col)
87
+ if idx is not None:
88
+ conc = results[:, idx]
89
+ total = conc if total is None else total + conc
90
+ return total
91
+
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # Target value helper
95
+ # ---------------------------------------------------------------------------
96
+
97
+ def _get_target_values(results, observable_type, placebo_ratio=None):
98
+ """Extract target concentration/values from results based on observable_type."""
99
+ from Modules.utils.centiloid_utils import get_column_index, calculate_centiloids
100
+
101
+ if observable_type == 'plasma_ab':
102
+ return _total_plasma(results)
103
+ elif observable_type == 'centiloid':
104
+ if placebo_ratio is None:
105
+ from Modules.utils.centiloid_utils import calculate_dense_ratio_77
106
+ placebo_ratio = calculate_dense_ratio_77()
107
+ return calculate_centiloids(results, placebo_ratio)
108
+ else:
109
+ # Generic column name
110
+ idx = get_column_index(results, observable_type)
111
+ if idx is not None:
112
+ return results[:, idx]
113
+ # Try with bracket notation
114
+ idx = get_column_index(results, f'[{observable_type}]')
115
+ if idx is not None:
116
+ return results[:, idx]
117
+ return None
118
+
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # Main entry point
122
+ # ---------------------------------------------------------------------------
123
+
124
+ def run_nuisance_sensitivity(settings, nuisance_param_settings, EXPERIMENT_dict,
125
+ n_samples=100):
126
+ """
127
+ LHS forward-only nuisance parameter sensitivity analysis.
128
+
129
+ For each drug in nuisance_param_settings:
130
+ 1. Compile one RoadRunner model per replicate (once).
131
+ 2. Run nominal simulation (params already set by Update_parameters).
132
+ 3. For each of n_samples LHS combinations: reset + override nuisance
133
+ params + simulate.
134
+ 4. Compute PRCC of each nuisance param against mean AUC across replicates.
135
+
136
+ Returns
137
+ -------
138
+ output : dict keyed by drug_name
139
+ param_names, samples, prcc, and per-replicate nominal/spaghetti arrays.
140
+ paths : dict
141
+ Paths dict from AntimonyGen (needed by plot functions).
142
+ """
143
+ from framework.AntimonyGen import AntimonyGen
144
+ from framework.TelluriumGen import TelluriumGen
145
+ from Engine.Simulate import simulate
146
+
147
+ MODEL_NAME = settings.get('MODEL_NAME', AntiGen_paths.MODEL_NAME)
148
+ model_text, paths = AntimonyGen(MODEL_NAME, repo_root=REPO_ROOT)
149
+ data_path = paths['data_path']
150
+ save_dir = os.path.join(paths['repo_root'], 'generated', MODEL_NAME)
151
+ save_path = os.path.join(save_dir, MODEL_NAME + '_events.txt')
152
+ os.makedirs(save_dir, exist_ok=True)
153
+
154
+ experiment = EXPERIMENT_dict['EXPERIMENT']
155
+ output = {}
156
+
157
+ # Check for metadata structure in settings
158
+ if 'params' in nuisance_param_settings:
159
+ drug_settings_dict = nuisance_param_settings['params']
160
+ meta = nuisance_param_settings
161
+ else:
162
+ drug_settings_dict = nuisance_param_settings
163
+ meta = {}
164
+
165
+ # Treat a flat parameter dict as a single nuisance case if no drug names match.
166
+ single_global_nuisance = False
167
+ if isinstance(drug_settings_dict, dict):
168
+ rep_drugs = {rep.get('Drug') for rep in experiment.replicates.values() if rep.get('Drug')}
169
+ is_param_dict = (
170
+ drug_settings_dict
171
+ and all(
172
+ isinstance(v, dict) and 'x0' in v and 'bounds' in v
173
+ for v in drug_settings_dict.values()
174
+ )
175
+ )
176
+ if is_param_dict and not (rep_drugs & set(drug_settings_dict.keys())):
177
+ drug_settings_dict = {'Nuisance': drug_settings_dict}
178
+ single_global_nuisance = True
179
+
180
+ observable_type = meta.get('target_observable', 'plasma_ab')
181
+ summary_metric = meta.get('summary_metric', 'auc')
182
+
183
+ placebo_ratio = None
184
+ if observable_type == 'centiloid':
185
+ from Modules.utils.centiloid_utils import calculate_dense_ratio_77
186
+ placebo_ratio = calculate_dense_ratio_77()
187
+
188
+ for drug_name, nuisance_settings in drug_settings_dict.items():
189
+ if single_global_nuisance:
190
+ drug_reps = {
191
+ lbl: rep for lbl, rep in experiment.replicates.items()
192
+ if rep.get('Drug') is not None
193
+ }
194
+ else:
195
+ drug_reps = {
196
+ lbl: rep for lbl, rep in experiment.replicates.items()
197
+ if rep.get('Drug') == drug_name
198
+ }
199
+ if not drug_reps:
200
+ continue
201
+
202
+ param_names, samples = _lhs_sample(nuisance_settings, n_samples)
203
+ print(f"\n[nuisance] {drug_name}: "
204
+ f"{len(drug_reps)} replicate(s) × {n_samples} LHS samples "
205
+ f"({len(param_names)} nuisance params)")
206
+
207
+ # ── Compile one model per replicate ──────────────────────────────────
208
+ compiled = {}
209
+ for lbl, rep in drug_reps.items():
210
+ df_dict = rep['Data'](rep, data_path)
211
+ events = rep['Events'](rep, df_dict)
212
+ with open(save_path, 'w') as f:
213
+ f.write(events)
214
+ r = TelluriumGen(model_text + '\n' + events, paths)
215
+ rep['Update_parameters'](r, rep)
216
+ compiled[lbl] = {
217
+ 'r': r,
218
+ 'df_dict': df_dict,
219
+ 'solver_settings': rep['Solver_settings'](rep),
220
+ 'observed': rep['Observed_species'](r),
221
+ 'age': rep['Age'],
222
+ 'label': lbl,
223
+ }
224
+
225
+ # ── Nominal trajectories (params already set by Update_parameters) ───
226
+ nominal = {}
227
+ for lbl, c in compiled.items():
228
+ c['r'].reset()
229
+ rep = drug_reps[lbl]
230
+ rep['Update_parameters'](c['r'], rep)
231
+ nominal[lbl] = simulate(c['r'], c['solver_settings'], c['observed'], label=lbl)
232
+
233
+ # ── LHS forward simulations ──────────────────────────────────────────
234
+ spaghetti = {lbl: [] for lbl in drug_reps}
235
+ auc_matrix = np.zeros((n_samples, len(drug_reps)))
236
+
237
+ for s_idx, sample in enumerate(samples):
238
+ override = dict(zip(param_names, sample))
239
+ for rep_idx, (lbl, c) in enumerate(compiled.items()):
240
+ r = c['r']
241
+ r.reset()
242
+ rep = drug_reps[lbl]
243
+ rep['Update_parameters'](r, rep)
244
+ param_ids = r.getGlobalParameterIds()
245
+ for pname, pval in override.items():
246
+ if pname in param_ids:
247
+ r[pname] = pval
248
+ try:
249
+ results = simulate(r, c['solver_settings'], c['observed'], label=lbl)
250
+ except Exception as exc:
251
+ print(f" [nuisance] sample {s_idx} {lbl} failed: {exc}")
252
+ spaghetti[lbl].append(None)
253
+ continue
254
+
255
+ spaghetti[lbl].append(results)
256
+
257
+ conc = _get_target_values(results, observable_type, placebo_ratio)
258
+ if conc is not None:
259
+ if summary_metric == 'auc':
260
+ time = np.asarray(results['time'])
261
+ t_event = c['age'] * 365 * 24
262
+ mask = time >= t_event
263
+ if mask.sum() > 1:
264
+ auc_matrix[s_idx, rep_idx] = np.trapezoid(
265
+ conc[mask], time[mask]
266
+ )
267
+ elif summary_metric == 'final':
268
+ auc_matrix[s_idx, rep_idx] = conc[-1]
269
+
270
+ # ── PRCC ─────────────────────────────────────────────────────────────
271
+ mean_auc = auc_matrix.mean(axis=1)
272
+ prcc = (
273
+ _compute_prcc(samples, mean_auc)
274
+ if mean_auc.std() > 0
275
+ else np.zeros(len(param_names))
276
+ )
277
+
278
+ print(f" PRCC ({summary_metric.upper()} of {observable_type}):")
279
+ for pn, prc in zip(param_names, prcc):
280
+ print(f" {pn:<35} {prc:+.3f}")
281
+
282
+ output[drug_name] = {
283
+ 'param_names': param_names,
284
+ 'samples': samples,
285
+ 'prcc': prcc,
286
+ 'meta': meta,
287
+ 'replicates': {
288
+ lbl: {
289
+ 'nominal': nominal[lbl],
290
+ 'spaghetti': spaghetti[lbl],
291
+ 'auc_samples': auc_matrix[:, i],
292
+ 'age': compiled[lbl]['age'],
293
+ }
294
+ for i, lbl in enumerate(drug_reps)
295
+ },
296
+ }
297
+
298
+ return output, paths