systems-biology/flux-balance-analysis/SKILL.md
Performs flux balance analysis (FBA), flux variability analysis (FVA), parsimonious FBA (pFBA), loopless FBA, flux sampling, and production envelopes on genome-scale metabolic models with COBRApy, solving the biomass-maximization linear program under a defined medium. Use when predicting growth rate on a carbon source, computing flux ranges and alternative optima (FVA), setting exchange bounds and minimal media, distinguishing a real growth phenotype from an under-constrained model, sampling the flux solution space, or choosing between FBA, pFBA, loopless FBA, and sampling for a flux distribution.
npx skillsauth add GPTomics/bioSkills bio-systems-biology-flux-balance-analysisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference examples tested with: COBRApy 0.29+, Python 3.10+
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signaturesIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
Note: the objective LP is solved by a backend solver. GLPK (the COBRApy default) can return degenerate or marginally-infeasible answers on large or ill-conditioned models; prefer HiGHS (if available in the installed cobra/optlang build) or a free academic CPLEX/Gurobi for genome-scale work and any MILP/QP method (loopless, MOMA). Set with model.solver = 'glpk'|'highs'|'gurobi'|'cplex'.
"Predict growth rate and metabolic fluxes for my organism" -> Solve a linear program over a genome-scale metabolic model that maximizes a biomass (or custom) objective subject to steady-state mass balance and exchange bounds, then quantify how much of that solution is actually determined.
model.optimize(), cobra.flux_analysis.flux_variability_analysis(), pfba(), cobra.sampling.sample() (COBRApy)FBA imposes steady state (S*v = 0) plus bounds and maximizes an objective. The steady-state constraint is a pseudo-steady-state on fast-turnover metabolite POOLS, not on the organism. Critically, the optimal objective is usually reached on a whole FACE of the solution polytope, not a single point: many different internal flux vectors give the identical maximal growth. model.optimize() returns ONE arbitrary vertex of that face. Consequences that govern every downstream decision:
solution.fluxes[rxn] as "the" flux without FVA (the range) or pFBA (a parsimonious representative) or sampling (the distribution).| Question | Method | Why |
|----------|--------|-----|
| Max growth rate / yield on a medium | FBA model.optimize() | single LP; objective value is the robust output |
| Is a reaction's flux determined, or free to vary? | FVA flux_variability_analysis | reports min/max flux at (near-)optimal growth; exposes alternate optima |
| One realistic representative flux vector | pFBA pfba | among optima, the one minimizing total flux (proxy for minimal enzyme cost) |
| Remove thermodynamically infeasible internal cycles | loopless (loopless_solution, or FVA loopless=True) | strips net flux around closed loops with no driving force |
| Full uncertainty / flux DISTRIBUTIONS, no objective needed | sampling cobra.sampling.sample | uniformly samples the solution space; use when the objective is unknown or confidence intervals are needed |
| Growth-vs-byproduct tradeoff for engineering | production_envelope | Pareto frontier of growth vs product secretion |
| Immediate knockout mutant flux (not re-optimized) | MOMA/ROOM -> gene-essentiality | minimal-adjustment, not re-optimization; see systems-biology/gene-essentiality |
| Overflow metabolism (acetate/Crabtree) missing | enzyme-constrained model (GECKO/sMOMENT) | plain FBA has no proteome budget; needs capacity constraints |
import cobra
model = cobra.io.load_model('textbook') # E. coli core (e_coli_core), 95 reactions, ships with cobra
model = cobra.io.load_model('iJO1366') # genome-scale E. coli, 2583 reactions
model = cobra.io.read_sbml_model('model.xml') # SBML (the standard exchange format)
model = cobra.io.load_json_model('model.json') # COBRA JSON
# Curated genome-scale models: http://bigg.ucsd.edu/models (King 2016). Record the model
# version; predictions are only comparable within the same model release.
Goal: Predict the maximum growth rate and a flux distribution under a defined medium, and judge whether the number is biological.
Approach: Load a model, confirm the objective is the intended biomass reaction, solve the LP, then read the objective value while treating individual fluxes as provisional until FVA/sampling confirms them.
import cobra
model = cobra.io.load_model('textbook')
solution = model.optimize()
print(f'Objective (growth): {solution.objective_value:.4f} /h status: {solution.status}')
print('Objective reaction:', str(model.objective.expression).split('*')[1].split()[0])
# A growth rate is interpretable ONLY against a stated medium and biomass reaction.
# Compare to a measured doubling time (mu = ln2 / t_double) rather than to a fixed
# "fast/slow" scale; absolute values are organism- and biomass-definition-specific.
Goal: Impose a defined nutrient environment so growth reflects the intended condition, not leftover open exchanges.
Approach: Close every exchange, then open only the intended uptakes. By COBRApy convention an exchange EX_x_e has flux < 0 for uptake and > 0 for secretion, so uptake is set through the lower bound. The model.medium dict is the concise idiom (its values are uptake magnitudes, positive).
def set_minimal_medium(model, carbon_source='EX_glc__D_e', carbon_uptake=10):
'''Close all uptake, then open a defined minimal medium.
carbon_uptake in mmol/gDW/h; 10 is the standard E. coli aerobic glucose rate (iJO1366).
'''
for rxn in model.exchanges:
rxn.lower_bound = 0
minimal = {'EX_o2_e': 1000, 'EX_h2o_e': 1000, 'EX_h_e': 1000, 'EX_nh4_e': 1000,
'EX_pi_e': 1000, 'EX_so4_e': 1000, 'EX_k_e': 1000, 'EX_mg2_e': 1000}
for ex_id, uptake in minimal.items():
if ex_id in model.reactions:
model.reactions.get_by_id(ex_id).lower_bound = -uptake
if carbon_source in model.reactions:
model.reactions.get_by_id(carbon_source).lower_bound = -carbon_uptake
return model
# The with-block reverts all bound changes on exit, so comparisons never leak state.
for cs in ['EX_glc__D_e', 'EX_ac_e', 'EX_succ_e']:
with model:
set_minimal_medium(model, carbon_source=cs)
print(f'{cs}: growth = {model.slim_optimize():.4f}') # slim_optimize returns the objective float only (fast)
from cobra.flux_analysis import flux_variability_analysis
# Range each reaction can carry while holding the objective at (fraction_of_optimum) of the max.
fva = flux_variability_analysis(model, fraction_of_optimum=1.0)
# fraction_of_optimum < 1 relaxes the objective and reveals the alternative-optima span.
fva90 = flux_variability_analysis(model, fraction_of_optimum=0.9)
# loopless=True removes thermodynamically infeasible internal loops from the ranges (slower).
fva_ll = flux_variability_analysis(model, loopless=True)
# A reaction with maximum == minimum is fully determined; a wide range means the single
# FBA value for it was arbitrary. Blocked reactions have min == max == 0.
fva['range'] = fva['maximum'] - fva['minimum']
fva['blocked'] = fva['range'].abs() < 1e-9
from cobra.flux_analysis import pfba
# Among all optima, pFBA returns the flux vector minimizing the sum of absolute fluxes,
# an Occam's-razor proxy for minimal total enzyme cost (Lewis 2010). It is a principled
# single representative of the optimal face; it is NOT more "true" than the face itself,
# and it hugs the polytope boundary (a min-flux vertex), so report the FVA range alongside it
# when the internal flux values matter.
pfba_solution = pfba(model)
print(f'FBA total flux : {model.optimize().fluxes.abs().sum():.1f}')
print(f'pFBA total flux: {pfba_solution.fluxes.abs().sum():.1f}')
from cobra.flux_analysis import loopless_solution
# Projects an FBA solution onto a loopless one: no net flux around a closed cycle that
# lacks a thermodynamic driving force (Schellenberger 2011). Internal cycles are a common
# artifact of reversible reactions and gap-filling and inflate apparent flux magnitudes.
loopless = loopless_solution(model)
Goal: Characterize the whole space of feasible steady-state fluxes rather than one optimum, giving each reaction a distribution and confidence interval.
Approach: Uniformly sample the (optionally objective-constrained) solution polytope with a Markov-chain sampler (OptGP or ACHR). Use when there is no clear objective, when the objective face is large, or when uncertainty on internal fluxes matters more than an optimum.
from cobra.sampling import sample
# n samples; method 'optgp' (parallel) or 'achr'. Thinning reduces autocorrelation.
samples = sample(model, n=1000, method='optgp', thinning=100, seed=1)
print(samples['PFK'].describe()) # per-reaction distribution, e.g. median and IQR
# To sample only high-growth states, constrain the objective first (e.g. biomass >= 0.9*max)
# inside a `with model:` block, then sample. Check mixing before trusting the distribution
# (multiple chains/seeds should agree); short chains give correlated, misleading samples.
from cobra.flux_analysis import production_envelope
# Pareto frontier of growth against a secreted product; the design space for strain engineering.
# objective defaults to the model's objective (the biomass reaction) when omitted.
env = production_envelope(model, reactions=['EX_ac_e'])
# To actually DESIGN knockouts that couple product to growth, see systems-biology/strain-design.
| Symptom | Cause | Fix |
|---------|-------|-----|
| Growth is 0 / status 'infeasible' | medium closed, or an essential exchange left at lb=0, or a biomass precursor unproducible | check model.medium; open the minimal exchange set; confirm biomass precursors have a route |
| Suspiciously high growth on "minimal" media | an exchange left open to uptake (rich carbon, or all exchanges default-open) | close all exchange lower bounds to 0, then open only the intended set; audit model.medium |
| A reaction's flux changes every run / disagrees between tools | alternate optima - the value was one arbitrary vertex | report the FVA range or a pFBA/sampling value, not a single optimize() flux |
| Implausibly large internal fluxes | thermodynamically infeasible internal cycle | use loopless_solution or FVA loopless=True |
| Model predicts no acetate overflow at high glucose | plain FBA has no proteome/enzyme budget | use an enzyme-constrained model (GECKO/sMOMENT); FBA cannot see overflow |
| phenotype_phase_plane(...) raises TypeError | it is now a module, not a callable, in modern COBRApy | use production_envelope instead |
| Solver returns tiny nonzero "fluxes" that should be 0 | GLPK feasibility tolerance / degeneracy | switch to HiGHS or CPLEX/Gurobi; threshold fluxes at ~1e-6 |
development
Installs 425 bioinformatics skills covering sequence analysis, RNA-seq, single-cell, variant calling, metagenomics, structural biology, and 56 more categories. Use when setting up bioinformatics capabilities or when a bioinformatics task requires specialized skills not yet installed.
testing
Chains a somatic (tumor-normal) SNV/indel and structural-variant pipeline end to end with GATK Mutect2 (or Strelka2), wiring the somatic-specific machinery - panel-of-normals and gnomAD germline-resource priors, GetPileupSummaries/CalculateContamination, and LearnReadOrientationModel FFPE/oxoG orientation-bias filtering fed into FilterMutectCalls. Use when calling somatic mutations from a tumor-normal pair (or tumor-only with PoN caveats), deciding which artifact filter removes which class of false positive, reasoning about VAF/purity/ploidy and clonal-vs-subclonal detection, adding somatic SV/CNV or TMB/MSI/signatures, or routing variants to AMP/ASCO/CAP tier and oncogenicity interpretation (never germline ACMG).
development
End-to-end pooled and single-cell CRISPR screen analysis from FASTQ to hit genes. Orchestrates library design QC, guide counting, six-stage screen QC (plasmid Gini, replicate Pearson, CEGv2 PR-AUC, copy-number artifact), method-appropriate hit calling across MAGeCK RRA/MLE, BAGEL2, drugZ, JACKS, and Chronos, cancer-cell-line copy-number correction (CRISPRcleanR / Chronos), batch correction for multi-batch screens, and the specialized branches for combinatorial paralog screens, single-cell Perturb-seq, base-editor variant-function screens, prime-editor screens, and in vivo bottleneck-aware screens. Use when analyzing any pooled CRISPR screen end-to-end, matching the hit-calling method to the experimental design, integrating copy-number correction into the pipeline, or branching the workflow for single-cell, combinatorial, base-editor, prime-editor, or in vivo variants.
development
Transcribe DNA to RNA and translate to protein using Biopython, with NCBI codon-table selection, CDS validation, and six-frame ORF finding. Use when converting a CDS or ORF to its amino-acid sequence, selecting a non-standard (mitochondrial, bacterial, ciliate) genetic code, validating a coding sequence, or scanning all reading frames.