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.
- framework/AntimonyGen.py +48 -0
- framework/RxnDict_to_antimony.py +594 -0
- framework/TelluriumGen.py +16 -0
- framework/__init__.py +0 -0
- framework/antimony_utils.py +294 -0
- framework/cli.py +229 -0
- framework/data_interpolation.py +340 -0
- framework/isotopomer_tools.py +41 -0
- framework/model_generation.py +46 -0
- framework/models.py +189 -0
- framework/module_base.py +42 -0
- framework/pyantigen.py +51 -0
- framework/rate_laws.py +101 -0
- framework/reaction_creation.py +43 -0
- framework/template/Example/AntiGen_paths.py +23 -0
- framework/template/Example/Engine/Anchor_cache.py +193 -0
- framework/template/Example/Engine/Deadline.py +535 -0
- framework/template/Example/Engine/Evaluator.py +1176 -0
- framework/template/Example/Engine/Event_times.py +491 -0
- framework/template/Example/Engine/Fast_profile.py +701 -0
- framework/template/Example/Engine/Fit_cache.py +329 -0
- framework/template/Example/Engine/Identifiability.py +698 -0
- framework/template/Example/Engine/Model_optimize.py +1483 -0
- framework/template/Example/Engine/Model_simulate.py +124 -0
- framework/template/Example/Engine/Nuisance_sensitivity.py +298 -0
- framework/template/Example/Engine/Optimize.py +6862 -0
- framework/template/Example/Engine/Petab_export.py +398 -0
- framework/template/Example/Engine/Preequil_cache.py +361 -0
- framework/template/Example/Engine/Profile_checkpoint.py +399 -0
- framework/template/Example/Engine/Results.py +395 -0
- framework/template/Example/Engine/Sensitivity_analysis.py +320 -0
- framework/template/Example/Engine/Simulate.py +617 -0
- framework/template/Example/Flipflop_reference.py +401 -0
- framework/template/Example/Model_generate.py +37 -0
- framework/template/Example/Model_run.py +261 -0
- framework/template/Example/Modules/Data.py +63 -0
- framework/template/Example/Modules/Events.py +14 -0
- framework/template/Example/Modules/Experiment.py +194 -0
- framework/template/Example/Modules/Loss_config.py +61 -0
- framework/template/Example/Modules/Observed_species.py +3 -0
- framework/template/Example/Modules/Optimizer_settings.py +258 -0
- framework/template/Example/Modules/Plots.py +89 -0
- framework/template/Example/Modules/Solver_settings.py +16 -0
- framework/template/Example/Modules/Update_opt_parameters.py +24 -0
- framework/template/Example/Modules/Update_parameters.py +49 -0
- framework/template/data/ADneg.csv +27 -0
- framework/template/data/ADpos.csv +27 -0
- framework/template/data/Flipflop.csv +29 -0
- framework/template/data/make_flipflop_data.py +174 -0
- pyantigen-1.0.9.dist-info/METADATA +129 -0
- pyantigen-1.0.9.dist-info/RECORD +55 -0
- pyantigen-1.0.9.dist-info/WHEEL +5 -0
- pyantigen-1.0.9.dist-info/entry_points.txt +2 -0
- pyantigen-1.0.9.dist-info/licenses/LICENSE +21 -0
- pyantigen-1.0.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from framework.RxnDict_to_antimony import generate_antimony_from_txt, write_list_to_file
|
|
8
|
+
|
|
9
|
+
# Default project settings (used when pyantigen_settings.json is missing or incomplete)
|
|
10
|
+
DEFAULT_SETTINGS = {
|
|
11
|
+
"archive_with_timestamp": False,
|
|
12
|
+
}
|
|
13
|
+
SETTINGS_FILENAME = "pyantigen_settings.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_project_settings(project_root):
|
|
17
|
+
"""
|
|
18
|
+
Load PyAntiGen project settings from project_root/pyantigen_settings.json.
|
|
19
|
+
Missing or invalid keys fall back to DEFAULT_SETTINGS.
|
|
20
|
+
"""
|
|
21
|
+
path = os.path.join(os.path.abspath(project_root), SETTINGS_FILENAME)
|
|
22
|
+
settings = dict(DEFAULT_SETTINGS)
|
|
23
|
+
if not os.path.isfile(path):
|
|
24
|
+
return settings
|
|
25
|
+
try:
|
|
26
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
27
|
+
data = json.load(f)
|
|
28
|
+
if isinstance(data, dict):
|
|
29
|
+
for key in DEFAULT_SETTINGS:
|
|
30
|
+
if key in data:
|
|
31
|
+
settings[key] = data[key]
|
|
32
|
+
except (json.JSONDecodeError, OSError):
|
|
33
|
+
pass
|
|
34
|
+
return settings
|
|
35
|
+
|
|
36
|
+
# Filenames (without path) that load_antimony_files expects, in concatenation order
|
|
37
|
+
ANTIMONY_FILE_NAMES = [
|
|
38
|
+
"_reactions.txt",
|
|
39
|
+
"_parameters.csv",
|
|
40
|
+
"_InitialConditions.csv",
|
|
41
|
+
"_manual.txt",
|
|
42
|
+
"_rules.txt",
|
|
43
|
+
"_events.txt",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _ensure_file_in_antimony_models(model_name, base_name, antimony_models_dir, generated_dir):
|
|
48
|
+
"""
|
|
49
|
+
If the file does not exist in antimony_models, copy it from generated.
|
|
50
|
+
Never overwrite an existing file in antimony_models.
|
|
51
|
+
"""
|
|
52
|
+
dest = os.path.join(antimony_models_dir, f"{model_name}{base_name}")
|
|
53
|
+
if os.path.isfile(dest):
|
|
54
|
+
return
|
|
55
|
+
src = os.path.join(generated_dir, f"{model_name}{base_name}")
|
|
56
|
+
if not os.path.isfile(src):
|
|
57
|
+
return
|
|
58
|
+
os.makedirs(antimony_models_dir, exist_ok=True)
|
|
59
|
+
shutil.copy2(src, dest)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _csv_to_antimony_parameters(csv_path):
|
|
63
|
+
"""Read parameters CSV and return Antimony lines: Parameter = Value # Units Comment."""
|
|
64
|
+
lines = []
|
|
65
|
+
try:
|
|
66
|
+
with open(csv_path, "r", newline="", encoding="utf-8-sig") as f:
|
|
67
|
+
reader = csv.DictReader(f)
|
|
68
|
+
for row in reader:
|
|
69
|
+
param = (row.get("Parameter") or "").strip()
|
|
70
|
+
if not param:
|
|
71
|
+
continue
|
|
72
|
+
val = (row.get("Value") or "").strip()
|
|
73
|
+
units = (row.get("Units") or "").strip()
|
|
74
|
+
comment = (row.get("Comment") or "").strip()
|
|
75
|
+
if val == "var":
|
|
76
|
+
line = f"var {param}"
|
|
77
|
+
else:
|
|
78
|
+
line = f"{param} = {val if val else '0'}"
|
|
79
|
+
|
|
80
|
+
if units:
|
|
81
|
+
line += f" // [{units}]"
|
|
82
|
+
if comment:
|
|
83
|
+
line += f" # {comment}"
|
|
84
|
+
lines.append(line)
|
|
85
|
+
except FileNotFoundError:
|
|
86
|
+
pass
|
|
87
|
+
return "\n".join(lines)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _csv_to_antimony_initial_conditions(csv_path):
|
|
91
|
+
"""Read InitialConditions CSV and return Antimony lines: Species = InitialCondition # Units Comment."""
|
|
92
|
+
lines = []
|
|
93
|
+
try:
|
|
94
|
+
with open(csv_path, "r", newline="", encoding="utf-8-sig") as f:
|
|
95
|
+
reader = csv.DictReader(f)
|
|
96
|
+
for row in reader:
|
|
97
|
+
species = (row.get("Species") or "").strip()
|
|
98
|
+
if not species:
|
|
99
|
+
continue
|
|
100
|
+
ic = (row.get("InitialCondition") or "").strip() or "0"
|
|
101
|
+
units = (row.get("Units") or "").strip()
|
|
102
|
+
comment = (row.get("Comment") or "").strip()
|
|
103
|
+
line = f"{species} = {ic}"
|
|
104
|
+
if units or comment:
|
|
105
|
+
line += f" # {units} {comment}".strip()
|
|
106
|
+
lines.append(line)
|
|
107
|
+
except FileNotFoundError:
|
|
108
|
+
pass
|
|
109
|
+
return "\n".join(lines)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def load_antimony_files(model_name, project_root):
|
|
113
|
+
"""
|
|
114
|
+
Load and concatenate Antimony model files from antimony_models.
|
|
115
|
+
|
|
116
|
+
For each of the standard files ({model_name}_reactions.txt, _parameters.csv,
|
|
117
|
+
_InitialConditions.csv, _manual.txt, _rules.txt, _events.txt), look first in
|
|
118
|
+
antimony_models. If a file is missing there, copy it from generated (without
|
|
119
|
+
overwriting if it already exists in antimony_models). Then read from
|
|
120
|
+
antimony_models and concatenate in order to form the full model text.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
model_name (str): Base name of the model (may differ from the run script name).
|
|
124
|
+
project_root (str): Project directory containing antimony_models and generated.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
str: Concatenated Antimony model text for Tellurium.
|
|
128
|
+
"""
|
|
129
|
+
project_root = os.path.abspath(project_root)
|
|
130
|
+
# Files for a model live in subfolders named by model_name
|
|
131
|
+
antimony_models_dir = os.path.join(project_root, "antimony_models", model_name)
|
|
132
|
+
generated_dir = os.path.join(project_root, "generated", model_name)
|
|
133
|
+
|
|
134
|
+
for base_name in ANTIMONY_FILE_NAMES:
|
|
135
|
+
_ensure_file_in_antimony_models(model_name, base_name, antimony_models_dir, generated_dir)
|
|
136
|
+
|
|
137
|
+
parts = []
|
|
138
|
+
for base_name in ANTIMONY_FILE_NAMES:
|
|
139
|
+
path = os.path.join(antimony_models_dir, f"{model_name}{base_name}")
|
|
140
|
+
if not os.path.isfile(path):
|
|
141
|
+
continue
|
|
142
|
+
if base_name == "_parameters.csv":
|
|
143
|
+
parts.append(_csv_to_antimony_parameters(path))
|
|
144
|
+
elif base_name == "_InitialConditions.csv":
|
|
145
|
+
parts.append(_csv_to_antimony_initial_conditions(path))
|
|
146
|
+
else:
|
|
147
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
148
|
+
parts.append(f.read().rstrip())
|
|
149
|
+
|
|
150
|
+
return "\n".join(p for p in parts if p.strip())
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def archive_antimony_snapshot(model_name, project_root, sbml_content=None):
|
|
154
|
+
"""
|
|
155
|
+
Archive the six Antimony source files and optional SBML into SBML_models/MODEL_NAME/.
|
|
156
|
+
|
|
157
|
+
By default writes to SBML_models/model_name/ (overwrites each run). If the project
|
|
158
|
+
setting archive_with_timestamp is true in pyantigen_settings.json, uses a
|
|
159
|
+
timestamped subfolder (e.g. 2025-03-14_15-30-22) so each run is kept.
|
|
160
|
+
|
|
161
|
+
Copies reactions, parameters, InitialConditions, manual, rules, and events from
|
|
162
|
+
antimony_models with an "_archive" suffix on each filename, and writes
|
|
163
|
+
model_name.xml there if sbml_content is provided.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
model_name (str): Base name of the model.
|
|
167
|
+
project_root (str): Project directory containing antimony_models and SBML_models.
|
|
168
|
+
sbml_content (str, optional): SBML XML string to write as model_name.xml in the archive.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
str: Path to the archive folder created.
|
|
172
|
+
"""
|
|
173
|
+
project_root = os.path.abspath(project_root)
|
|
174
|
+
settings = load_project_settings(project_root)
|
|
175
|
+
antimony_models_model_dir = os.path.join(project_root, "antimony_models", model_name)
|
|
176
|
+
sbml_models_dir = os.path.join(project_root, "SBML_models")
|
|
177
|
+
model_archive_base = os.path.join(sbml_models_dir, model_name)
|
|
178
|
+
if settings.get("archive_with_timestamp", False):
|
|
179
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
180
|
+
archive_dir = os.path.join(model_archive_base, timestamp)
|
|
181
|
+
else:
|
|
182
|
+
archive_dir = model_archive_base
|
|
183
|
+
os.makedirs(archive_dir, exist_ok=True)
|
|
184
|
+
|
|
185
|
+
for base_name in ANTIMONY_FILE_NAMES:
|
|
186
|
+
src = os.path.join(antimony_models_model_dir, f"{model_name}{base_name}")
|
|
187
|
+
if not os.path.isfile(src):
|
|
188
|
+
continue
|
|
189
|
+
# Add _archive before extension: e.g. _reactions.txt -> _reactions_archive.txt
|
|
190
|
+
stem, ext = os.path.splitext(base_name)
|
|
191
|
+
archive_basename = f"{model_name}{stem}_archive{ext}"
|
|
192
|
+
dest = os.path.join(archive_dir, archive_basename)
|
|
193
|
+
shutil.copy2(src, dest)
|
|
194
|
+
|
|
195
|
+
if sbml_content is not None:
|
|
196
|
+
sbml_path = os.path.join(archive_dir, f"{model_name}.xml")
|
|
197
|
+
with open(sbml_path, "w", encoding="utf-8") as f:
|
|
198
|
+
f.write(sbml_content)
|
|
199
|
+
|
|
200
|
+
return archive_dir
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def convert_to_antimony(model_path, name, rules_path, output_dir=None):
|
|
204
|
+
"""
|
|
205
|
+
Convert the generated reaction file to Antimony format.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
model_path (str): Path to the generated reaction text file
|
|
209
|
+
name (str): Name to use for output files
|
|
210
|
+
rules_path (str): Path to the rules file (optional)
|
|
211
|
+
output_dir (str): Base directory for outputs (defaults to parent of this file)
|
|
212
|
+
"""
|
|
213
|
+
if output_dir is None:
|
|
214
|
+
output_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
215
|
+
else:
|
|
216
|
+
output_dir = os.path.abspath(output_dir)
|
|
217
|
+
|
|
218
|
+
# Resolve model path so the reaction file is found regardless of cwd
|
|
219
|
+
model_path = os.path.abspath(model_path)
|
|
220
|
+
|
|
221
|
+
# Generate Antimony script from text file
|
|
222
|
+
complete_script, species, parameters, unique_compartments, errors = generate_antimony_from_txt(model_path, name)
|
|
223
|
+
|
|
224
|
+
# Group all outputs by model name into subfolders
|
|
225
|
+
generated_dir = os.path.join(output_dir, "generated", name)
|
|
226
|
+
os.makedirs(generated_dir, exist_ok=True)
|
|
227
|
+
antimony_models_dir = os.path.join(output_dir, "antimony_models", name)
|
|
228
|
+
os.makedirs(antimony_models_dir, exist_ok=True)
|
|
229
|
+
|
|
230
|
+
# Write _reactions.txt to both antimony_models and generated
|
|
231
|
+
reactions_basename = f"{name}_reactions.txt"
|
|
232
|
+
for folder in (antimony_models_dir, generated_dir):
|
|
233
|
+
path = os.path.join(folder, reactions_basename)
|
|
234
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
235
|
+
f.write(complete_script)
|
|
236
|
+
|
|
237
|
+
# Write unique compartments/species/parameters to generated (model_name before unique_*)
|
|
238
|
+
compartments_filename = os.path.join(generated_dir, f'{name}_unique_compartments.txt')
|
|
239
|
+
with open(compartments_filename, "w", encoding="utf-8") as f:
|
|
240
|
+
for compartment in sorted(unique_compartments):
|
|
241
|
+
f.write(f"{compartment}\n")
|
|
242
|
+
|
|
243
|
+
species_filename = os.path.join(generated_dir, f'{name}_unique_species.txt')
|
|
244
|
+
parameters_list_filename = os.path.join(generated_dir, f'{name}_unique_parameters.txt')
|
|
245
|
+
write_list_to_file(species, species_filename)
|
|
246
|
+
write_list_to_file(parameters, parameters_list_filename)
|
|
247
|
+
|
|
248
|
+
# Write generated-only CSV and empty files (only in generated; user may copy to antimony_models)
|
|
249
|
+
init_cond_path = os.path.join(generated_dir, f"{name}_InitialConditions.csv")
|
|
250
|
+
with open(init_cond_path, "w", encoding="utf-8", newline="") as f:
|
|
251
|
+
writer = csv.DictWriter(f, fieldnames=["Species", "InitialCondition", "Units", "Comment"])
|
|
252
|
+
writer.writeheader()
|
|
253
|
+
for s in sorted(species):
|
|
254
|
+
writer.writerow({"Species": s, "InitialCondition": "0", "Units": "", "Comment": ""})
|
|
255
|
+
|
|
256
|
+
params_csv_path = os.path.join(generated_dir, f"{name}_parameters.csv")
|
|
257
|
+
with open(params_csv_path, "w", encoding="utf-8", newline="") as f:
|
|
258
|
+
writer = csv.DictWriter(f, fieldnames=["Parameter", "Value", "Units", "Comment"])
|
|
259
|
+
writer.writeheader()
|
|
260
|
+
for p in sorted(parameters):
|
|
261
|
+
writer.writerow({"Parameter": p, "Value": "", "Units": "", "Comment": ""})
|
|
262
|
+
|
|
263
|
+
for empty_basename in (f"{name}_events.txt", f"{name}_manual.txt"):
|
|
264
|
+
empty_path = os.path.join(generated_dir, empty_basename)
|
|
265
|
+
with open(empty_path, "w", encoding="utf-8") as f:
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
# Write errors to file
|
|
269
|
+
errors_filename = os.path.join(generated_dir, f'conversion_errors_{name}.log')
|
|
270
|
+
with open(errors_filename, "w", encoding="utf-8") as f:
|
|
271
|
+
for error in errors:
|
|
272
|
+
f.write(f"{error}\n")
|
|
273
|
+
|
|
274
|
+
# Read rules file and write to both antimony_models and generated
|
|
275
|
+
if rules_path is not None and os.path.isfile(rules_path):
|
|
276
|
+
with open(rules_path, "r", encoding="utf-8") as f:
|
|
277
|
+
rules_content = f.read()
|
|
278
|
+
rules_basename = f"{name}_rules.txt"
|
|
279
|
+
for folder in (antimony_models_dir, generated_dir):
|
|
280
|
+
rules_output_path = os.path.join(folder, rules_basename)
|
|
281
|
+
with open(rules_output_path, "w", encoding="utf-8") as f:
|
|
282
|
+
f.write(rules_content)
|
|
283
|
+
print(f" - Rules file written to antimony_models and generated")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# Print summary
|
|
287
|
+
print(f"\nAntimony conversion complete:")
|
|
288
|
+
print(f" - Found {len(unique_compartments)} unique compartments")
|
|
289
|
+
print(f" - Found {len(species)} unique species and {len(parameters)} unique parameters")
|
|
290
|
+
print(f" - Antimony reactions written to antimony_models and generated")
|
|
291
|
+
if errors:
|
|
292
|
+
print(f" - Found {len(errors)} errors (written to '{errors_filename}')")
|
|
293
|
+
else:
|
|
294
|
+
print(f" - No errors found during conversion")
|
framework/cli.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
def create_project():
|
|
8
|
+
parser = argparse.ArgumentParser(description="Initialize a new PyAntiGen project structure.")
|
|
9
|
+
parser.add_argument("project_name", help="Name of the directory to create for the new project")
|
|
10
|
+
args = parser.parse_args()
|
|
11
|
+
|
|
12
|
+
project_dir = args.project_name
|
|
13
|
+
|
|
14
|
+
if os.path.exists(project_dir):
|
|
15
|
+
print(f"Error: Directory '{project_dir}' already exists.")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
# Directories to scaffold (top-level .agents for Cursor/agent skills)
|
|
19
|
+
directories = [
|
|
20
|
+
"Projects",
|
|
21
|
+
"antimony_modules",
|
|
22
|
+
"antimony_modules/Basic",
|
|
23
|
+
"data",
|
|
24
|
+
"antimony_models",
|
|
25
|
+
"generated",
|
|
26
|
+
"results",
|
|
27
|
+
"SBML_models",
|
|
28
|
+
".agents",
|
|
29
|
+
".agents/skills",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
print(f"Creating PyAntiGen project '{project_dir}'...")
|
|
33
|
+
|
|
34
|
+
# Create directories
|
|
35
|
+
for d in directories:
|
|
36
|
+
path = os.path.join(project_dir, d)
|
|
37
|
+
os.makedirs(path, exist_ok=True)
|
|
38
|
+
# Create an __init__.py in antimony_modules so it's a python package
|
|
39
|
+
if d == "antimony_modules":
|
|
40
|
+
with open(os.path.join(path, "__init__.py"), "w") as f:
|
|
41
|
+
pass
|
|
42
|
+
elif d == "antimony_modules/Basic":
|
|
43
|
+
with open(os.path.join(path, "__init__.py"), "w") as f:
|
|
44
|
+
f.write("# Basic module\n")
|
|
45
|
+
with open(os.path.join(path, "ma_reaction.py"), "w") as f:
|
|
46
|
+
f.write(textwrap.dedent(f"""\
|
|
47
|
+
\"\"\"
|
|
48
|
+
Basic module with a single MA reaction A -> B.
|
|
49
|
+
\"\"\"
|
|
50
|
+
|
|
51
|
+
from framework.module_base import PyAntiGenModule
|
|
52
|
+
|
|
53
|
+
class BasicMAReaction(PyAntiGenModule):
|
|
54
|
+
\"\"\"
|
|
55
|
+
Creates a simple MA reaction A -> B.
|
|
56
|
+
\"\"\"
|
|
57
|
+
def build(self):
|
|
58
|
+
Compartments = ['Comp1']
|
|
59
|
+
for Comp in Compartments:
|
|
60
|
+
# Define reaction properties
|
|
61
|
+
Reaction_name = f"Basic_A_to_B_{{Comp}}"
|
|
62
|
+
Reactants = f"[A_{{Comp}}]"
|
|
63
|
+
Products = f"[B_{{Comp}}]"
|
|
64
|
+
Rate_type = "MA"
|
|
65
|
+
Rate_eqtn_prototype = "k_A_to_B"
|
|
66
|
+
|
|
67
|
+
# Add the reaction to the model
|
|
68
|
+
self.add_reaction(Reaction_name, Reactants, Products, Rate_type, Rate_eqtn_prototype)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class BasicChainReaction(PyAntiGenModule):
|
|
72
|
+
\"\"\"
|
|
73
|
+
Adds the second step of the chain A -> B -> C.
|
|
74
|
+
With k_B_to_C = 0 (the default in Example_parameters.csv) the
|
|
75
|
+
model behaves exactly like the single-step A -> B examples;
|
|
76
|
+
Example4/Example5 fit k_B_to_C to demonstrate flip-flop
|
|
77
|
+
bimodality.
|
|
78
|
+
\"\"\"
|
|
79
|
+
def build(self):
|
|
80
|
+
Compartments = ['Comp1']
|
|
81
|
+
for Comp in Compartments:
|
|
82
|
+
Reaction_name = f"Basic_B_to_C_{{Comp}}"
|
|
83
|
+
Reactants = f"[B_{{Comp}}]"
|
|
84
|
+
Products = f"[C_{{Comp}}]"
|
|
85
|
+
Rate_type = "MA"
|
|
86
|
+
Rate_eqtn_prototype = "k_B_to_C"
|
|
87
|
+
|
|
88
|
+
self.add_reaction(Reaction_name, Reactants, Products, Rate_type, Rate_eqtn_prototype)
|
|
89
|
+
"""))
|
|
90
|
+
print(f" Created folder: {d}/")
|
|
91
|
+
|
|
92
|
+
# PyAntiGen project settings (e.g. archive_with_timestamp: false by default to avoid large projects)
|
|
93
|
+
settings_path = os.path.join(project_dir, "pyantigen_settings.json")
|
|
94
|
+
with open(settings_path, "w", encoding="utf-8") as f:
|
|
95
|
+
json.dump({"archive_with_timestamp": False}, f, indent=2)
|
|
96
|
+
print(f" Created file: pyantigen_settings.json")
|
|
97
|
+
|
|
98
|
+
# Copy framework's .agents/skills into project top-level .agents (so agents run in project context)
|
|
99
|
+
framework_dir = os.path.dirname(os.path.abspath(__file__))
|
|
100
|
+
framework_agents = os.path.join(framework_dir, ".agents")
|
|
101
|
+
project_agents_skills = os.path.join(project_dir, ".agents", "skills")
|
|
102
|
+
if os.path.isdir(framework_agents):
|
|
103
|
+
src_skills = os.path.join(framework_agents, "skills")
|
|
104
|
+
if os.path.isdir(src_skills):
|
|
105
|
+
for name in os.listdir(src_skills):
|
|
106
|
+
src_sub = os.path.join(src_skills, name)
|
|
107
|
+
if os.path.isdir(src_sub):
|
|
108
|
+
dst_sub = os.path.join(project_agents_skills, name)
|
|
109
|
+
shutil.copytree(src_sub, dst_sub)
|
|
110
|
+
print(f" Copied .agents/skills/{name}/")
|
|
111
|
+
else:
|
|
112
|
+
print(" Created folder: .agents/skills/ (no template skills in this install)")
|
|
113
|
+
else:
|
|
114
|
+
print(" Created folder: .agents/skills/")
|
|
115
|
+
|
|
116
|
+
# Copy Example template exactly into Projects/Example/
|
|
117
|
+
template_dir = os.path.join(framework_dir, "template")
|
|
118
|
+
example_template = os.path.join(template_dir, "Example")
|
|
119
|
+
example_Projects_dir = os.path.join(project_dir, "Projects", "Example")
|
|
120
|
+
if os.path.isdir(example_template):
|
|
121
|
+
shutil.copytree(example_template, example_Projects_dir)
|
|
122
|
+
print(" Copied Projects/Example/ (from template)")
|
|
123
|
+
else:
|
|
124
|
+
os.makedirs(example_Projects_dir, exist_ok=True)
|
|
125
|
+
print(" Created folder: Projects/Example/ (template not found)")
|
|
126
|
+
|
|
127
|
+
# Copy Example data files into project data/
|
|
128
|
+
template_data = os.path.join(template_dir, "data")
|
|
129
|
+
project_data_dir = os.path.join(project_dir, "data")
|
|
130
|
+
if os.path.isdir(template_data):
|
|
131
|
+
for name in os.listdir(template_data):
|
|
132
|
+
src = os.path.join(template_data, name)
|
|
133
|
+
if os.path.isfile(src):
|
|
134
|
+
shutil.copy2(src, os.path.join(project_data_dir, name))
|
|
135
|
+
print(f" Copied data/{name}")
|
|
136
|
+
|
|
137
|
+
# Example model dirs and parameter/initial-condition CSVs (for generate output and run)
|
|
138
|
+
example_antimony_dir = os.path.join(project_dir, "antimony_models", "Example")
|
|
139
|
+
example_generated_dir = os.path.join(project_dir, "generated", "Example")
|
|
140
|
+
example_results_dir = os.path.join(project_dir, "results", "Example")
|
|
141
|
+
os.makedirs(example_antimony_dir, exist_ok=True)
|
|
142
|
+
os.makedirs(example_generated_dir, exist_ok=True)
|
|
143
|
+
os.makedirs(example_results_dir, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
param_csv_path = os.path.join(example_antimony_dir, "Example_parameters.csv")
|
|
146
|
+
with open(param_csv_path, "w") as f:
|
|
147
|
+
f.write("Parameter,Value,Units,Comment\n")
|
|
148
|
+
f.write("k_A_to_B,0.1,,Default rate constant for A to B\n")
|
|
149
|
+
f.write("k_B_to_C,0.0,,Default rate constant for B to C (0 disables the chain step)\n")
|
|
150
|
+
f.write("V_Comp1,1.0,,Default compartment volume\n")
|
|
151
|
+
print(" Created file: antimony_models/Example/Example_parameters.csv")
|
|
152
|
+
|
|
153
|
+
init_cond_path = os.path.join(example_antimony_dir, "Example_InitialConditions.csv")
|
|
154
|
+
with open(init_cond_path, "w") as f:
|
|
155
|
+
f.write("Species,InitialCondition,Units,Comment\n")
|
|
156
|
+
f.write("A_Comp1,0.0,,Initial amount of A\n")
|
|
157
|
+
f.write("B_Comp1,0.0,,Initial amount of B\n")
|
|
158
|
+
f.write("C_Comp1,0.0,,Initial amount of C\n")
|
|
159
|
+
print(" Created file: antimony_models/Example/Example_InitialConditions.csv")
|
|
160
|
+
|
|
161
|
+
init_cond_path = os.path.join(example_antimony_dir, "Example_manual.txt")
|
|
162
|
+
with open(init_cond_path, "w") as f:
|
|
163
|
+
f.write("SF = 1.0\n")
|
|
164
|
+
f.write("predicted_A := A_Comp1/V_Comp1\n")
|
|
165
|
+
f.write("predicted_B := SF*B_Comp1/V_Comp1\n")
|
|
166
|
+
print(" Created file: antimony_models/Example/Example_manual.txt")
|
|
167
|
+
|
|
168
|
+
# Project-named folder: same structure as Example with Example Modules copied in
|
|
169
|
+
project_Projects_dir = os.path.join(project_dir, "Projects", project_dir)
|
|
170
|
+
project_modules_dir = os.path.join(project_Projects_dir, "Modules")
|
|
171
|
+
os.makedirs(project_Projects_dir, exist_ok=True)
|
|
172
|
+
os.makedirs(project_modules_dir, exist_ok=True)
|
|
173
|
+
print(f" Created folder: Projects/{project_dir}/")
|
|
174
|
+
example_modules_src = os.path.join(example_template, "Modules")
|
|
175
|
+
if os.path.isdir(example_modules_src):
|
|
176
|
+
for name in os.listdir(example_modules_src):
|
|
177
|
+
src = os.path.join(example_modules_src, name)
|
|
178
|
+
if os.path.isfile(src):
|
|
179
|
+
shutil.copy2(src, os.path.join(project_modules_dir, name))
|
|
180
|
+
print(f" Copied Projects/{project_dir}/Modules/{name}")
|
|
181
|
+
print(f" Created folder: Projects/{project_dir}/Modules/ (Example modules)")
|
|
182
|
+
else:
|
|
183
|
+
print(f" Created folder: Projects/{project_dir}/Modules/ (empty, template not found)")
|
|
184
|
+
|
|
185
|
+
# Copy Example Engine into project folder
|
|
186
|
+
project_engine_dir = os.path.join(project_Projects_dir, "Engine")
|
|
187
|
+
os.makedirs(project_engine_dir, exist_ok=True)
|
|
188
|
+
example_engine_src = os.path.join(example_template, "Engine")
|
|
189
|
+
if os.path.isdir(example_engine_src):
|
|
190
|
+
for name in os.listdir(example_engine_src):
|
|
191
|
+
src = os.path.join(example_engine_src, name)
|
|
192
|
+
if os.path.isfile(src):
|
|
193
|
+
shutil.copy2(src, os.path.join(project_engine_dir, name))
|
|
194
|
+
print(f" Copied Projects/{project_dir}/Engine/{name}")
|
|
195
|
+
print(f" Created folder: Projects/{project_dir}/Engine/ (Example engine)")
|
|
196
|
+
else:
|
|
197
|
+
print(f" Created folder: Projects/{project_dir}/Engine/ (empty, template not found)")
|
|
198
|
+
|
|
199
|
+
project_antimony_dir = os.path.join(project_dir, "antimony_models", project_dir)
|
|
200
|
+
project_generated_dir = os.path.join(project_dir, "generated", project_dir)
|
|
201
|
+
project_results_dir = os.path.join(project_dir, "results", project_dir)
|
|
202
|
+
os.makedirs(project_antimony_dir, exist_ok=True)
|
|
203
|
+
os.makedirs(project_generated_dir, exist_ok=True)
|
|
204
|
+
os.makedirs(project_results_dir, exist_ok=True)
|
|
205
|
+
|
|
206
|
+
# Copy Model_generate.py / Model_run.py into project folder (MODEL_NAME derived from folder name at runtime)
|
|
207
|
+
if os.path.isdir(example_template):
|
|
208
|
+
for base in ("Model_generate", "Model_run"):
|
|
209
|
+
src = os.path.join(example_template, base + ".py")
|
|
210
|
+
if os.path.isfile(src):
|
|
211
|
+
dst = os.path.join(project_Projects_dir, base + ".py")
|
|
212
|
+
shutil.copy2(src, dst)
|
|
213
|
+
print(f" Created file: Projects/{project_dir}/{base}.py")
|
|
214
|
+
|
|
215
|
+
# Also copy AntiGen_paths.py utility
|
|
216
|
+
paths_src = os.path.join(example_template, "AntiGen_paths.py")
|
|
217
|
+
if os.path.isfile(paths_src):
|
|
218
|
+
paths_dst = os.path.join(project_Projects_dir, "AntiGen_paths.py")
|
|
219
|
+
shutil.copy2(paths_src, paths_dst)
|
|
220
|
+
print(f" Created file: Projects/{project_dir}/AntiGen_paths.py")
|
|
221
|
+
|
|
222
|
+
print("\nProject scaffolded successfully!")
|
|
223
|
+
print("Note: Because PyAntiGen is installed in your Python environment,")
|
|
224
|
+
print("you DO NOT need a copy of the 'framework' folder here. You can simply")
|
|
225
|
+
print("import it directly (e.g., 'from framework.pyantigen import PyAntiGen')")
|
|
226
|
+
print("from any script.")
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
create_project()
|