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,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()