Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Constraint-based (COBRA) analysis of genome-scale metabolic models: FBA, FVA, knockouts, flux sampling, production envelopes, gapfilling, media optimization. Use for strain design, essential gene ID, flux analysis. For kinetic modeling use tellurium; for visualization use Escher.
.claude/skills/jaechang-hits-cobrapy-metabolic-modeling/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 93% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 299% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 460% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 643% | 0% |
COBRApy is a Python package for constraint-based reconstruction and analysis (COBRA) of genome-scale metabolic models. It provides flux balance analysis (FBA), flux variability analysis (FVA), gene and reaction knockout screens, flux sampling, production envelopes, gapfilling, and media optimization on SBML-format metabolic networks.
cobra (includes GLPK solver), numpy, pandasbashpip install cobra
pythonfrom cobra.io import load_model model = load_model("textbook") # E. coli core model print(f"Model: {model.id} — {len(model.reactions)} rxns, {len(model.metabolites)} mets, {len(model.genes)} genes") solution = model.optimize() print(f"Growth rate: {solution.objective_value:.4f} /h") print(f"Status: {solution.status}") # Model: e_coli_core — 95 rxns, 72 mets, 137 genes # Growth rate: 0.8739 /h # Status: optimal
Load bundled models and read/write standard formats.
pythonfrom cobra.io import load_model, read_sbml_model, write_sbml_model, load_json_model, save_json_model # Bundled: "textbook" (95 rxns), "ecoli" (2583 rxns), "salmonella" model = load_model("textbook") # model = read_sbml_model("my_model.xml") # from SBML file # model = load_json_model("my_model.json") # from JSON file write_sbml_model(model, "output_model.xml") save_json_model(model, "output_model.json") print(f"Saved model: {model.id}")
Access reactions, metabolites, and genes via DictList containers.
pythonfrom cobra.io import load_model model = load_model("textbook") # Inspect a reaction rxn = model.reactions.get_by_id("PFK") print(f"Reaction: {rxn.id} — {rxn.name}") print(f"Equation: {rxn.reaction}") print(f"Bounds: {rxn.bounds}, GPR: {rxn.gene_reaction_rule}") # Inspect a metabolite met = model.metabolites.get_by_id("atp_c") print(f"Metabolite: {met.id}, Formula: {met.formula}, Compartment: {met.compartment}") # Query and list exchange reactions atp_rxns = model.reactions.query("atp", attribute="name") print(f"ATP-related reactions: {len(atp_rxns)}, Exchange reactions: {len(model.exchanges)}")
Predict optimal flux distributions by maximizing an objective.
pythonfrom cobra.io import load_model from cobra.flux_analysis import pfba model = load_model("textbook") # Standard FBA solution = model.optimize() print(f"Growth: {solution.objective_value:.4f} /h, Active fluxes: {(solution.fluxes.abs() > 1e-6).sum()}") # Parsimonious FBA — same growth, minimal total flux pfba_sol = pfba(model) print(f"pFBA total flux: {pfba_sol.fluxes.abs().sum():.1f} vs standard: {solution.fluxes.abs().sum():.1f}")
python# Change objective; slim_optimize for speed from cobra.io import load_model model = load_model("textbook") with model: model.objective = "ATPM" print(f"Max ATPM flux: {model.optimize().objective_value:.2f}") print(f"Growth (slim): {model.slim_optimize():.4f}") # no flux vector, faster
Determine feasible flux ranges at or near optimality.
pythonfrom cobra.io import load_model from cobra.flux_analysis import flux_variability_analysis model = load_model("textbook") fva = flux_variability_analysis(model, fraction_of_optimum=1.0) fva_90 = flux_variability_analysis(model, fraction_of_optimum=0.9) fva["range"] = fva["maximum"] - fva["minimum"] fva_90["range"] = fva_90["maximum"] - fva_90["minimum"] print(f"Mean range at 100%: {fva['range'].mean():.2f}, at 90%: {fva_90['range'].mean():.2f}")
python# Loopless FVA on specific reactions from cobra.io import load_model from cobra.flux_analysis import flux_variability_analysis model = load_model("textbook") fva_ll = flux_variability_analysis( model, loopless=True, reaction_list=["PFK", "PGI", "FBA", "TPI", "GAPD"], ) print(fva_ll)
Screen for essential genes/reactions via knockout simulations.
pythonfrom cobra.io import load_model from cobra.flux_analysis import single_gene_deletion, double_gene_deletion model = load_model("textbook") wt_growth = model.slim_optimize() # Single gene deletions gene_results = single_gene_deletion(model) gene_results["growth_fraction"] = gene_results["growth"] / wt_growth essential = gene_results[gene_results["growth_fraction"] < 0.01] print(f"Essential genes: {len(essential)} / {len(model.genes)}") # Double deletions (synthetic lethality) — use multiprocessing double_results = double_gene_deletion(model, processes=4) print(f"Double deletion results: {double_results.shape}")
Modify nutrient availability and compute minimal media.
pythonfrom cobra.io import load_model from cobra.medium import minimal_medium model = load_model("textbook") # View current medium for rxn_id, flux in sorted(model.medium.items()): print(f" {rxn_id}: {flux}") # Anaerobic switch via context manager with model: medium = model.medium medium["EX_o2_e"] = 0.0 model.medium = medium print(f"Anaerobic growth: {model.slim_optimize():.4f} /h") # Minimal medium min_med = minimal_medium(model, minimize_components=True, open_exchanges=True) print(f"Minimal medium: {len(min_med)} components")
Sample feasible flux distributions for variability analysis.
pythonfrom cobra.io import load_model from cobra.sampling import sample model = load_model("textbook") samples = sample(model, n=500, method="optgp") print(f"Samples shape: {samples.shape}") # (500, n_reactions) print(f"PFK flux: mean={samples['PFK'].mean():.2f}, std={samples['PFK'].std():.2f}")
Compute phenotype phase planes and fill model gaps.
pythonfrom cobra.io import load_model from cobra.flux_analysis import production_envelope model = load_model("textbook") envelope = production_envelope( model, reactions=model.reactions.get_by_id("EX_ac_e"), carbon_sources=model.reactions.get_by_id("EX_glc__D_e"), ) print(f"Envelope: {len(envelope)} points") print(envelope[["flux_minimum", "flux_maximum", "carbon_yield_minimum", "carbon_yield_maximum"]].head())
python# Gapfilling: restore growth after reaction removal from cobra.io import load_model from cobra.flux_analysis.gapfilling import gapfill model = load_model("textbook") universal = load_model("textbook") # In practice, use a universal reaction DB with model: model.remove_reactions([model.reactions.get_by_id("PFK")]) print(f"Growth after removing PFK: {model.slim_optimize():.4f}") for rxn in gapfill(model, universal)[0]: print(f" Gapfill suggests: {rxn.id}")
Reactions, metabolites, and genes are stored in DictList — ordered, indexable, and accessible by ID.
pythonrxn = model.reactions[0] # by index rxn = model.reactions.get_by_id("PFK") # by ID matches = model.reactions.query("phospho") # keyword search
EX_ prefix reactions represent system boundary. Positive flux = secretion; negative = uptake. Managed via model.medium dict.
Boolean expressions linking genes to reactions: (b0726 and b0727) or b1234. Gene knockout propagates through GPR logic.
with model: snapshots state and reverts all changes on exit (bounds, objective, medium, knockouts). Nesting supported.
Goal: Identify essential, growth-reducing, and neutral genes.
pythonfrom cobra.io import load_model from cobra.flux_analysis import single_gene_deletion model = load_model("textbook") wt_growth = model.slim_optimize() results = single_gene_deletion(model) results["growth_fraction"] = results["growth"] / wt_growth essential = results[results["growth_fraction"] < 0.01] reduced = results[(results["growth_fraction"] >= 0.01) & (results["growth_fraction"] < 0.9)] neutral = results[results["growth_fraction"] >= 0.9] print(f"Essential: {len(essential)}, Reduced: {len(reduced)}, Neutral: {len(neutral)}") for idx in essential.index: print(f" Essential gene: {list(idx)[0]}")
Goal: Find minimal medium at different growth targets; compare aerobic vs anaerobic.
pythonfrom cobra.io import load_model from cobra.medium import minimal_medium import pandas as pd model = load_model("textbook") results = [] for frac in [0.1, 0.5, 0.8, 1.0]: with model: target = model.slim_optimize() * frac model.reactions.get_by_id("Biomass_Ecoli_core").lower_bound = target try: mm = minimal_medium(model, minimize_components=True, open_exchanges=True) results.append({"growth_frac": frac, "n_components": len(mm)}) except Exception: results.append({"growth_frac": frac, "n_components": None}) print(pd.DataFrame(results).to_string(index=False)) for label, o2 in [("Aerobic", 1000.0), ("Anaerobic", 0.0)]: with model: medium = model.medium medium["EX_o2_e"] = o2 model.medium = medium print(f"{label} growth: {model.slim_optimize():.4f} /h")
Goal: Design a strain with maximized target metabolite production. Combines modules 3, 5, and 8.
| Parameter | Module/Function | Default | Range / Options | Effect | |-----------|----------------|---------|-----------------|--------| | fraction_of_optimum | flux_variability_analysis | 1.0 | 0.0-1.0 | Fraction of max objective to maintain; lower = wider flux ranges | | loopless | flux_variability_analysis | False | True, False | Eliminate thermodynamically infeasible loops; slower | | method | sample | "optgp" | "optgp", "achr" | Sampling algorithm; optgp supports parallelism | | n | sample | required | 100-10000 | Number of flux samples to draw | | processes | sample, double_gene_deletion | 1 | 1-N_cores | Parallel worker processes | | minimize_components | minimal_medium | False | True, False | True = fewest nutrients (MILP); False = minimize total flux | | open_exchanges | minimal_medium | False | True, False | Allow all exchanges as nutrient candidates | | carbon_sources | production_envelope | None | Reaction object | Compute carbon yield alongside flux envelope | | thinning | sample | 100 | 1-1000 | Steps between kept samples; higher = less correlated |
with model: reverts all modifications on exit.python with model: model.reactions.PFK.knock_out() print(model.slim_optimize()) # modified # model is restored here
slim_optimize() before analysis: Quick feasibility check before expensive operations (FVA, sampling).solution.status after optimization: Always verify "optimal" before interpreting fluxes.processes parameter.slim_optimize() in loops: Skips full flux vector construction, significantly faster for screening.sampler.validate(samples) to check stoichiometric and bound constraints.pythonfrom cobra.io import load_model model = load_model("textbook") print("Glucose_uptake | Growth_rate") for glc_uptake in [1, 2, 5, 10, 15, 20]: with model: model.reactions.get_by_id("EX_glc__D_e").lower_bound = -glc_uptake growth = model.slim_optimize() print(f" {glc_uptake:>13} | {growth:.4f}")
pythonfrom cobra.io import load_model import pandas as pd model = load_model("textbook") conditions = [ {"name": "Rich aerobic", "EX_o2_e": 1000, "EX_glc__D_e": 10}, {"name": "Anaerobic", "EX_o2_e": 0, "EX_glc__D_e": 10}, {"name": "Low glucose", "EX_o2_e": 1000, "EX_glc__D_e": 1}, ] results = [] for c in conditions: with model: medium = model.medium medium["EX_o2_e"], medium["EX_glc__D_e"] = c["EX_o2_e"], c["EX_glc__D_e"] model.medium = medium results.append({"condition": c["name"], "growth": round(model.slim_optimize(), 4)}) print(pd.DataFrame(results).to_string(index=False))
pythonfrom cobra.io import load_model from cobra.flux_analysis import find_blocked_reactions model = load_model("textbook") # Feasibility, mass balance, dead-ends, blocked reactions print(f"Growth feasible: {model.slim_optimize() > 0}") print(f"Missing formula: {sum(1 for m in model.metabolites if m.formula is None)}") print(f"Dead-end metabolites: {sum(1 for m in model.metabolites if len(m.reactions) == 1)}") print(f"Blocked reactions: {len(find_blocked_reactions(model))} / {len(model.reactions)}")
| Problem | Cause | Solution | |---------|-------|----------| | solution.status == "infeasible" | Constraints cannot be simultaneously satisfied | Check medium has required nutrients; verify reaction bounds; use model.medium to restore defaults | | solution.status == "unbounded" | No upper bound on fluxes | Set finite upper bounds on exchange reactions | | Very slow optimization | Large model + default GLPK solver | Install CPLEX or Gurobi: model.solver = "cplex" | | ValueError setting bounds | lower_bound > upper_bound temporarily | Set as tuple: rxn.bounds = (new_lb, new_ub) | | Gene deletion returns NaN | Knockout makes model infeasible | Expected for essential genes; classify as essential | | IOError reading SBML | Invalid SBML or missing namespace | Validate at sbml.org; try cobra.io.sbml.validate_sbml_model(path) | | Flux samples fail validation | Numerical solver tolerance | Increase thinning parameter; try method="achr" |
1 reference file:
references/api_workflows.md — Consolidates API quick reference and advanced workflows. Covers: detailed function signatures, solver configuration (GLPK/CPLEX/Gurobi), advanced analysis (find_blocked_reactions, find_essential_genes/find_essential_reactions), model manipulation (adding reactions/metabolites/genes), flux sample validation, and 5 workflow examples (knockout with visualization, media design, flux space exploration, production strain design, model validation). Relocated inline: basic FBA/FVA/deletion/sampling (Core API modules 3-7). Omitted: geometric FBA internals, MIP gap configuration — consult COBRApy docs.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 20,857 | 9,275 | -56% | 1 | 1 | 0% | 3,392 | 6,561 | +93% | 0 | 0 | — |
case-02 | pass→pass | 15,793 | 7,864 | -50% | 1 | 1 | 0% | 1,154 | 6,459 | +460% | 0 | 0 | — |
case-03 | pass→pass | 4,740 | 4,580 | -3% | 1 | 1 | 0% | 759 | 5,643 | +643% | 0 | 0 | — |
case-04 | pass→pass | 8,106 | 4,443 | -45% | 1 | 1 | 0% | 1,429 | 5,645 | +295% | 0 | 0 | — |
case-05 | pass→pass | 7,310 | 4,839 | -34% | 1 | 1 | 0% | 1,366 | 5,774 | +323% | 0 | 0 | — |
case-06 | pass→pass | 9,480 | 8,053 | -15% | 1 | 1 | 0% | 1,633 | 6,440 | +294% | 0 | 0 | — |
case-07 | pass→pass | 7,423 | 4,537 | -39% | 1 | 1 | 0% | 1,309 | 5,549 | +324% | 0 | 0 | — |
case-08 | fail→pass | 9,170 | 7,184 | -22% | 1 | 1 | 0% | 1,544 | 6,160 | +299% | 0 | 0 | — |
case-09 | fail→pass | 21,474 | 14,486 | -33% | 1 | 1 | 0% | 3,965 | 7,729 | +95% | 0 | 0 | — |
case-10 | pass→pass | 34,279 | 9,195 | -73% | 1 | 1 | 0% | 2,526 | 6,567 | +160% | 0 | 0 | — |
case-11 | pass→pass | 8,634 | 4,198 | -51% | 1 | 1 | 0% | 1,470 | 5,651 | +284% | 0 | 0 | — |
case-12 | pass→pass | 3,244 | 3,590 | +11% | 1 | 1 | 0% | 548 | 5,486 | +901% | 0 | 0 | — |
case-13 | pass→pass | 3,191 | 4,450 | +39% | 1 | 1 | 0% | 546 | 5,722 | +948% | 0 | 0 | — |
case-14 | pass→pass | 7,277 | 4,245 | -42% | 1 | 1 | 0% | 1,245 | 5,714 | +359% | 0 | 0 | — |
case-15 | pass→pass | 9,287 | 4,121 | -56% | 1 | 1 | 0% | 1,614 | 5,639 | +249% | 0 | 0 | — |
case-16 | pass→pass | 3,122 | 2,777 | -11% | 1 | 1 | 0% | 569 | 5,429 | +854% | 0 | 0 | — |
case-17 | pass→pass | 9,562 | 5,944 | -38% | 1 | 1 | 0% | 1,732 | 6,086 | +251% | 0 | 0 | — |
case-18 | pass→pass | 13,714 | 9,887 | -28% | 1 | 1 | 0% | 2,565 | 6,797 | +165% | 0 | 0 | — |
case-19 | pass→pass | 7,904 | 3,181 | -60% | 1 | 1 | 0% | 1,324 | 5,435 | +310% | 0 | 0 | — |
case-20 | pass→pass | 12,467 | 7,480 | -40% | 1 | 1 | 0% | 2,290 | 6,344 | +177% | 0 | 0 | — |
case-21 | pass→pass | 15,639 | 7,051 | -55% | 1 | 1 | 0% | 2,435 | 6,103 | +151% | 0 | 0 | — |
case-22 | pass→pass | 12,328 | 4,851 | -61% | 1 | 1 | 0% | 1,950 | 5,694 | +192% | 0 | 0 | — |
case-23 | pass→pass | 13,358 | 10,880 | -19% | 1 | 1 | 0% | 2,426 | 6,866 | +183% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.