Skip to content

Commit

Permalink
add basic pipe-able pfba
Browse files Browse the repository at this point in the history
  • Loading branch information
stelmo committed Feb 20, 2023
1 parent c3ee550 commit e0ccc03
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 2 deletions.
1 change: 0 additions & 1 deletion src/analysis/flux_balance_analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ function flux_balance_analysis(model::AbstractMetabolicModel, optimizer; modific
ModelWithResult(model, opt_model)
end


"""
$(TYPEDSIGNATURES)
Expand Down
9 changes: 9 additions & 0 deletions src/reconstruction/pipes/parsimonious.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
$(TYPEDSIGNATURES)
Pipe-able version of the [`ParsimoniousModel`](@ref) wrapper.
"""
with_parsimonious_solution(semantics::Vector{Symbol}) =
model -> ParsimoniousModel(model, semantics)

with_parsimonious_solution(semantic::Symbol) = with_parsimonious_solution([semantic])
48 changes: 48 additions & 0 deletions src/types/wrappers/ParsimoniousModel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

"""
$(TYPEDEF)
A wrapper that adds a quadratic objective that minimizes the sum of a set of
squared variables. If the bounds of the growth rate of the model are fixed, this
corresponds to finding a parsimonious solution (i.e. pFBA).
This is used to implement [`parsimonious_flux_balance_analysis`](@ref).
# Example
```
model |> with_changed_bound("biomass", lower_bound = 0.1) |> with_parsimonious_solution(:enzymes) |> flux_balance_analysis(Clarabel.Optimizer)
```
"""
struct ParsimoniousModel <: AbstractModelWrapper
inner::AbstractMetabolicModel
var_ids::Vector{String}
end

function ParsimoniousModel(model::AbstractMetabolicModel, semantics::Vector{Symbol})
var_ids = vcat([@eval $(Symbol(sem, :s))($(model)) for sem in semantics]...)
ParsimoniousModel(model, var_ids)
end

ParsimoniousModel(model::AbstractMetabolicModel, semantic::Symbol) =
ParsimoniousModel(model, [semantic])

Accessors.unwrap_model(m::ParsimoniousModel) = m.inner

"""
$(TYPEDSIGNATURES)
Return a negative, uniformly weighted, quadratic-only objective representing the
squared sum of `model.var_ids`.
"""
function Accessors.objective(model::ParsimoniousModel)::SparseMat
obj = spzeros(n_variables(model), n_variables(model) + 1) # + 1 for QP solver formulation

idxs = indexin(model.var_ids, variables(model))
j = findfirst(isnothing, idxs)
isnothing(j) || throw(DomainError(model.var_ids[j], "No variable with this ID."))

for i in idxs
obj[i, i] = -1.0 # negative because objective will be maximized
end
obj
end
12 changes: 12 additions & 0 deletions test/analysis/parsimonious_flux_balance_analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@
# The used optimizer doesn't really converge to the same answer everytime
# here, we therefore tolerate a wide range of results.
@test isapprox(d["biomass1"], 10.0, atol = QP_TEST_TOLERANCE)

sol =
model |>
with_changed_bound("biomass1", lower_bound = 10.0) |>
with_parsimonious_solution(:reaction) |>
flux_balance_analysis(Clarabel.Optimizer)

@test isapprox(
values_dict(:reaction, model, sol)["biomass1"],
10.0,
atol = QP_TEST_TOLERANCE,
)
end
29 changes: 28 additions & 1 deletion test/reconstruction/enzyme_constrained.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@
atol = TEST_TOLERANCE,
)

# test enzyme objective
# test pFBA
growth_lb = rxn_fluxes["BIOMASS_Ecoli_core_w_GAM"] * 0.9

res = flux_balance_analysis(
gm,
Tulip.Optimizer;
Expand All @@ -86,6 +87,32 @@
)
mass_groups_min = values_dict(:enzyme_group, res)
@test mass_groups_min["uncategorized"] < mass_groups["uncategorized"]

gm_changed_bound =
model |>
with_changed_bound("BIOMASS_Ecoli_core_w_GAM", lower_bound = growth_lb) |>
with_enzyme_constraints(
gene_product_mass_group_bound = Dict(
"uncategorized" => total_gene_product_mass,
),
)

pfba_sol =
gm_changed_bound |>
with_parsimonious_solution(:enzyme) |>
flux_balance_analysis(Clarabel.Optimizer)

@test isapprox(
values_dict(:reaction, gm_changed_bound, pfba_sol)["BIOMASS_Ecoli_core_w_GAM"],
0.7315450597991255,
atol = QP_TEST_TOLERANCE,
)

@test isapprox(
values_dict(:enzyme_group, gm_changed_bound, pfba_sol)["uncategorized"],
91.425,
atol = QP_TEST_TOLERANCE,
)
end

@testset "GECKO small model" begin
Expand Down

0 comments on commit e0ccc03

Please sign in to comment.