AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
COBRApy is a Python library for constraint-based reconstruction and analysis of metabolic networks. It enables researchers to perform steady-state flux analysis, simulate gene knockouts, design growth media, and explore feasible flux spaces. The tool supports standard formats like SBML and integrates with various solvers to facilitate metabolic engineering and systems biology research.
What it does
- Perform Flux Balance Analysis (FBA) and parsimonious FBA (pFBA).
- Conduct Flux Variability Analysis (FVA) to determine flux ranges.
- Simulate single and double gene deletions for knockout screening.
- Calculate minimal growth media and production envelopes.
- Validate and manipulate metabolic models using SBML, JSON, or YAML formats.
- Sample the feasible flux space of metabolic networks.
When to use it
- When building or loading metabolic network models for steady-state analysis.
- To predict the effects of genetic modifications on metabolic flux.
- When identifying minimal nutrient requirements for specific growth targets.
- To analyze the metabolic potential and production capabilities of a biological system.
Advantages
- Standardized interface for constraint-based modeling in Python.
- Supports diverse analysis methods including sampling and gap-filling.
- Compatible with multiple optimization solvers for flexibility.
- Facilitates reproducible research through structured model I/O and configuration.
Usage examples
- “Load the textbook E. coli model and perform a standard FBA to find the growth rate.”
- The agent loads the model, sets the solver, optimizes the objective, and returns the growth rate and flux values.
- “Run a single gene deletion screen on the current model to identify essential genes.”
- The agent executes the deletion analysis and provides a report of growth impacts for each gene knockout.
- “Calculate the minimal medium required to achieve 50% of the baseline growth rate.”
- The agent computes the minimal nutrient set and validates that the model achieves the target growth rate with this medium.
- “Perform flux variability analysis on the PFK reaction with a 90% optimality fraction.”
- The agent returns the minimum and maximum flux bounds for the PFK reaction under the specified constraints.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/k-dense-ai/cobrapy/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip cobrapy.zip -d .agents/skills
Documentation by K-Dense Inc.
COBRApy - Constraint-Based Reconstruction and Analysis
When to use
Use for loading/building metabolic networks, optimizing their steady-state fluxes, knockout screens, medium design, feasible-space sampling, and gap-filling hypotheses. FBA is a constraint-based prediction; it does not infer kinetic rates or establish experimental growth, thermodynamic feasibility, or flux identifiability.
Setup and reproducibility
Targets cobra 0.32.1 (import cobra), checked against its released source and
current documentation.
uv pip install "cobra==0.32.1"
# Optional SciPy support for MATLAB I/O and array operations:
uv pip install "cobra[array]==0.32.1"Plotting examples additionally require matplotlib, pandas, and seaborn. The
cobra[chrr] extra supplies hopsy for the new CHRR sampler; that optional backend
was documentation-reviewed, not executed in this refresh. In 0.32.1, sample()
defaults to method="auto" (CHRR when hopsy is installed, otherwise OptGP).
Choose the method explicitly to avoid environment-dependent changes.
Record model source/version/checksum, cobra and solver versions, objective,
medium, bounds, tolerances, and any random seed with each analysis. GLPK handles
the examples below; inspect cobra.util.solver.solvers before choosing an
optional solver. Use "hybrid" for its HiGHS/OSQP interface when installed;
the legacy "osqp" alias is deprecated. QP methods require a suitable backend.
Start with processes=1; scripts using multiprocessing need a guarded entry point.
Workflow
1. Load, inspect, and validate a model
from cobra.io import load_model
# These names are bundled: textbook, iJO1366, salmonella.
model = load_model("textbook") # model.id is e_coli_core; 95 reactions
model.solver = "glpk"
print(model.id, len(model.reactions), len(model.metabolites), len(model.genes))
print(model.reactions.get_by_id("PFK").reaction)
print(model.reactions.PFK.gene_reaction_rule)
print(model.medium)
solution = model.optimize(raise_error=True)
assert solution.status == "optimal"
print(solution.objective_value, solution.fluxes["PFK"])
# error_value=None raises on a failed solve; the default instead returns NaN.
baseline = model.slim_optimize(error_value=None)
assert baseline > 0e_coli_core is a remote BiGG identifier, not a bundled alias for textbook.
The released remote adapters encounter redirects on current BiGG/BioModels URLs;
see model I/O for verified download routes
and local SBML loading. Do not assume load_model caches: its 0.32.1 implementation
accepts cache but does not use it. Save source files for reproducibility.
Check chemical formulas/charges and boundary annotations separately from solver feasibility. Review excluded biomass/pseudo reactions and missing chemistry; an empty imbalance dictionary alone cannot establish a chemically valid model. The validation workflow distinguishes those cases.
2. Compare FBA, pFBA, and FVA under stated constraints
from cobra.flux_analysis import pfba, geometric_fba, flux_variability_analysis
biomass_id = "Biomass_Ecoli_core" # inspect IDs/objective for each new model
parsimonious = pfba(model)
print(parsimonious.fluxes[biomass_id])
# parsimonious.objective_value is the minimized total flux, not biomass growth.
centered = geometric_fba(model, processes=1)
fva = flux_variability_analysis(
model, reaction_list=["PFK", "FBA", "PGI"],
fraction_of_optimum=0.9, processes=1,
)
loopless_fva = flux_variability_analysis(
model, reaction_list=["PFK", "FBA", "PGI"],
fraction_of_optimum=0.9, loopless="fastSNP", processes=1,
)
print(fva) # index: reaction ID; columns: minimum, maximumFVA extrema are optimized separately and need not be jointly achievable.
loopless="fastSNP" computes loopless bounds; "cycleFreeFlux" is an alternative
that need not find the optimal bounds. Boolean loopless arguments are deprecated.
Loop removal does not impose measured Gibbs energies or metabolite concentrations.
Fractional objective thresholds here assume a positive biomass maximization
objective; use an explicit constraint for other objective signs/directions.
3. Knockouts and media
from cobra.flux_analysis import single_gene_deletion
from cobra.medium import minimal_medium
results = single_gene_deletion(model, processes=1)
valid = results[results.status.eq("optimal") & results.growth.notna()]
low_growth = valid[valid.growth < 0.01 * baseline] # declared 1% criterion
unresolved = results[~results.index.isin(valid.index)]
print(low_growth[["ids", "growth"]], unresolved[["ids", "status"]])
with model:
medium = model.medium
medium["EX_o2_e"] = 0.0
model.medium = medium # editing the returned dict alone does not change model
anaerobic = model.optimize()
print(anaerobic.status, anaerobic.objective_value)
min_medium = minimal_medium(model, 0.5 * baseline, minimize_components=True)
if min_medium is None:
raise RuntimeError("No medium found at the requested growth target")
with model:
model.medium = min_medium.to_dict()
assert model.slim_optimize(error_value=None) >= 0.5 * baseline - 1e-6Deletion results contain ids sets, growth, and status; the index is not a
pair MultiIndex. Double-deletion results also contain singleton sets (self-pairs).
Classify failed/infeasible solves separately from feasible low-growth mutants.
A knockout's growth column means the model's objective, so ensure it is biomass.
model.medium values are positive import bounds, not measured concentrations.
Exchange flux signs depend on reaction stoichiometry; the textbook's one-reactant
exchanges use negative flux for uptake. open_exchanges=False retains the allowed
nutrient universe while the optimizer selects a nutrient subset and its import
bounds; it does not fix the selected nutrients or amounts. open_exchanges=True
expands that universe and can choose unintended carbon sources. Minimal media can
be nonunique; validate the returned medium with the intended growth objective.
4. Sample the same feasible region as the FVA comparison
from cobra.sampling import OptGPSampler
with model:
model.reactions.get_by_id(biomass_id).lower_bound = 0.9 * baseline
# The growth floor is already installed. fraction=0 adds no stronger optimum.
sampled_fva = flux_variability_analysis(
model, reaction_list=["PFK"], fraction_of_optimum=0.0, processes=1,
)
sampler = OptGPSampler(model, processes=1, thinning=100, seed=7)
samples = sampler.sample(200)
codes = sampler.validate(samples)
assert (codes == "v").all(), "Inspect bound/equality violations"
assert (samples[biomass_id] >= 0.9 * baseline - 1e-6).all()
print(samples["PFK"].describe(), sampled_fva)FVA does not leave its objective-fraction constraint on the model; sampling a fresh/unconstrained model explores a different space. Feasible samples are not a confidence interval for measured biology. Inspect independent chains, autocorrelation and effective sample size before interpreting distributions; 200 samples are a smoke test. Sampling can include internal cycles.
5. Production envelopes and design hypotheses
from cobra.flux_analysis import production_envelope
with model:
model.reactions.get_by_id(biomass_id).lower_bound = 0.1 * baseline
envelope = production_envelope(
model, reactions=["EX_glc__D_e"], objective="EX_ac_e",
carbon_sources=["EX_glc__D_e"], points=8,
)
print(envelope[["EX_glc__D_e", "flux_minimum", "flux_maximum"]])These are acetate flux extrema; carbon_yield_* and mass_yield_* are
separate outputs, potentially NaN when inputs or formulas are unsuitable.
A multi-reaction grid needs a surface/heatmap, not an arbitrary connected line.
When zero flux is already allowed for the affected reactions, a knockout only
removes feasible states and cannot improve the global product maximum under
otherwise identical constraints. Check that knockout bound replacement does not
relax an original forced nonzero flux. The design workflow
screens the minimum product flux at a common growth requirement and also reports
maximum growth, an explicit model-based coupling hypothesis requiring validation.
6. Build and gap-fill carefully
Use Model, Reaction, Metabolite, model.add_reactions([reaction]), and
reaction.gene_reaction_rule to build the network. Set formulas and charges;
include water/protons when needed for balanced chemistry. Exchanges belong to
external metabolites, demands remove metabolites, and sinks permit both directions.
Do not add arbitrary ATP sources to make a model grow.
The API reference includes a balanced toy
network and a gap-fill example with a known missing reaction. gapfill returns
a list of reaction lists, one per iteration; it does not modify the input
model. Candidate additions are hypotheses: check evidence, directionality,
chemistry, energy-generating cycles, and growth after adding copied reactions.
Export and troubleshooting
Prefer SBML for exchange, JSON for interoperable tooling, YAML for inspection.
Round-trip the file and compare reaction IDs, bounds, objective, and growth.
Use context managers for temporary objective/bound/GPR changes. Inspect statuses
before reading fluxes; do not turn NaN or every solver failure into zero growth.
When debugging infeasibility, test medium changes inside with model: and record
which constraints changed. Feasibility after opening all nutrients does not
establish biological validity. Keep CSV/PNG output in the task's chosen directory.
Citing Scientific Agent Skills
This skill is part of Scientific Agent Skills by K-Dense. If it materially contributed to a manuscript, report, presentation, or code release, add the paper to the references or software section and tell the user you did so:
Kassis, T., Agarwal, V., He, Y., Patel, D., & Brueckner, A. M. (2026). Scientific Agent Skills: A Library of Procedural Knowledge for Research Agents. arXiv:2609.00065. https://doi.org/10.48550/arXiv.2609.00065
Always cite the current version. The DOI and https://arxiv.org/abs/2609.00065 resolve to the
latest arXiv version, so never append a version suffix such as v1. When network access is
available, fetch https://arxiv.org/abs/2609.00065 (or
http://export.arxiv.org/api/query?id_list=2609.00065) before writing the reference and take
the author list, year, and version from that record. If the record lists a journal reference
or publisher DOI, cite the published version instead.
Files
- ATTRIBUTION.mdAdded by AutoGineer968 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md11.4 KiB
- references/api_quick_reference.md16.0 KiB
- references/workflows.md15.7 KiB
references/workflows.md
COBRApy workflows
The five Python workflows below were executed with cobra 0.32.1, GLPK, and Python
3.12 on the bundled textbook model. They are small demonstrations, not validated
predictions for an organism or condition. Run this setup first, then each workflow
independently; each loads a fresh model. Plotting needs matplotlib/seaborn. Choose
OUTDIR from the task context; authorization to produce outputs covers those files.
from pathlib import Path
import cobra
OUTDIR = Path("cobrapy_output")
OUTDIR.mkdir(parents=True, exist_ok=True)
cobra.Configuration().solver = "glpk"
cobra.Configuration().processes = 1Workflow 1: Knockouts and synthetic-lethal candidates
The 1% wild-type growth threshold is an analysis choice. Separate successful
low-growth predictions from infeasible, numerical, or other unresolved solves.
The ids column contains sets, including singletons for self-pairs. Never derive
identifiers from the integer row index. GPR redundancy means one gene need not
correspond to one reaction. A bounded subset is a screen, not an exhaustive study.
import pandas as pd
import matplotlib.pyplot as plt
from cobra.io import load_model
from cobra.flux_analysis import single_gene_deletion, double_gene_deletion
model = load_model("textbook")
baseline = model.slim_optimize(error_value=None)
assert baseline > 0
threshold = 0.01 * baseline
single_results = single_gene_deletion(model, processes=1)
valid_single = single_results[
single_results.status.eq("optimal") & single_results.growth.notna()
].copy()
valid_single["gene"] = valid_single.ids.map(lambda ids: next(iter(ids)))
single_by_gene = valid_single.set_index("gene")["growth"]
low_growth = valid_single[valid_single.growth < threshold]
unresolved_single = single_results.drop(valid_single.index)
target_genes = sorted(single_by_gene[single_by_gene >= 0.5 * baseline].index)[:30]
double_results = double_gene_deletion(
model, gene_list1=target_genes, gene_list2=target_genes, processes=1,
)
synthetic_lethals = double_results[
double_results.status.eq("optimal")
& double_results.growth.lt(threshold)
& double_results.ids.map(
lambda ids: len(ids) == 2
and all(single_by_gene.get(g, float("nan")) >= 0.5 * baseline for g in ids)
)
].copy()
fig, ax = plt.subplots()
valid_single.growth.hist(bins=25, ax=ax)
ax.axvline(baseline, color="red", linestyle="--", label="Wild type")
ax.set(xlabel="Predicted growth (1/h)", ylabel="Number of genes")
ax.legend()
fig.savefig(OUTDIR / "single_deletion_distribution.png", dpi=150)
plt.close(fig)
# Sort identifiers for reproducible, human-readable CSVs.
for name, table in [("single", single_results), ("double", double_results),
("synthetic_lethals", synthetic_lethals)]:
exported = table.assign(ids=table.ids.map(lambda ids: ";".join(sorted(ids))))
exported.sort_values("ids").to_csv(OUTDIR / f"{name}_deletions.csv", index=False)
print("Low-growth single mutants:", len(low_growth))
print("Unresolved single mutants:", len(unresolved_single))
print("Candidate synthetic-lethal pairs:", len(synthetic_lethals))Workflow 2: Minimal and custom media
With open_exchanges=False, the allowed nutrient universe stays fixed. The
optimizer still selects the nutrient subset and its import bounds within that
universe; the selected nutrients and amounts are not fixed. This workflow therefore
minimizes component count within allowed nutrients, then rechecks the selected
medium. Opening all exchanges expands the allowed universe, permits alternative
carbon sources, and defines a different experiment. Minimal media may be nonunique; integer tolerances can affect tiny
imports. Report the objective, growth target and tested uptake units.
import pandas as pd
from cobra.io import load_model
from cobra.medium import minimal_medium
model = load_model("textbook")
baseline = model.slim_optimize(error_value=None)
assert baseline > 0
minimal_media = {}
rechecked_growth = {}
for fraction in [0.25, 0.5, 0.75, 0.95]:
target = fraction * baseline
medium = minimal_medium(
model, min_objective_value=target,
minimize_components=True, open_exchanges=False,
)
if medium is None:
raise RuntimeError(f"No medium found for growth {target}")
with model:
model.medium = medium.to_dict()
growth = model.slim_optimize(error_value=None)
assert growth >= target - 1e-6
minimal_media[fraction] = medium
rechecked_growth[fraction] = growth
pd.DataFrame(minimal_media).fillna(0).to_csv(OUTDIR / "minimal_media.csv")
# Retain valid IDs and other nutrients in this model; textbook has no EX_so4_e.
custom_medium = model.medium
custom_medium["EX_glc__D_e"] = 5.0
custom_medium["EX_o2_e"] = 0.0
with model:
model.medium = custom_medium
custom_growth = model.slim_optimize(error_value=None)
limiting = []
if custom_growth > model.tolerance:
for exchange, bound in custom_medium.items():
if bound <= 0: # Doubling zero is not nutrient supplementation.
continue
with model:
altered = model.medium
altered[exchange] = 2 * bound
model.medium = altered
growth = model.slim_optimize(error_value=None)
if growth > 1.01 * custom_growth:
limiting.append((exchange, growth / custom_growth - 1))
print("Custom growth:", custom_growth, "Candidate limiting imports:", limiting)Workflow 3: Matched FVA and sampling
FVA constraints are temporary. Explicitly install the growth floor before creating the sampler. Compare samples with FVA from that same constrained model. OptGP feasibility checks do not measure convergence; use independent seeds/chains, trace plots and effective sample size for scientific inference. Correlation from stoichiometric constraints is not evidence of regulation or causation.
import matplotlib.pyplot as plt
import seaborn as sns
from cobra.io import load_model
from cobra.flux_analysis import flux_variability_analysis
from cobra.sampling import OptGPSampler
model = load_model("textbook")
biomass_id = "Biomass_Ecoli_core"
baseline = model.slim_optimize(error_value=None)
key_reactions = ["PFK", "FBA", "TPI", "GAPD", "PGK", "PGM", "ENO", "PYK"]
fva_optimal = flux_variability_analysis(
model, reaction_list=key_reactions, fraction_of_optimum=1.0, processes=1,
)
with model:
model.reactions.get_by_id(biomass_id).lower_bound = 0.9 * baseline
fva_sampled = flux_variability_analysis(
model, reaction_list=key_reactions, fraction_of_optimum=0.0, processes=1,
)
sampler = OptGPSampler(model, processes=1, thinning=100, seed=7)
samples = sampler.sample(500)
assert (sampler.validate(samples) == "v").all()
assert (samples[biomass_id] >= 0.9 * baseline - 1e-6).all()
for reaction_id in key_reactions:
assert samples[reaction_id].min() >= fva_sampled.loc[reaction_id, "minimum"] - 1e-6
assert samples[reaction_id].max() <= fva_sampled.loc[reaction_id, "maximum"] + 1e-6
fig, ax = plt.subplots()
samples.PFK.hist(bins=25, ax=ax)
for bound in fva_sampled.loc["PFK", ["minimum", "maximum"]]:
ax.axvline(bound, color="red", linestyle="--")
ax.set(xlabel="PFK flux (mmol/gDW/h)", ylabel="Frequency")
fig.savefig(OUTDIR / "matched_flux_distribution.png", dpi=150)
plt.close(fig)
correlation = samples[key_reactions].corr()
fig, ax = plt.subplots()
sns.heatmap(correlation, vmin=-1, vmax=1, center=0, cmap="coolwarm", ax=ax)
fig.tight_layout()
fig.savefig(OUTDIR / "flux_correlations.png", dpi=150)
plt.close(fig)
samples.to_csv(OUTDIR / "flux_samples.csv", index=False)
fva_optimal.to_csv(OUTDIR / "fva_optimal.csv")
fva_sampled.to_csv(OUTDIR / "fva_sampled.csv")Workflow 4: Product coupling at a common growth floor
For affected reactions whose bounds already allow zero, deletion removes feasible flux states; it cannot increase the global product maximum if other constraints are identical. Verify this premise: resetting a forced nonzero reaction to zero can instead relax an original requirement. Instead, this screen asks whether minimum acetate secretion increases at the same absolute growth floor, while recording growth capacity and maximum secretion. A higher minimum is a conditional coupling prediction, not proof of better experimental production. Ranking only one arbitrary FBA solution can mistake alternate optima for improved production.
import pandas as pd
import matplotlib.pyplot as plt
from cobra.io import load_model
from cobra.flux_analysis import production_envelope, flux_variability_analysis
model = load_model("textbook")
biomass_id = "Biomass_Ecoli_core" # Explicit model-specific objective, not solver variable order.
product_id = "EX_ac_e"
baseline = model.slim_optimize(error_value=None)
growth_floor = 0.1 * baseline
with model:
model.reactions.get_by_id(biomass_id).lower_bound = growth_floor
envelope = production_envelope(
model, reactions=["EX_glc__D_e"], objective=product_id,
carbon_sources=["EX_glc__D_e"], points=12,
)
wt_range = flux_variability_analysis(
model, reaction_list=[product_id], fraction_of_optimum=0.0, processes=1,
).loc[product_id]
records = []
for gene in sorted(model.genes, key=lambda g: g.id):
with model:
gene.knock_out()
growth_solution = model.optimize()
record = {"gene": gene.id, "status": growth_solution.status}
if growth_solution.status == "optimal":
product_range = flux_variability_analysis(
model, reaction_list=[product_id], fraction_of_optimum=0.0,
processes=1,
).loc[product_id]
record.update(max_growth=growth_solution.objective_value,
product_min=product_range.minimum,
product_max=product_range.maximum)
records.append(record)
knockout_results = pd.DataFrame(records)
feasible = knockout_results[knockout_results.status.eq("optimal")].copy()
# Bounds can differ by solver tolerance, but a strict subset cannot raise the maximum.
assert (feasible.product_max <= wt_range.maximum + 1e-6).all()
candidates = feasible[feasible.product_min > wt_range.minimum + 1e-6]
candidates = candidates.sort_values("product_min", ascending=False)
knockout_results.to_csv(OUTDIR / "product_coupling_screen.csv", index=False)
plot_data = envelope.assign(uptake=-envelope.EX_glc__D_e).sort_values("uptake")
fig, ax = plt.subplots()
ax.plot(plot_data.uptake, plot_data.flux_maximum, label="Maximum acetate flux")
ax.plot(plot_data.uptake, plot_data.flux_minimum, label="Minimum acetate flux")
ax.set(xlabel="Glucose uptake (mmol/gDW/h)", ylabel="Acetate secretion (mmol/gDW/h)")
ax.legend()
fig.savefig(OUTDIR / "production_envelope.png", dpi=150)
plt.close(fig)
print("Conditional coupling candidates:", len(candidates))Do not automatically combine the best single knockouts: rerun combined genotypes under the identical medium and growth requirement. Report failed statuses and any trade-off in growth or secretion bounds. Yield requires a nonzero, correctly oriented substrate flux; flux units are not yield units.
Workflow 5: Validation and bounded diagnostics
Feasibility, chemistry, connectivity, and loop constraints answer different questions. Missing formulas/charges make chemical validation incomplete. Boundary, biomass and pseudoreactions have deliberate bookkeeping exceptions; inspect them rather than declaring every nonzero imbalance an error. A direction-aware local dead-end screen is only a topology heuristic, not flux consistency.
import pandas as pd
from cobra.io import load_model
from cobra.flux_analysis import find_blocked_reactions, flux_variability_analysis
model = load_model("textbook")
solution = model.optimize()
status = solution.status
objective_value = solution.objective_value if status == "optimal" else None
missing_chemistry = [m.id for m in model.metabolites if not m.formula or m.charge is None]
excluded_sbo = {"SBO:0000627", "SBO:0000628", "SBO:0000629", "SBO:0000631", "SBO:0000632"}
excluded_ids = {"Biomass_Ecoli_core"} | {r.id for r in model.boundary}
unbalanced = {}
unchecked = []
for reaction in model.reactions:
if reaction.id in excluded_ids or reaction.annotation.get("sbo") in excluded_sbo:
continue
if any(m.id in missing_chemistry for m in reaction.metabolites):
unchecked.append(reaction.id)
continue
try:
imbalance = reaction.check_mass_balance()
except ValueError:
unchecked.append(reaction.id)
continue
if imbalance:
unbalanced[reaction.id] = imbalance
tol = model.tolerance
dead_end_candidates = []
for met in model.metabolites:
producers, consumers = set(), set()
for reaction in met.reactions:
coefficient = reaction.metabolites[met]
if (coefficient > 0 and reaction.upper_bound > tol) or (coefficient < 0 and reaction.lower_bound < -tol):
producers.add(reaction.id)
if (coefficient < 0 and reaction.upper_bound > tol) or (coefficient > 0 and reaction.lower_bound < -tol):
consumers.add(reaction.id)
if not producers or not consumers:
dead_end_candidates.append(met.id)
# Exact stoichiometric duplicates only; compare bounds/GPRs before merging anything.
seen, duplicates = {}, []
for reaction in model.reactions:
key = tuple(sorted((m.id, c) for m, c in reaction.metabolites.items()))
if key in seen:
duplicates.append((seen[key], reaction.id))
else:
seen[key] = reaction.id
orphan_genes = [g.id for g in model.genes if not g.reactions]
blocked, loop_sensitive = None, None
if status == "optimal":
blocked = find_blocked_reactions(model, open_exchanges=False, processes=1)
subset = ["PFK", "FRD7", "SUCDi"]
ordinary = flux_variability_analysis(model, subset, processes=1)
loopless = flux_variability_analysis(model, subset, loopless="fastSNP", processes=1)
loop_sensitive = [rid for rid in subset if
ordinary.loc[rid, "maximum"] > loopless.loc[rid, "maximum"] + 1e-6
or ordinary.loc[rid, "minimum"] < loopless.loc[rid, "minimum"] - 1e-6]
else:
print("[FAIL] Original model status:", status)
with model:
medium = {r.id: 1000.0 for r in model.exchanges}
model.medium = medium
diagnostic = model.optimize()
print("Open-import diagnostic status:", diagnostic.status)
# Do not overwrite the original status or claim a structural cause from this alone.
report = {
"model_id": model.id, "status": status, "objective_value": objective_value,
"missing_chemistry": len(missing_chemistry), "unchecked_internal": len(unchecked),
"unbalanced_internal": len(unbalanced), "dead_end_candidates": len(dead_end_candidates),
"exact_duplicate_pairs": len(duplicates), "orphan_genes": len(orphan_genes),
"blocked_in_current_medium": None if blocked is None else len(blocked),
"loop_sensitive_in_tested_subset": None if loop_sensitive is None else len(loop_sensitive),
}
pd.DataFrame([report]).to_csv(OUTDIR / "validation_report.csv", index=False)
print(report)A negative loop screen on three reactions is not a whole-model thermodynamic certificate. Growth, knockouts, media, and sampling all depend on the model's objective, uptake bounds, maintenance costs, and solver tolerances.
Official sources
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Needs network access, Needs Python
The author says: Requires Python 3.9+ and cobra; examples tested with Python 3.12 and cobra 0.32.1. GLPK installs via swiglpk; commercial solvers need separate installation/licensing. Remote model downloads need network access; bundled textbook examples run offline.
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- K-Dense Inc.
- Publisher
- K-Dense (Community)
- Source
- K-Dense-AI/scientific-agent-skills · skills/cobrapy · commit a25cf9e
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by K-Dense Inc. or K-Dense.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download cobrapy.zip