diff --git a/src/analysis/dfba.jl b/src/analysis/dfba.jl deleted file mode 100644 index 464090415c47109523e91779d4f40e19495c9cf1..0000000000000000000000000000000000000000 --- a/src/analysis/dfba.jl +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/src/analysis/fba.jl b/src/analysis/fba.jl index 3e25aa0fe6f5328f7e3df35f5e15dd65eb72e9ea..bc03a097f96ec8aff9c27f4d11c45df04fa0b975 100644 --- a/src/analysis/fba.jl +++ b/src/analysis/fba.jl @@ -22,10 +22,9 @@ Arguments are passed to [`flux_balance_analysis`](@ref). """ function flux_balance_analysis_vec(args...)::Union{Vector{Float64},Nothing} optmodel = flux_balance_analysis(args...) - vars = optmodel[:x] - termination_status(optmodel) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing - value.(vars) + JuMP.termination_status(optmodel) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing + value.(optmodel[:x]) end """ @@ -44,126 +43,80 @@ function flux_balance_analysis_dict( end """ - fba(model::CobraModel, optimizer; objective_func::Union{Reaction, Array{Reaction, 1}}=Reaction[], weights=Float64[], solver_attributes=Dict{Any, Any}(), constraints=Dict{String, Tuple{Float64,Float64}}()) + flux_balance_analysis(model::StandardModel, optimizer; modifications) -Run flux balance analysis (FBA) on the `model` optionally specifying `objective_rxn(s)` and their `weights` (empty `weights` mean equal weighting per reaction). -Optionally also specify any additional flux constraints with `constraints`, a dictionary mapping reaction `id`s to tuples of (lb, ub) flux constraints. +Run flux balance analysis (FBA) on the `model` optionally specifying `modifications` to the problem. +These modifications can be entered as an array of modifications, or a single modification. +Leave this keyword argument out if you do not want to modify the problem. +See [`modify_constraint`](@ref), [`modify_solver_attribute`](@ref),[`modify_objective`](@ref), and [`modify_sense`](@ref) +for possible modifications. Note, the `optimizer` must be set to perform the analysis, any JuMP solver will work. -The `solver_attributes` can also be specified in the form of a dictionary where each (key, value) pair will be passed to `set_optimizer_attribute(cbmodel, key, value)`. -This function builds the optimization problem from the model, and hence uses the constraints implied by the model object. -Returns a dictionary of reaction `id`s mapped to fluxes if solved successfully, otherwise an empty dictionary. +Returns a solved JuMP model. # Example ``` optimizer = Gurobi.Optimizer -atts = Dict("OutputFlag" => 0) -model = CobraTools.read_model("iJO1366.json") -biomass = findfirst(model.reactions, "BIOMASS_Ec_iJO1366_WT_53p95M") -sol = fba(model, biomass, optimizer; solver_attributes=atts) +model = CobraTools.read_model("e_coli_core.json") +biomass = findfirst(model.reactions, "BIOMASS_Ecoli_core_w_GAM") +solved_model = fba(model, optimizer; modifications=[modify_objective(biomass)]) ``` """ -function fba( - model::CobraModel, +function flux_balance_analysis( + model::StandardModel, optimizer; - modifications = [(model, opt_model)->nothing] + modifications = [(model, opt_model) -> nothing], ) - -objective_func::Union{Reaction,Array{Reaction,1}} = Reaction[] -weights = Float64[] -solver_attributes = Dict{Any,Any}() -sense = MOI.MAX_SENSE - # get core optimization problem - cbm = make_optimization_model(model, optimizer, sense = sense) - v = cbm[:x] # fluxes + cbm = make_optimization_model(model, optimizer) # apply callbacks - for mod in modifications - mod(model, cbm) - end - - # modify core optimization problem according to user specifications - if !isempty(solver_attributes) # set other attributes - for (k, val) in solver_attributes - set_optimizer_attribute(cbm, k, val) + if typeof(modifications) <: Vector # many modifications + for mod in modifications + mod(model, cbm) end + else # single modification + modifications(model, cbm) end -<<<<<<< Updated upstream - # if an objective function is supplied, modify the default objective - if typeof(objective_func) == Reaction || !isempty(objective_func) - # ensure that an array of objective indices are fed in - if typeof(objective_func) == Reaction - objective_indices = [model[objective_func]] - else - objective_indices = [model[rxn] for rxn in objective_func] - end + JuMP.optimize!(cbm) - if isempty(weights) - weights = ones(length(objective_indices)) - end - opt_weights = zeros(length(model.reactions)) - - # update the objective function tracker - # don't update model objective function - silly thing to do - wcounter = 1 - for i in eachindex(model.reactions) - if i in objective_indices - # model.reactions[i].objective_coefficient = weights[wcounter] - opt_weights[i] = weights[wcounter] - wcounter += 1 - # else - # model.reactions[i].objective_coefficient = 0.0 - end - end + return cbm +end - @objective(cbm, sense, sum(opt_weights[i] * v[i] for i in objective_indices)) - else # use default objective - # automatically assigned by make_optimization_model - end +""" + flux_balance_analysis_vec(model::StandardModel, optimizer; modifications) - optimize!(cbm) +Perform flux balance analysis on `model` using `optimizer`. +Returns a vector fluxes in the order supplied in `model`. +Calls [`flux_balance_analysis`](@ref) internally. +""" +function flux_balance_analysis_vec( + model::StandardModel, + optimizer; + modifications = [(model, opt_model) -> nothing], +) + cbm = flux_balance_analysis(model, optimizer; modifications = modifications) -======= -function flux_balance_analysis_vec(args) - cbm = flux_balance_analysis(args...) - - status = ( - termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED - ) - - if status - return value.(cbm[:x]) - else - @warn "Optimization issues occurred." - return Vector{Float64}[] - end + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing + + return value.(cbm[:x]) end -function flux_balance_analysis_dict(model, args) - cbm = flux_balance_analysis(model, args...) - ->>>>>>> Stashed changes - status = ( - termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED - ) -<<<<<<< Updated upstream - - if status - return map_fluxes(v, model) - else - @warn "Optimization issues occurred." - return Dict{String,Float64}() - end -======= +""" + flux_balance_analysis_dict(model::StandardModel, optimizer; modifications) + +Perform flux balance analysis on `model` using `optimizer`. +Returns a dictionary mapping reaction `id`s to fluxes. +Calls [`flux_balance_analysis`](@ref) internally. +""" +function flux_balance_analysis_dict( + model::StandardModel, + optimizer; + modifications = [(model, opt_model) -> nothing], +) + cbm = flux_balance_analysis(model, optimizer; modifications = modifications) + + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing - if status - return Dict(zip(reactions(model), value.(cbm[:x]))) - else - @warn "Optimization issues occurred." - return Dict{String, Float64}() - end ->>>>>>> Stashed changes + return Dict(zip(reactions(model), value.(cbm[:x]))) end diff --git a/src/analysis/fva.jl b/src/analysis/fva.jl index 17794201f378dc2b9df0d8913531fe592f333282..19d4db4cb3a78d93747960b3588460c4e06ef256 100644 --- a/src/analysis/fva.jl +++ b/src/analysis/fva.jl @@ -45,8 +45,13 @@ function flux_variability_analysis( save_model = :( begin optmodel = COBREXA.make_optimization_model($model, $optimizer) - x = optmodel[:x] - COBREXA._FVA_add_constraint(optmodel, $(objective(model)), x, $Z0, $gamma) + COBREXA._FVA_add_constraint( + optmodel, + $(objective(model)), + optmodel[:x], + $Z0, + $gamma, + ) optmodel end ) @@ -111,120 +116,60 @@ function _FVA_optimize_reaction(model, rid) end """ - fva(model::CobraModel, objective_rxns::Union{Reaction, Array{Reaction, 1}}, optimizer; optimum_bound=0.9999, weights=Float64[], solver_attributes=Dict{Any, Any}(), constraints=Dict{String, Tuple{Float64,Float64}}()) + fva(model::StandardModel, optimizer; optimum_bound=0.9999, modifications) -Run flux variability analysis (FVA) on the `model` with `objective_rxn(s)` and optionally specifying their `weights` (empty `weights` mean equal weighting per reaction). -It runs fba on the model once to determine the optimum of the objective. -Optionally also specify any additional flux constraints with `constraints`, a dictionary mapping reaction `id`s to tuples of (ub, lb) flux constraints. -The model is then constrained to produce objective flux bounded by `optimum_bound` from below (set to slightly less than 1.0 for stability) and each flux in the model sequentially minimized and maximized. +Run flux variability analysis (FVA) on the `model` (of type `StandardModel`). +Optionally specifying problem modifications like in [`flux_balance_analysis`](@ref). +This algorithm runs FBA on the model to determine the optimum of the objective. +This optimum then constrains subsequent problems, where `optimum_bound` can be used to relax this constraint as a fraction of the FBA optimum. Note, the `optimizer` must be set to perform the analysis, any JuMP solver will work. -The `solver_attributes` can also be specified in the form of a dictionary where each (key, value) pair will be passed to `set_optimizer_attribute(cbmodel, key, value)`. -This function builds the optimization problem from the model, and hence uses the constraints implied by the model object. -Returns two dictionaries (`fva_max` and `fva_min`) that each reaction `id`s to dictionaries of the resultant flux distributions (if solved successfully) when that `id` is optimized. +Note, this function only runs serially. +Consider using a different model type for parallel implementations. +Returns two dictionaries (`fva_max` and `fva_min`) that map each reaction `id` to dictionaries of the resultant flux distributions when that `id` is optimized. # Example ``` optimizer = Gurobi.Optimizer -atts = Dict("OutputFlag" => 0) model = Cobraread_model("iJO1366.json") biomass = findfirst(model.reactions, "BIOMASS_Ec_iJO1366_WT_53p95M") fva_max, fva_min = fva(model, biomass, optimizer; solver_attributes=atts) ``` """ -function fva( - model::CobraModel, +function flux_variability_analysis( + model::StandardModel, optimizer; - objective_func::Union{Reaction,Array{Reaction,1}} = Reaction[], optimum_bound = 0.9999, - weights = Float64[], - solver_attributes = Dict{Any,Any}(), - constraints = Dict{String,Tuple{Float64,Float64}}(), - sense = MOI.MAX_SENSE, + modifications = [(model, opt_model) -> nothing], ) -<<<<<<< Updated upstream - cbm = make_optimization_model(model, optimizer, sense = sense) - v = cbm[:x] -======= - # get core optimization problem - cbm = make_optimization_model(model, optimizer) ->>>>>>> Stashed changes - - if !isempty(solver_attributes) # set other attributes - for (k, v) in solver_attributes - set_optimizer_attribute(cbm, k, v) - end - end - - # set additional constraints - for (rxnid, con) in constraints - ind = model.reactions[findfirst(model.reactions, rxnid)] - set_bound(ind, cbm; lb = con[1], ub = con[2]) - end - - # if an objective function is supplied, modify the default objective - if typeof(objective_func) == Reaction || !isempty(objective_func) - # ensure that an array of objective indices are fed in - if typeof(objective_func) == Reaction - objective_indices = [model[objective_func]] - else - objective_indices = [model[rxn] for rxn in objective_func] - end - - if isempty(weights) - weights = ones(length(objective_indices)) - end - opt_weights = zeros(length(model.reactions)) - - # update the objective function tracker - # don't update model objective function - silly thing to do - wcounter = 1 - for i in eachindex(model.reactions) - if i in objective_indices - # model.reactions[i].objective_coefficient = weights[wcounter] - opt_weights[i] = weights[wcounter] - wcounter += 1 - # else - # model.reactions[i].objective_coefficient = 0.0 - end - end - @objective(cbm, sense, sum(opt_weights[i] * v[i] for i in objective_indices)) - end - - optimize!(cbm) - - status = ( - termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED - ) + # Run FBA + cbm = flux_balance_analysis(model, optimizer; modifications=modifications) - fva_min = Dict{String,Dict{String,Float64}}() - fva_max = Dict{String,Dict{String,Float64}}() + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || (return nothing, nothing) - if !status - @warn "Error getting the initial optimum, aborting " - return fva_max, fva_min - end + fva_min = Dict{String,Union{Nothing, Dict{String,Float64}}}() + fva_max = Dict{String,Union{Nothing, Dict{String,Float64}}}() # Now do FVA v = cbm[:x] - λ = objective_value(cbm) # objective value - @constraint( + λ = JuMP.objective_value(cbm) # objective value + JuMP.@constraint( cbm, - optimum_bound * λ <= sum(opt_weights[i] * v[i] for i in objective_indices) <= λ + optimum_bound * λ <= JuMP.objective_function(cbm) <= λ * 1.0/optimum_bound # in case there is a negative bound and the problem has min sense ) # for stability for i = 1:length(v) - @objective(cbm, Max, v[i]) - optimize!(cbm) + JuMP.@objective(cbm, Max, v[i]) + JuMP.optimize!(cbm) status = ( - termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED + JuMP.termination_status(cbm) == MOI.OPTIMAL || + JuMP.termination_status(cbm) == MOI.LOCALLY_SOLVED ) if status - fva_max[model.reactions[i].id] = map_fluxes(v, model) + fva_max[model.reactions[i].id] = Dict(zip(reactions(model), value.(cbm[:x]))) else @warn "Error maximizing index: $i with error $(termination_status(cbm))" + fva_max[model.reactions[i].id] = nothing end @objective(cbm, Min, v[i]) @@ -234,9 +179,10 @@ function fva( termination_status(cbm) == MOI.LOCALLY_SOLVED ) if status - fva_min[model.reactions[i].id] = map_fluxes(v, model) + fva_min[model.reactions[i].id] = Dict(zip(reactions(model), value.(cbm[:x]))) else @warn "Error minimizing index: $i with error $(termination_status(cbm))" + fva_min[model.reactions[i].id] = nothing end end diff --git a/src/analysis/pfba.jl b/src/analysis/pfba.jl index e23f423115ae546e7ce71c40179d7984f15e2aa2..5b611222326055e371fbf03fa13e098aa0aa3fed 100644 --- a/src/analysis/pfba.jl +++ b/src/analysis/pfba.jl @@ -1,15 +1,15 @@ """ - pfba(model::CobraModel, optimizer; objective_func::Union{Reaction, Array{Reaction, 1}}=Reaction[], weights=Float64[], solver_attributes=Dict{Any, Any}(), constraints=Dict{String, Tuple{Float64,Float64}}(), sense=MOI.MAX_SENSE) + pfba(model::StandardModel, optimizer; modifications, qp_solver, qp_solver_attributes) -Run parsimonious flux balance analysis (pFBA) on the `model` with `objective_rxn(s)` and optionally specifying their `weights` (empty `weights` mean equal weighting per reaction) for the initial FBA problem. +Run parsimonious flux balance analysis (pFBA) on the `model`. Note, the `optimizer` must be set to perform the analysis, any JuMP solver will work. -Optionally also specify any additional flux constraints with `constraints`, a dictionary mapping reaction `id`s to tuples of (lb, ub) flux constraints. -When `optimizer` is an array of optimizers, e.g. `[opt1, opt2]`, then `opt1` is used to solve the FBA problem, and `opt2` is used to solve the QP problem. -This strategy is useful when the QP solver is not good at solving the LP problem. -The `solver_attributes` can also be specified in the form of a dictionary where each (key, value) pair will be passed to `set_optimizer_attribute(cbmodel, k, v)`. -If more than one solver is specified in `optimizer`, then `solver_attributes` must be a dictionary of dictionaries with keys "opt1" and "opt2", e.g. Dict("opt1" => Dict{Any, Any}(),"opt2" => Dict{Any, Any}()). -This function builds the optimization problem from the model, and hence uses the constraints implied by the model object. -Returns a dictionary of reaction `id`s mapped to fluxes if solved successfully, otherwise an empty dictionary. +Optionally, specify problem `modifications` as in [`flux_balance_analysis`](@ref). +Also, `qp_solver` can be set to be different from `optimizer`, where the latter is then the LP optimizer only. +Also note that `qp_solver_attributes` is meant to set the attributes for the `qp_solver` and NOT `modifications`. +Any solver attributes changed in `modifications` will only affect he LP solver. +This function automatically relaxes the constraint that the FBA solution objective matches the pFBA solution. +This is iteratively relaxed like 1.0, 0.999999, 0.9999, etc. of the bound until 0.99 when the function fails and returns nothing. +Return a solved pFBA model. # Example ``` @@ -20,116 +20,67 @@ biomass = findfirst(model.reactions, "BIOMASS_Ec_iJO1366_WT_53p95M") sol = pfba(model, biomass, optimizer; solver_attributes=atts) ``` """ -function pfba( - model::CobraModel, - optimizer; - modifications = [(model, opt_model) -> nothing] -) - ## FBA ################################################ +function parsimonious_flux_balance_analysis(model::StandardModel, optimizer; modifications = [(model, opt_model) -> nothing], qp_solver = [(model, opt_model) -> nothing], qp_solver_attributes=[(model, opt_model) -> nothing]) + # Run FBA + cbm = flux_balance_analysis(model, optimizer)#; modifications=modifications) + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing # FBA failed + + # Run pFBA + λ = objective_value(cbm) + old_objective = JuMP.objective_function(cbm) - if typeof(optimizer) <: AbstractArray # choose optimizer - cbm = make_optimization_model(model, optimizer[1], sense = sense) - v = cbm[:x] - if !isempty(solver_attributes["opt1"]) # set other attributes - for (k, v) in solver_attributes["opt1"] - set_optimizer_attribute(cbm, k, v) - end - end - else # singe optimizer - cbm = make_optimization_model(model, optimizer, sense = sense) - v = cbm[:x] - if !isempty(solver_attributes) # set other attributes - for (k, v) in solver_attributes - set_optimizer_attribute(cbm, k, v) - end + qp_solver(model, cbm) # change the solver if specified, otherwise default argument does nothing + if typeof(qp_solver_attributes) <: Vector # many modifications + for mod in qp_solver_attributes + mod(model, cbm) end + else # single modification + qp_solver_attributes(model, cbm) end - # set additional constraints - for (rxnid, con) in constraints - ind = model.reactions[findfirst(model.reactions, rxnid)] - set_bound(ind, cbm; lb = con[1], ub = con[2]) - end - - # check if default objective should be used - if typeof(objective_func) == Reaction || !isempty(objective_func) - # check if an array of objective indices are fed in - if typeof(objective_func) == Reaction - objective_indices = [model[objective_func]] - else - objective_indices = [model[rxn] for rxn in objective_func] - end - - if isempty(weights) - weights = ones(length(objective_indices)) - end - opt_weights = zeros(length(model.reactions)) - - wcounter = 1 - for i in eachindex(model.reactions) - if i in objective_indices - # model.reactions[i].objective_coefficient = weights[wcounter] - opt_weights[i] = weights[wcounter] - wcounter += 1 - # else - # model.reactions[i].objective_coefficient = 0.0 - end - end + v = cbm[:x] # fluxes + @objective(cbm, Min, sum(dot(v, v))) - @objective(cbm, sense, sum(opt_weights[i] * v[i] for i in objective_indices)) - else - # objective_indices = findnz(objective(model)) - # opt_weights = ones(length(objective_indices)) # assume equal weighting, assume sense is max + for lbconval in [1.0, 0.999999, 0.99999, 0.9999, 0.999, 0.99] # sequentially relax bound for stability + @constraint( + cbm, + pfbacon, + lbconval * λ <= old_objective <= λ * 1.0/lbconval # in case of negative objective and minimized fba + ) + optimize!(cbm) + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] && break + COBREXA.JuMP.delete(cbm, pfbacon) end - optimize!(cbm) - - fba_status = ( - termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED - ) - - ## pFBA ############################################### - λ = objective_value(cbm) - - if typeof(optimizer) <: AbstractArray # choose optimizer - set_optimizer(cbm, optimizer[2]) - if !isempty(solver_attributes["opt2"]) # set other attributes - for (k, v) in solver_attributes["opt2"] - set_optimizer_attribute(cbm, k, v) - end - end - end + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing # pFBA failed - @constraint( - cbm, - pfbacon, - λ <= sum(opt_weights[i] * v[i] for i in objective_indices) <= λ - ) - @objective(cbm, Min, sum(dot(v, v))) + return cbm +end - optimize!(cbm) +""" + parsimonious_flux_balance_analysis_dict(model::StandardModel, optimizer; modifications, qp_solver, qp_solver_attributes) - for lbconval in [0.999999, 0.99999, 0.9999, 0.999, 0.99] # relax bound for stability - if termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED # try to relax bound if failed optimization - break - else - COBREXA.JuMP.delete(cbm, pfbacon) - @constraint(cbm, lbconval * λ <= sum(v[i] for i in objective_indices) <= λ) - optimize!(cbm) - end - end +Perform parsimonious flux balance analysis on `model` using `optimizer`. +Returns a vector of fluxes in the same order as the reactions in `model`. +Calls [`parsimonious_flux_balance_analysis_dict`](@ref) internally. +""" +function parsimonious_flux_balance_analysis_vec(model::StandardModel, optimizer; modifications = [(model, opt_model) -> nothing], qp_solver = [(model, opt_model) -> nothing], qp_solver_attributes=[(model, opt_model) -> nothing]) + cbm = parsimonious_flux_balance_analysis(model, optimizer; modifications=modifications, qp_solver=qp_solver, qp_solver_attributes=qp_solver_attributes) + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing + + return value.(cbm[:x]) +end - pfba_status = ( - termination_status(cbm) == MOI.OPTIMAL || - termination_status(cbm) == MOI.LOCALLY_SOLVED - ) +""" + parsimonious_flux_balance_analysis_dict(model::StandardModel, optimizer; modifications, qp_solver, qp_solver_attributes) - if fba_status && pfba_status - return map_fluxes(v, model) - else - @warn "Optimization issues occurred." - return Dict{String,Float64}() # something went wrong - end +Perform parsimonious flux balance analysis on `model` using `optimizer`. +Returns a dictionary mapping reaction `id`s to fluxes. +Calls [`parsimonious_flux_balance_analysis_dict`](@ref) internally. +""" +function parsimonious_flux_balance_analysis_dict(model::StandardModel, optimizer; modifications = [(model, opt_model) -> nothing], qp_solver = [(model, opt_model) -> nothing], qp_solver_attributes=[(model, opt_model) -> nothing]) + cbm = parsimonious_flux_balance_analysis(model, optimizer; modifications=modifications, qp_solver=qp_solver, qp_solver_attributes=qp_solver_attributes) + JuMP.termination_status(cbm) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED] || return nothing + + return Dict(zip(reactions(model), value.(cbm[:x]))) end diff --git a/src/analysis/fluxes.jl b/src/analysis/utilities.jl similarity index 50% rename from src/analysis/fluxes.jl rename to src/analysis/utilities.jl index 4d9db2f15bced0de35c106955803722b80530983..c23b9988c33c0d65a4b6db8184061cffe1aad340 100644 --- a/src/analysis/fluxes.jl +++ b/src/analysis/utilities.jl @@ -1,33 +1,10 @@ """ - map_fluxes(v, model::CobraModel) - -Map fluxes from an optimization problem (`v`) to rxns in a model. -`v` can be a JuMP object (fluxes) or an array of Float64 fluxes. -Assumes they are in order of `model.reactions`, which they should be since the optimization problem is constructed from the model. -""" -function map_fluxes(v::Array{Float64,1}, model::CobraModel) - rxndict = Dict{String,Float64}() - for i in eachindex(model.reactions) - rxndict[model.reactions[i].id] = v[i] - end - return rxndict -end - -function map_fluxes(v::Array{VariableRef,1}, model::CobraModel) - rxndict = Dict{String,Float64}() - for i in eachindex(model.reactions) - rxndict[model.reactions[i].id] = value(v[i]) - end - return rxndict -end - -""" - atom_exchange(flux_dict::Dict{String, Float64}, model::CobraModel) + atom_exchange(flux_dict::Dict{String, Float64}, model::StandardModel) Return a dictionary mapping the flux of atoms across the boundary of the model given `flux_dict` of reactions in `model`. Here `flux_dict` is a mapping of reaction `id`s to fluxes, e.g. from FBA. """ -function atom_exchange(flux_dict::Dict{String,Float64}, model::CobraModel) +function atom_exchange(flux_dict::Dict{String,Float64}, model::StandardModel) atom_flux = Dict{String,Float64}() for (rxnid, flux) in flux_dict if startswith(rxnid, "EX_") || startswith(rxnid, "DM_") # exchange, demand reaction @@ -101,11 +78,11 @@ function exchange_reactions( end """ - metabolite_fluxes(fluxdict::Dict{String, Float64}, model::CobraModel) + metabolite_fluxes(fluxdict::Dict{String, Float64}, model::StandardModel) Return two dictionaries of metabolite `id`s mapped to reactions that consume or produce them given the flux distribution supplied in `fluxdict`. """ -function metabolite_fluxes(fluxdict::Dict{String,Float64}, model::CobraModel) +function metabolite_fluxes(fluxdict::Dict{String,Float64}, model::StandardModel) S = Array(stoichiometry(model)) met_flux = Dict{String,Float64}() rxnids = reactions(model) @@ -134,3 +111,110 @@ function metabolite_fluxes(fluxdict::Dict{String,Float64}, model::CobraModel) end return consuming, producing end + +""" + set_bound(index, optimization_model; ub=1000, lb=-1000) +Helper function to set the bounds of variables. +The JuMP `set_normalized_rhs` function is a little confusing, so this function simplifies setting +constraints. Just supply the constraint `index` and the model and they will be set to `ub` and `lb`. +""" +function set_bound(vind, opt_model; ub = 1000, lb = -1000) + if lb <= 0 + set_normalized_rhs(opt_model[:lbs][vind], abs(lb)) + else + set_normalized_rhs(opt_model[:lbs][vind], -abs(lb)) + end + set_normalized_rhs(opt_model[:ubs][vind], ub) +end + +""" + modify_constraint(reaction::Reaction, lb, ub) + +Modify constraints of model reaction. +""" +function modify_constraint(reaction::Reaction, lb, ub) + (model, opt_model) -> begin + ind = model.reactions[reaction] + set_bound(ind, opt_model, lb = lb, ub = ub) + end +end + +""" + modify_solver_attribute(option_key, option_val) + +Modify a solver attribute. These attributes are solver specific, +refer the either JuMP or the solver you are using's documentation. +""" +function modify_solver_attribute(option_key, option_val) + (model, opt_model) -> begin + JuMP.set_optimizer_attribute(opt_model, option_key, option_val) + end +end + +""" + modify_sense(objective_sense) + +Modify the objective sense. +Possible arguments are `MOI.MAX_SENSE` and `MOI.MIN_SENSE`. +Note, [`modify_objective`](@ref) sets the sense of the objective, +so it doesn't make sense to use this function AND [`modify_objective`](@ref) simultaneously. +""" +function modify_sense(objective_sense) + (model, opt_model) -> begin + JuMP.set_objective_sense(opt_model, objective_sense) + end +end + +""" + modify_objective(objective_functions::Union{Reaction, Array{Reaction, 1}}; weights=[], sense=MOI.MAX_SENSE) + +Callback function to modify the objective function used in a constraint based analysis function. +`objective_functions` can be a single reaction or an array of reactions (of type `Reaction`). +Optionally specify their `weights` and the sense of the new objective (`MOI.MAX_SENSE`, `MOI.MIN_SENSE`). +Note, this function sets the sense of the objective. +""" +function modify_objective( + objective_functions::Union{Reaction,Array{Reaction,1}}; + weights = [], + sense = MOI.MAX_SENSE, +) + (model, opt_model) -> begin + + # Construct objective_indices array + if typeof(objective_functions) == Reaction + objective_indices = [model[objective_functions]] + else + objective_indices = [model[rxn] for rxn in objective_functions] + end + + # Initialize weights + opt_weights = zeros(length(model.reactions)) + + isempty(weights) && (weights = ones(length(objective_indices))) # equal weights + + wcounter = 1 + for i in eachindex(model.reactions) + if i in objective_indices + opt_weights[i] = weights[wcounter] + wcounter += 1 + end + end + + v = opt_model[:x] + @objective(opt_model, sense, sum(opt_weights[i] * v[i] for i in objective_indices)) + end +end + +""" + modify_solver(optimizer) + +Modify the solver used to solve the model. +Typically the solver is specified as a required argument. +This function is useful if the problem has multiple subparts that require different solvers. +See +""" +function modify_solver(optimizer) + (model, opt_model) -> begin + JuMP.set_optimizer(opt_model, optimizer) + end +end diff --git a/src/base/solver.jl b/src/base/solver.jl index fd7b6ab7dc8994e0f4fde6eeab92f9e9fecff12a..0a159d622cfafecbf961c5feeab02fea49a56778 100644 --- a/src/base/solver.jl +++ b/src/base/solver.jl @@ -29,7 +29,6 @@ function optimize_model( sense = MOI.MIN_SENSE, ) where {LM<:MetabolicModel} optimization_model = make_optimization_model(model, optimizer; sense = sense) - x = optimization_model[:x] JuMP.optimize!(optimization_model) return optimization_model end diff --git a/src/base/utilities.jl b/src/base/utilities.jl index e2d6d7233bd8234886c830f7f03c94f03f50bb34..59f202da7e1c259e25bac84fdb206e92d8e3b528 100644 --- a/src/base/utilities.jl +++ b/src/base/utilities.jl @@ -18,31 +18,3 @@ Base.isequal(model1::CoupledLinearModel, model2::CoupledLinearModel) = Base.copy(model::CoupledLinearModel) = CoupledLinearModel(model.lm, model.C, model.cl, model.cu) - - -""" - set_bound(index, optimization_model; ub=1000, lb=-1000) -Helper function to set the bounds of variables. -The JuMP `set_normalized_rhs` function is a little confusing, so this function simplifies setting -constraints. Just supply the constraint `index` and the model and they will be set to `ub` and `lb`. -""" -function set_bound(vind, opt_model; ub = 1000, lb = -1000) - if lb <= 0 - set_normalized_rhs(opt_model[:lbs][vind], abs(lb)) - else - set_normalized_rhs(opt_model[:lbs][vind], -abs(lb)) - end - set_normalized_rhs(opt_model[:ubs][vind], ub) -end - -""" - modify_constraint(reaction::Reaction, lb, ub) - -Modify constraints of model reaction. -""" -function modify_constraint(reaction::Reaction, lb, ub) - (model, opt_model) -> begin - ind = model.reactions[reaction] - set_bound(ind, opt_model, lb=lb, ub=ub) - end -end diff --git a/src/io/io.jl b/src/io/io.jl index 843d42ddee4001bc454fd90f4a01ac9630b195ef..ba12321a4848521db08670fdcbb61f6b14b65a37 100644 --- a/src/io/io.jl +++ b/src/io/io.jl @@ -1,7 +1,7 @@ """ read_model(file_location::String)) -Reads a model at `file_location` and returns a constraint based `model::CobraModel`. +Reads a model at `file_location` and returns a constraint based `model::StandardModel`. Currently supported formats include SBML (.xml), Matlab (.mat) and JSON (.json) models. The model format is inferred from the `file_location` extension. @@ -25,31 +25,31 @@ function read_model(file_location::String) model = read_json_model(file_location) catch err @error "JSON model reading error.\n$err" - model = CobraModel() + model = StandardModel() end elseif endswith(file_location, ".xml") try model = reconstruct_model_sbml(file_location) catch err @error "SBML model reading error.\n$err" - model = CobraModel() + model = StandardModel() end elseif endswith(file_location, ".mat") try model = read_matlab_model(file_location) catch err @error "Matlab model reading error.\n$err" - model = CobraModel() + model = StandardModel() end else @error "Model format not supported. The format is inferred from the file extension. Supported formats: *.mat, *.xml, *.json." - model = CobraModel() + model = StandardModel() end return model end """ - save_model(model::CobraModel, file_location::String) + save_model(model::StandardModel, file_location::String) Save model at `file_location`. Infers format from `file_location` extension. Supported formats include SBML (.xml), Matlab COBRA models (.mat) and JSON COBRA models (.json). @@ -57,7 +57,7 @@ Supported formats include SBML (.xml), Matlab COBRA models (.mat) and JSON COBRA Note, only the fields contained in model are saved. Make sure that information isn't lost between reading a model and writing a model (e.g. check gene reaction rules, notes and annotations). """ -function save_model(model::CobraModel, file_location::String) +function save_model(model::StandardModel, file_location::String) if endswith(file_location, ".json") write_json_model(model, file_location) elseif endswith(file_location, ".xml") @@ -126,13 +126,13 @@ function reconstruct_model_sbml(file_location::String) # m.species # m.compartments # return Model() - return CobraModel() + return StandardModel() end """ - save_sbml_model(model::CobraModel, file_location::String) + save_sbml_model(model::StandardModel, file_location::String) """ -function save_sbml_model(model::CobraModel, file_location::String) +function save_sbml_model(model::StandardModel, file_location::String) # To do... end diff --git a/src/io/json_reader.jl b/src/io/json_reader.jl index c767b97917c74235e99209d54d8f3160697d99aa..222604d363b8a9bb0f7ce0784d33ea4adb17c6e2 100644 --- a/src/io/json_reader.jl +++ b/src/io/json_reader.jl @@ -138,5 +138,5 @@ function read_json_model(file_location::String) ) end - return CobraModel(modelid, rxns, mets, genes) + return StandardModel(modelid, rxns, mets, genes) end diff --git a/src/io/json_writer.jl b/src/io/json_writer.jl index a0686de759348e54a0b2b1180d22c2adf6d719e5..3aac7c956fff13566df4cad3e8ac5e82827603a6 100644 --- a/src/io/json_writer.jl +++ b/src/io/json_writer.jl @@ -1,7 +1,7 @@ """ - save_json_model(model::CobraModel, file_location::String) + save_json_model(model::StandardModel, file_location::String) """ -function write_json_model(model::CobraModel, file_location::String) +function write_json_model(model::StandardModel, file_location::String) modeldict = Dict{String,Any}() modeldict["id"] = model.id diff --git a/src/io/m_reader.jl b/src/io/m_reader.jl index d1427c50b26a5e71f28dc305a9bc35bff1d7a8cd..8c5379244c4fbf737529cec5a1b63c805ca3eb77 100644 --- a/src/io/m_reader.jl +++ b/src/io/m_reader.jl @@ -136,5 +136,5 @@ function read_matlab_model(file_location::String) ) end - return CobraModel(model_id, rxns, mets, genes) + return StandardModel(model_id, rxns, mets, genes) end diff --git a/src/io/m_writer.jl b/src/io/m_writer.jl index b646e1211d0aae1d07e9d3fa523b97ea68baf985..3938bbc797ab24f4a1cdb976b51e7e3af3cc447d 100644 --- a/src/io/m_writer.jl +++ b/src/io/m_writer.jl @@ -1,9 +1,9 @@ """ - write_matlab_model(model::CobraModel, file_location::String) + write_matlab_model(model::StandardModel, file_location::String) Some information is lost here, e.g. notes and some annotations. """ -function write_matlab_model(model::CobraModel, file_location::String) +function write_matlab_model(model::StandardModel, file_location::String) S = stoichiometry(model) b = balance(model) lbs, ubs = bounds(model) diff --git a/src/reconstruction/model_manipulations.jl b/src/reconstruction/model_manipulations.jl index d6b6c3ed9abe128e09840de5995838e904441329..4aa977ef4d57f83deb69c3476b7eefe0171d3678 100644 --- a/src/reconstruction/model_manipulations.jl +++ b/src/reconstruction/model_manipulations.jl @@ -1,15 +1,15 @@ """ - add!(model::CobraModel, rxns::Union{Array{Reaction, 1}, Reaction}) + add!(model::StandardModel, rxns::Union{Array{Reaction, 1}, Reaction}) Add `rxn(s)` to `model` if they are not already present based on reaction `id`. """ -function add!(model::CobraModel, rxns::Array{Reaction,1}) +function add!(model::StandardModel, rxns::Array{Reaction,1}) for rxn in rxns add!(model, rxn) end end -function add!(model::CobraModel, rxn::Reaction) +function add!(model::StandardModel, rxn::Reaction) if model[rxn] == -1 push!(model.reactions, rxn) else @@ -19,17 +19,17 @@ function add!(model::CobraModel, rxn::Reaction) end """ - add!(model::CobraModel, mets::Union{Array{Metabolite, 1}, Metabolite}) + add!(model::StandardModel, mets::Union{Array{Metabolite, 1}, Metabolite}) Add `met(s)` to `model` if they are not already present, based on metabolite `id`. """ -function add!(model::CobraModel, mets::Array{Metabolite,1}) +function add!(model::StandardModel, mets::Array{Metabolite,1}) for met in mets add!(model, met) end end -function add!(model::CobraModel, met::Metabolite) +function add!(model::StandardModel, met::Metabolite) if model[met] == -1 push!(model.metabolites, met) else @@ -39,17 +39,17 @@ function add!(model::CobraModel, met::Metabolite) end """ - add!(model::CobraModel, genes::Union{Array{Gene, 1}, Gene}) + add!(model::StandardModel, genes::Union{Array{Gene, 1}, Gene}) Add `gene(s)` to `model` if they are not already present based on gene `id`. """ -function add!(model::CobraModel, genes::Array{Gene,1}) +function add!(model::StandardModel, genes::Array{Gene,1}) for gene in genes add!(model, gene) end end -function add!(model::CobraModel, gene::Gene) +function add!(model::StandardModel, gene::Gene) if model[gene] == -1 push!(model.genes, gene) else @@ -59,11 +59,11 @@ function add!(model::CobraModel, gene::Gene) end """ - rm!(model::CobraModel, rxns::Union{Array{Reaction, 1}, Reaction}) + rm!(model::StandardModel, rxns::Union{Array{Reaction, 1}, Reaction}) Remove all `rxn(s)` from `model` if the `id`s match those in `rxns`. """ -function rm!(model::CobraModel, rxns::Union{Array{Reaction,1},Reaction}) +function rm!(model::StandardModel, rxns::Union{Array{Reaction,1},Reaction}) new_rxn_list = Reaction[] for r in model.reactions if typeof(rxns) == Reaction @@ -81,11 +81,11 @@ function rm!(model::CobraModel, rxns::Union{Array{Reaction,1},Reaction}) end """ - rm!(model::CobraModel, mets::Union{Array{Metabolite, 1}, Metabolite}) + rm!(model::StandardModel, mets::Union{Array{Metabolite, 1}, Metabolite}) Remove `met(s)` from `model` based on metabolite `id`. """ -function rm!(model::CobraModel, mets::Union{Array{Metabolite,1},Metabolite}) +function rm!(model::StandardModel, mets::Union{Array{Metabolite,1},Metabolite}) new_met_list = Metabolite[] for m in model.metabolites if typeof(mets) == Metabolite @@ -103,11 +103,11 @@ function rm!(model::CobraModel, mets::Union{Array{Metabolite,1},Metabolite}) end """ - rm!(model::CobraModel, genes::Union{Array{Gene, 1}, Gene}) + rm!(model::StandardModel, genes::Union{Array{Gene, 1}, Gene}) Remove `gene(s)` from `model` based on gene `id`. """ -function rm!(model::CobraModel, genes::Union{Array{Gene,1},Gene}) +function rm!(model::StandardModel, genes::Union{Array{Gene,1},Gene}) new_gene_list = Gene[] for g in model.genes if typeof(genes) == Gene @@ -125,7 +125,7 @@ function rm!(model::CobraModel, genes::Union{Array{Gene,1},Gene}) end """ - fix_model!(model::CobraModel) + fix_model!(model::StandardModel) Inspect metabolites and genes of `model` relative to the reactions of `model`. Remove genes or metabolites that are not used in the reactions. @@ -135,7 +135,7 @@ a different `id`. Other functions are provided to help identify these cases. See also: [`check_duplicate_annotations`](@ref), [`check_duplicate_reaction`](@ref), [`check_same_formula`](@ref). """ -function fix_model!(model::CobraModel) +function fix_model!(model::StandardModel) rxn_mets = Metabolite[] # list of metabolites used in reactions for rxn in model.reactions for met in keys(rxn.metabolites) diff --git a/src/sampling/sampling_tools.jl b/src/sampling/sampling_tools.jl index 90551e75f37a9118c806d39a919d72649bee5e61..991965565e2951ad72805b155da469250ad64e7b 100644 --- a/src/sampling/sampling_tools.jl +++ b/src/sampling/sampling_tools.jl @@ -3,17 +3,21 @@ Generate warmup points for all the reactions in the model that are not fixed. Assumes you feed in a JuMP model that is already -constrained i.e. the constrains are already applied into cbm. -Note, extra constraints applied to ubs and lbs will have no effect. +constrained i.e. the constraints are already applied to `cbm`. -numstop (maximum value is 2*number of warmup points). Reduce this to prematurely -stop finding warmup points. +If you do not want to generate all possible warmup points +(2*n where n is the number of non-fixed variables), then reduce `numstop` +to the desired number of warmup points. This number of warmup points will +be randomly drawn from the non-fixed fluxes. + +By default each flux will be minimized and maximized, however, random objectives +can be used by setting `random_objective` true. """ function get_warmup_points(cbm; random_objective = false, numstop = 1e10) v = cbm[:x] ubs = cbm[:ubs] lbs = cbm[:lbs] - # determine which rxns should be max/min-ized + # determine which rxns should be max/min-ized (non fixed fluxes) fixed_rxns = Int64[] for i in eachindex(v) ub_val = normalized_rhs(ubs[i]) @@ -25,11 +29,12 @@ function get_warmup_points(cbm; random_objective = false, numstop = 1e10) end end - # determine number of warmup points + # determine number of warmup points (if numstops is less than the number of non-fixed reactions.) var_rxn_inds = shuffle!(filter(x -> !(x in fixed_rxns), 1:length(v))) # shuffle the optimization points NN = numstop > length(var_rxn_inds) ? length(var_rxn_inds) : numstop wpoints = zeros(length(v), 2 * NN) + # generate warmup points. for (i, ii) in zip(1:length(var_rxn_inds), 1:2:(2*length(var_rxn_inds))) i > NN && break @@ -62,7 +67,8 @@ end """ get_bound_vectors(opt_model) -Return Float64 vectors of the lower and upper bounds of the JuMP constraint refs. +Return Float64 vectors of the lower and upper bounds of `opt_model` constraints, +which is a JuMP model. """ function get_bound_vectors(opt_model) lbconref = opt_model[:lbs] @@ -77,17 +83,19 @@ function get_bound_vectors(opt_model) end end ubs = [normalized_rhs(ubconref[i]) for i in eachindex(ubconref)] + return lbs, ubs end """ - hit_and_run(N::Int64, model::CobraModel, optimizer; constraints=Dict{String, Tuple{Float64,Float64}}(), keepevery=100, samplesize=1000, solver_attributes=Dict{Any, Any}(), random_objective=false) + hit_and_run(N::Int64, cbm; keepevery=100, samplesize=1000, random_objective=false) -Perform basic hit and run sampling for `N` iterations using `model` with `optimizer` from `JuMP`. -Additional constraints supplied by `constraints` as a dictionary of reaction `id`s mapped to a tuple of `(lb, ub)` of fluxes. +Perform basic hit and run sampling for `N` iterations on `cbm`. +Note that `cbm` is a JuMP model that contains whatever solver will be used to find the warmup points. +Also note, `cbm` should already be fully constrained by however you desire. Every `keepevery` iteration is logged as a sample, where the sample size matrix has `samplesize` columns. -Solver specific settings can be set using `solver_attributes`. -Warm up points are generated in a flux variability sense, unless `random_objective` is true, in which case a randomly weighted objective is used 2*number of reactions to define the warmup points. +Warm up points are generated in a flux variability sense, unless `random_objective` is true, +in which case a randomly weighted objective is used 2*number of reactions to define the warmup points. Note that N needs to be >> samplesize. Sample size is the size of the samples kept in memory. @@ -97,31 +105,11 @@ See also: [`achr`](@ref) """ function hit_and_run( N::Int64, - model::CobraModel, - optimizer; - constraints = Dict{String,Tuple{Float64,Float64}}(), + cbm; keepevery = 100, samplesize = 1000, - solver_attributes = Dict{Any,Any}(), random_objective = false, - sense = MOI.MAX_SENSE, ) - # get core optimization problem - cbm = make_optimization_model(model, optimizer, sense = sense) - v = cbm[:x] - - if !isempty(solver_attributes) # set other attributes - for (k, v) in solver_attributes - set_optimizer_attribute(cbm, k, v) - end - end - - # set additional constraints - for (rxnid, con) in constraints - ind = model.reactions[findfirst(model.reactions, rxnid)] - set_bound(ind, cbm; lb = con[1], ub = con[2]) - end - lbs, ubs = get_bound_vectors(cbm) # get actual ub and lb constraints, can't use model function because the user may have changed them in the function arguments wpoints = get_warmup_points(cbm; random_objective = random_objective) @@ -185,25 +173,19 @@ function hit_and_run( end - violation_inds = test_samples(samples, model, ubs, lbs) - if !isempty(violation_inds) - @warn "Samples: $violation_inds do not satisfy the problem constraints." - end - return samples end """ - test_samples(samples::Array{Float64, 2}, model::CobraModel, ubs, lbs) + test_samples(samples::Array{Float64, 2}, model::StandardModel, ubs, lbs) Test if samples pass tests: mass balances and constraints are satisfied.. """ -function test_samples(samples::Array{Float64,2}, model::CobraModel, ubs, lbs) - S = stoichiometry(model) # assume S has not been modified from model AND b is zero +function test_samples(samples::Array{Float64,2}, mass_balance, balance, lbs, ubs) violations = Int64[] tol = 1e-6 for i = 1:size(samples, 2) - if sum(abs, S * samples[:, i]) < tol + if sum(abs, mass_balance * samples[:, i] - balance) < tol equality = true else equality = false @@ -219,106 +201,106 @@ function test_samples(samples::Array{Float64,2}, model::CobraModel, ubs, lbs) return violations end -""" - achr(N::Int64, model::CobraModel, optimizer;constraints=Dict{String, Tuple{Float64,Float64}}(), keepevery=100, samplesize=1000, solver_attributes=Dict{Any, Any}(), random_objective=false) - -Perform artificially centered hit and run. -Uses the same arguments as the `hit_and_run` sampler. -Needs work, for long iterations it becomes unstable (violates bounds). - -See also: [`hit_and_run`](@ref) -""" -function achr( - N::Int64, - model::CobraModel, - optimizer; - constraints = Dict{String,Tuple{Float64,Float64}}(), - keepevery = 100, - samplesize = 1000, - solver_attributes = Dict{Any,Any}(), - random_objective = false, -) - cbm, v, _, ubcons, lbcons = build_cbm(model) - - set_optimizer(cbm, optimizer) # choose optimizer - if !isempty(solver_attributes) # set other attributes - for (k, v) in solver_attributes - set_optimizer_attribute(cbm, k, v) - end - end - - # set additional constraints - for (rxnid, con) in constraints - ind = model.reactions[findfirst(model.reactions, rxnid)] - set_bound(ind, ubcons, lbcons; ub = con[1], lb = con[2]) - end - - ubs, lbs = get_bound_vectors(ubcons, lbcons) # get actual ub and lb constraints - - wpoints = get_warmup_points(cbm, v, ubcons, lbcons, random_objective = random_objective) - - nwpts = size(wpoints, 2) # number of warmup points generated - samples = zeros(size(wpoints, 1), samplesize) # sample storage - current_point = zeros(size(wpoints, 1)) - current_point .= wpoints[:, rand(1:nwpts)] # pick random initial point - - shat = mean(wpoints, dims = 2)[:] # mean point - - δdirtol = 1e-6 # too small directions get ignored ≈ 0 (solver precision issue) - sample_num = 0 - samplelength = 0 - updatesamplesizelength = true - for n = 1:N - if updatesamplesizelength # switch to samples - direction_point = (@view wpoints[:, rand(1:nwpts)]) - (@view current_point[:]) # use warmup points to find direction in warmup phase - else - direction_point = (@view samples[:, rand(1:(samplelength))]) - (@view shat[:]) # after warmup phase, only find directions in sampled space - end - - λmax = 1e10 - λmin = -1e10 - for i in eachindex(lbs) - δlower = lbs[i] - current_point[i] - δupper = ubs[i] - current_point[i] - if direction_point[i] < -δdirtol - lower = δupper / direction_point[i] - upper = δlower / direction_point[i] - elseif direction_point[i] > δdirtol - lower = δlower / direction_point[i] - upper = δupper / direction_point[i] - else - lower = -1e10 - upper = 1e10 - end - lower > λmin && (λmin = lower) # max min step size that satisfies all bounds - upper < λmax && (λmax = upper) # min max step size that satisfies all bounds - end - - if λmax <= λmin || λmin == -1e10 || λmax == 1e10 # this sometimes can happen - @warn "Infeasible direction at iteration $(n)..." - continue - end - - λ = rand() * (λmax - λmin) + λmin # random step size - current_point .= current_point .+ λ .* direction_point # will be feasible - shat .= (n .* shat .+ current_point) ./ (n + 1) - - if n % keepevery == 0 - sample_num += 1 - samples[:, sample_num] .= current_point - if sample_num >= samplesize - updatesamplesizelength = false - sample_num = 0 # reset, start replacing the older samples - end - updatesamplesizelength && (samplelength += 1) # lags sample_num because the latter is a flag as well - end - - end - - violation_inds = test_samples(samples, model, ubs, lbs) - if !isempty(violation_inds) - @warn "Samples: $violation_inds do not satisfy the problem constraints." - end - - return samples -end +# """ +# achr(N::Int64, model::StandardModel, optimizer;constraints=Dict{String, Tuple{Float64,Float64}}(), keepevery=100, samplesize=1000, solver_attributes=Dict{Any, Any}(), random_objective=false) + +# Perform artificially centered hit and run. +# Uses the same arguments as the `hit_and_run` sampler. +# Needs work, for long iterations it becomes unstable (violates bounds). + +# See also: [`hit_and_run`](@ref) +# """ +# function achr( +# N::Int64, +# model::StandardModel, +# optimizer; +# constraints = Dict{String,Tuple{Float64,Float64}}(), +# keepevery = 100, +# samplesize = 1000, +# solver_attributes = Dict{Any,Any}(), +# random_objective = false, +# ) +# cbm, v, _, ubcons, lbcons = build_cbm(model) + +# set_optimizer(cbm, optimizer) # choose optimizer +# if !isempty(solver_attributes) # set other attributes +# for (k, v) in solver_attributes +# set_optimizer_attribute(cbm, k, v) +# end +# end + +# # set additional constraints +# for (rxnid, con) in constraints +# ind = model.reactions[findfirst(model.reactions, rxnid)] +# set_bound(ind, ubcons, lbcons; ub = con[1], lb = con[2]) +# end + +# ubs, lbs = get_bound_vectors(ubcons, lbcons) # get actual ub and lb constraints + +# wpoints = get_warmup_points(cbm, v, ubcons, lbcons, random_objective = random_objective) + +# nwpts = size(wpoints, 2) # number of warmup points generated +# samples = zeros(size(wpoints, 1), samplesize) # sample storage +# current_point = zeros(size(wpoints, 1)) +# current_point .= wpoints[:, rand(1:nwpts)] # pick random initial point + +# shat = mean(wpoints, dims = 2)[:] # mean point + +# δdirtol = 1e-6 # too small directions get ignored ≈ 0 (solver precision issue) +# sample_num = 0 +# samplelength = 0 +# updatesamplesizelength = true +# for n = 1:N +# if updatesamplesizelength # switch to samples +# direction_point = (@view wpoints[:, rand(1:nwpts)]) - (@view current_point[:]) # use warmup points to find direction in warmup phase +# else +# direction_point = (@view samples[:, rand(1:(samplelength))]) - (@view shat[:]) # after warmup phase, only find directions in sampled space +# end + +# λmax = 1e10 +# λmin = -1e10 +# for i in eachindex(lbs) +# δlower = lbs[i] - current_point[i] +# δupper = ubs[i] - current_point[i] +# if direction_point[i] < -δdirtol +# lower = δupper / direction_point[i] +# upper = δlower / direction_point[i] +# elseif direction_point[i] > δdirtol +# lower = δlower / direction_point[i] +# upper = δupper / direction_point[i] +# else +# lower = -1e10 +# upper = 1e10 +# end +# lower > λmin && (λmin = lower) # max min step size that satisfies all bounds +# upper < λmax && (λmax = upper) # min max step size that satisfies all bounds +# end + +# if λmax <= λmin || λmin == -1e10 || λmax == 1e10 # this sometimes can happen +# @warn "Infeasible direction at iteration $(n)..." +# continue +# end + +# λ = rand() * (λmax - λmin) + λmin # random step size +# current_point .= current_point .+ λ .* direction_point # will be feasible +# shat .= (n .* shat .+ current_point) ./ (n + 1) + +# if n % keepevery == 0 +# sample_num += 1 +# samples[:, sample_num] .= current_point +# if sample_num >= samplesize +# updatesamplesizelength = false +# sample_num = 0 # reset, start replacing the older samples +# end +# updatesamplesizelength && (samplelength += 1) # lags sample_num because the latter is a flag as well +# end + +# end + +# violation_inds = test_samples(samples, model, ubs, lbs) +# if !isempty(violation_inds) +# @warn "Samples: $violation_inds do not satisfy the problem constraints." +# end + +# return samples +# end diff --git a/src/types/agene.jl b/src/types/gene.jl similarity index 100% rename from src/types/agene.jl rename to src/types/gene.jl diff --git a/src/types/ametabolite.jl b/src/types/metabolite.jl similarity index 100% rename from src/types/ametabolite.jl rename to src/types/metabolite.jl diff --git a/src/types/areaction.jl b/src/types/reaction.jl similarity index 100% rename from src/types/areaction.jl rename to src/types/reaction.jl diff --git a/src/types/metabolicModel.jl b/src/types/standardModel.jl similarity index 68% rename from src/types/metabolicModel.jl rename to src/types/standardModel.jl index f2c354d418f0e5354ed9d2662fde076127e26488..36a70c14b27f7b461e970e2a40df4d71836e4da8 100644 --- a/src/types/metabolicModel.jl +++ b/src/types/standardModel.jl @@ -1,5 +1,5 @@ """ -CobraModel struct of a constraint based metabolic model. +StandardModel struct of a constraint based metabolic model. # Fields ```` @@ -9,7 +9,7 @@ metabolites :: Array{Metabolite, 1} genes :: Array{Gene, 1} ```` """ -mutable struct CobraModel <: MetabolicModel +mutable struct StandardModel <: MetabolicModel id::String reactions::Array{Reaction,1} metabolites::Array{Metabolite,1} @@ -17,9 +17,9 @@ mutable struct CobraModel <: MetabolicModel end """ -Pretty printing of model::CobraModel. +Pretty printing of model::StandardModel. """ -function Base.show(io::IO, ::MIME"text/plain", m::CobraModel) +function Base.show(io::IO, ::MIME"text/plain", m::StandardModel) println( io, "Constraint based model: ", @@ -37,61 +37,61 @@ function Base.show(io::IO, ::MIME"text/plain", m::CobraModel) end """ -CobraModel() +StandardModel() Empty model constructor. """ -function CobraModel() - CobraModel("blank", Array{Reaction,1}(), Array{Metabolite,1}(), Array{Gene,1}()) +function StandardModel() + StandardModel("blank", Array{Reaction,1}(), Array{Metabolite,1}(), Array{Gene,1}()) end """ - getindex(model::CobraModel, rxn::Reaction) + getindex(model::StandardModel, rxn::Reaction) Get the index of `rxn` in `model`, based on reaction `id`. Return -1 if not found. Typical usage: ind = model[rxn] """ -function Base.getindex(model::CobraModel, rxn::Reaction) +function Base.getindex(model::StandardModel, rxn::Reaction) return model.reactions[rxn] end """ - getindex(model::CobraModel, met::Metabolite) + getindex(model::StandardModel, met::Metabolite) Get the index of `met` in `model`, based on metabolite `id`. Return -1 if not found. Typical usage: ind = model[met] """ -function Base.getindex(model::CobraModel, met::Metabolite) +function Base.getindex(model::StandardModel, met::Metabolite) return model.metabolites[met] end """ - getindex(model::CobraModel, gene::Gene) + getindex(model::StandardModel, gene::Gene) Get the index of `gene` in `model`, based on gene `id`. Return -1 if not found. Typical usage: ind = model[gene] """ -function Base.getindex(model::CobraModel, gene::Gene) +function Base.getindex(model::StandardModel, gene::Gene) return model.genes[gene] end # generic interface functions -function reactions(model::CobraModel)::Vector{String} +function reactions(model::StandardModel)::Vector{String} [r.id for r in model.reactions] end -function metabolites(model::CobraModel)::Vector{String} +function metabolites(model::StandardModel)::Vector{String} [m.id for m in model.metabolites] end -function stoichiometry(model::CobraModel)::SparseMat +function stoichiometry(model::StandardModel)::SparseMat S = SparseArrays.spzeros(length(model.metabolites), length(model.reactions)) metids = metabolites(model) for (i, rxn) in enumerate(model.reactions) # column @@ -106,17 +106,17 @@ function stoichiometry(model::CobraModel)::SparseMat return S end -function bounds(model::CobraModel)::Tuple{SparseVec,SparseVec} +function bounds(model::StandardModel)::Tuple{SparseVec,SparseVec} ubs = [rxn.ub for rxn in model.reactions] lbs = [rxn.lb for rxn in model.reactions] return lbs, ubs end -function balance(model::CobraModel)::SparseVec +function balance(model::StandardModel)::SparseVec SparseArrays.spzeros(length(model.metabolites)) end -function objective(model::CobraModel)::SparseVec +function objective(model::StandardModel)::SparseVec obj_arr = SparseArrays.spzeros(length(model.reactions)) j = -1 for (i, r) in enumerate(model.reactions) diff --git a/test/analysis/fba.jl b/test/analysis/fba.jl index 4e89af7b5cf72bf4813216002029669f6927571c..86fa3bca8fd79746e9e223f874b23960959ae18e 100644 --- a/test/analysis/fba.jl +++ b/test/analysis/fba.jl @@ -1,23 +1,20 @@ -@testset "Flux balance analysis" begin +@testset "Flux balance analysis with LinearModel" begin cp = test_simpleLP() lp = flux_balance_analysis(cp, GLPK.Optimizer) - x = lp[:x] @test termination_status(lp) === MOI.OPTIMAL - sol = COBREXA.JuMP.value.(x) + sol = JuMP.value.(lp[:x]) @test sol ≈ [1.0, 2.0] lp = flux_balance_analysis(cp, Clp.Optimizer) - x = lp[:x] @test termination_status(lp) === MOI.OPTIMAL - sol = COBREXA.JuMP.value.(x) + sol = JuMP.value.(lp[:x]) @test sol ≈ [1.0, 2.0] # test the maximization of the objective cp = test_simpleLP2() lp = flux_balance_analysis(cp, GLPK.Optimizer) - x = lp[:x] @test termination_status(lp) === MOI.OPTIMAL - sol = COBREXA.JuMP.value.(x) + sol = JuMP.value.(lp[:x]) @test sol ≈ [-1.0, 2.0] # test with a more biologically meaningfull model @@ -31,9 +28,8 @@ expected_optimum = 0.9219480950504393 lp = flux_balance_analysis(cp, GLPK.Optimizer) - x = lp[:x] @test termination_status(lp) === MOI.OPTIMAL - sol = COBREXA.JuMP.value.(x) + sol = JuMP.value.(lp[:x]) @test objective_value(lp) ≈ expected_optimum @test cp.c' * sol ≈ expected_optimum @@ -45,7 +41,7 @@ @test all([fluxes_dict[rxns[i]] == sol[i] for i in eachindex(rxns)]) end -@testset "Flux balance analysis with CobraModel" begin +@testset "Flux balance analysis with StandardModel" begin model = read_model( download_data_file( "http://bigg.ucsd.edu/static/models/e_coli_core.json", @@ -53,49 +49,15 @@ end "7bedec10576cfe935b19218dc881f3fb14f890a1871448fc19a9b4ee15b448d8", ), ) - @test length(model.reactions) == 95 # read in correctly - # FBA biomass = findfirst(model.reactions, "BIOMASS_Ecoli_core_w_GAM") - cons = Dict("EX_glc__D_e" => (-12.0, -12.0)) - optimizer = COBREXA.Tulip.Optimizer # quiet by default - sol = fba(model, optimizer; objective_func = biomass, constraints = cons) - pfl = findfirst(model.reactions, "PFL") - solmulti = fba(model, optimizer; objective_func = [biomass, pfl], weights = [0.8, 0.2]) # classic flux balance analysis - - - flux_vec = [sol[rxn.id] for rxn in model.reactions] - sol_mapped = map_fluxes(flux_vec, model) - @test isapprox(sol_mapped["BIOMASS_Ecoli_core_w_GAM"], 1.0572509997013568, atol = 1e-6) + glucose = findfirst(model.reactions, "EX_glc__D_e") + sol = flux_balance_analysis_dict(model, Tulip.Optimizer; modifications=[modify_objective(biomass), modify_constraint(glucose, -12, -12), modify_sense(MOI.MAX_SENSE), modify_solver_attribute("IPM_IterationsLimit", 110)]) @test isapprox(sol["BIOMASS_Ecoli_core_w_GAM"], 1.0572509997013568, atol = 1e-6) - @test !isempty(solmulti) - - sol = fba(model, optimizer; objective_func = biomass) - # atom tracker - atom_fluxes = atom_exchange(sol, model) - @test isapprox(atom_fluxes["C"], -37.1902, atol = 1e-3) - - # exchange trackers - consuming, producing = exchange_reactions(sol; verbose = false) - @test isapprox(consuming["EX_nh4_e"], -4.76532, atol = 1e-3) - - # metabolite trackers - consuming, producing = metabolite_fluxes(sol, model) - @test isapprox(consuming["atp_c"]["PFK"], -7.47738, atol = 1e-3) - @test isapprox(producing["atp_c"]["PYK"], 1.75818, atol = 1e-3) - - # set bounds - cbm = make_optimization_model(model, optimizer) - ubs = cbm[:ubs] - lbs = cbm[:lbs] - glucose_index = model[findfirst(model.reactions, "EX_glc__D_e")] - o2_index = model[findfirst(model.reactions, "EX_o2_e")] - atpm_index = model[findfirst(model.reactions, "ATPM")] - set_bound(glucose_index, cbm; ub = -1.0, lb = -1.0) - @test normalized_rhs(ubs[glucose_index]) == -1.0 - @test normalized_rhs(lbs[glucose_index]) == 1.0 - set_bound(o2_index, cbm; ub = 1.0, lb = 1.0) - @test normalized_rhs(ubs[o2_index]) == 1.0 - @test normalized_rhs(lbs[o2_index]) == -1.0 + pfl = findfirst(model.reactions, "PFL") + pfl_frac = 0.8 + biomass_frac = 0.2 + sol_multi = flux_balance_analysis_dict(model, Tulip.Optimizer; modifications= modify_objective([biomass, pfl]; weights = [biomass_frac, pfl_frac])) + @test isapprox(biomass_frac*sol_multi["BIOMASS_Ecoli_core_w_GAM"] + pfl_frac*sol_multi["PFL"], 31.999999998962604, atol = 1e-6) end diff --git a/test/analysis/fva.jl b/test/analysis/fva.jl index 8c555dc8c2482d6ba3377a5c7396f81b23c68b80..17fa868ad3c249042b10cb8aebb866415afb3719 100644 --- a/test/analysis/fva.jl +++ b/test/analysis/fva.jl @@ -60,7 +60,7 @@ end rmprocs(pids) end -@testset "Flux variability analysis with CobraModel" begin +@testset "Flux variability analysis with StandardModel" begin model = read_model( download_data_file( "http://bigg.ucsd.edu/static/models/e_coli_core.json", @@ -68,28 +68,10 @@ end "7bedec10576cfe935b19218dc881f3fb14f890a1871448fc19a9b4ee15b448d8", ), ) - @test length(model.reactions) == 95 # read in correctly biomass = findfirst(model.reactions, "BIOMASS_Ecoli_core_w_GAM") - pfl = findfirst(model.reactions, "PFL") - - # FVA - optimizer = COBREXA.Tulip.Optimizer - atts = Dict("IPM_IterationsLimit" => 500) - cons = Dict("EX_glc__D_e" => (-10.0, -10.0)) - fva_max, fva_min = - fva(model, optimizer; objective_func = biomass, solver_attributes = atts) - fva_max2, fva_min2 = fva( - model, - optimizer; - objective_func = [biomass, pfl], - weights = [0.5, 0.5], - constraints = cons, - ) - @testset "FVA" begin - @test isapprox(fva_max["PDH"]["PDH"], 9.338922420065819, atol = 1e-6) - @test isapprox(fva_min["PDH"]["PDH"], 9.270274952732315, atol = 1e-6) - @test !isempty(fva_max2) - @test !isempty(fva_min2) - end + glucose = findfirst(model.reactions, "EX_glc__D_e") + fva_max, fva_min = flux_variability_analysis(model, Tulip.Optimizer; modifications=[modify_solver_attribute("IPM_IterationsLimit", 500), modify_constraint(glucose, -10, -10)]) + @test isapprox(fva_max["PDH"]["PDH"], 9.338920475333763, atol = 1e-6) + @test isapprox(fva_min["PDH"]["PDH"], 9.270274952732315, atol = 1e-6) end diff --git a/test/analysis/pfba.jl b/test/analysis/pfba.jl index fffd674fe770ba92855c70b2b18860e5336ac53b..106e5aa6512f82a8bf5621c8d1e21e7eb1454ffe 100644 --- a/test/analysis/pfba.jl +++ b/test/analysis/pfba.jl @@ -1,5 +1,4 @@ -@testset "Parsimonious flux balance analysis with CobraModel" begin - +@testset "Parsimonious flux balance analysis with StandardModel" begin model = read_model( download_data_file( "http://bigg.ucsd.edu/static/models/e_coli_core.json", @@ -9,29 +8,11 @@ ) biomass = findfirst(model.reactions, "BIOMASS_Ecoli_core_w_GAM") - cons = Dict("EX_glc__D_e" => (-12.0, -12.0)) - - # pFBA - atts = Dict( - "eps_abs" => 5e-4, - "eps_rel" => 5e-4, - "max_iter" => 100_000, - "verbose" => false, - ) - solworks = pfba( - model, - OSQP.Optimizer; - objective_func = biomass, - solver_attributes = atts, - constraints = cons, - ) # just see if it works - OSQP is a terrible LP solver - sol = pfba( - model, - [COBREXA.Tulip.Optimizer, COBREXA.OSQP.Optimizer]; - objective_func = biomass, - solver_attributes = Dict("opt1" => Dict{Any,Any}(), "opt2" => atts), - ) # try two optimizers - - @test !isempty(solworks) - @test isapprox(sol["PGM"], -14.737442319041387, atol = 1e-3) + glucose = findfirst(model.reactions, "EX_glc__D_e") + + d = parsimonious_flux_balance_analysis_dict(model, Tulip.Optimizer; modifications=modify_constraint(glucose, -12, -12), qp_solver=modify_solver(OSQP.Optimizer), qp_solver_attributes=modify_solver_attribute("verbose", false)) + v = parsimonious_flux_balance_analysis_vec(model, Tulip.Optimizer; modifications=modify_constraint(glucose, -12, -12), qp_solver=modify_solver(OSQP.Optimizer), qp_solver_attributes=modify_solver_attribute("verbose", false)) + + @test isapprox(d["PGM"], -14.751259785795853, atol = 1e-3) + @test isapprox(v[8], -14.751259785795853, atol = 1e-3) end diff --git a/test/analysis/utilities.jl b/test/analysis/utilities.jl new file mode 100644 index 0000000000000000000000000000000000000000..fe3ac19a3f158f3e7783305527d5224ef7fca7f2 --- /dev/null +++ b/test/analysis/utilities.jl @@ -0,0 +1,44 @@ +@testset "Analysis utilities" begin + model = read_model( + download_data_file( + "http://bigg.ucsd.edu/static/models/e_coli_core.json", + joinpath("data", "e_coli_core.json"), + "7bedec10576cfe935b19218dc881f3fb14f890a1871448fc19a9b4ee15b448d8", + ), + ) + @test length(model.reactions) == 95 # read in correctly + + # FBA + biomass = findfirst(model.reactions, "BIOMASS_Ecoli_core_w_GAM") + + cons = Dict("EX_glc__D_e" => (-12.0, -12.0)) + optimizer = Tulip.Optimizer # quiet by default + sol = flux_balance_analysis_dict(model, optimizer; modifications=modify_objective(biomass)) + + # atom tracker + atom_fluxes = atom_exchange(sol, model) + @test isapprox(atom_fluxes["C"], -37.1902, atol = 1e-3) + + # exchange trackers + consuming, producing = exchange_reactions(sol; verbose = false) + @test isapprox(consuming["EX_nh4_e"], -4.76532, atol = 1e-3) + + # metabolite trackers + consuming, producing = metabolite_fluxes(sol, model) + @test isapprox(consuming["atp_c"]["PFK"], -7.47738, atol = 1e-3) + @test isapprox(producing["atp_c"]["PYK"], 1.75818, atol = 1e-3) + + # set bounds + cbm = make_optimization_model(model, optimizer) + ubs = cbm[:ubs] + lbs = cbm[:lbs] + glucose_index = model[findfirst(model.reactions, "EX_glc__D_e")] + o2_index = model[findfirst(model.reactions, "EX_o2_e")] + atpm_index = model[findfirst(model.reactions, "ATPM")] + set_bound(glucose_index, cbm; ub = -1.0, lb = -1.0) + @test normalized_rhs(ubs[glucose_index]) == -1.0 + @test normalized_rhs(lbs[glucose_index]) == 1.0 + set_bound(o2_index, cbm; ub = 1.0, lb = 1.0) + @test normalized_rhs(ubs[o2_index]) == 1.0 + @test normalized_rhs(lbs[o2_index]) == -1.0 +end diff --git a/test/base/solver.jl b/test/base/solver.jl index 5f7f8ab7954e6657fb8cee911e84586d2d61dd30..bdfa647824eb087cdc2569542704fe9fba36fe26 100644 --- a/test/base/solver.jl +++ b/test/base/solver.jl @@ -3,15 +3,13 @@ cp = test_simpleLP() optimizer = GLPK.Optimizer lp = optimize_model(cp, optimizer) - x = lp[:x] @test termination_status(lp) === MOI.OPTIMAL - sol = COBREXA.JuMP.value.(x) + sol = JuMP.value.(lp[:x]) @test sol ≈ [1.0, 2.0] optimizer = Clp.Optimizer lp = optimize_model(cp, optimizer) - x = lp[:x] @test termination_status(lp) === MOI.OPTIMAL - sol = COBREXA.JuMP.value.(x) + sol = JuMP.value.(lp[:x]) @test sol ≈ [1.0, 2.0] end diff --git a/test/reconstruction/model_manipulations_test.jl b/test/reconstruction/model_manipulations_test.jl index 8822ae7dc56e1d50c7bfffe6e8afc5c59503ce55..a3b74cdc33ccf22492e514a57b758ece67aca1b5 100644 --- a/test/reconstruction/model_manipulations_test.jl +++ b/test/reconstruction/model_manipulations_test.jl @@ -26,7 +26,7 @@ rxns = [r1, r2] - model = CobraModel() + model = StandardModel() model.id = "model" model.reactions = rxns model.metabolites = mets diff --git a/test/sampling/sampling_tools_test.jl b/test/sampling/sampling_tools_test.jl index 92611296bdeb91fd8fa33f25c5cbdc6e0eee107c..dea4e51d7c05aeab1f43c797fe2f0a3ade0ea62f 100644 --- a/test/sampling/sampling_tools_test.jl +++ b/test/sampling/sampling_tools_test.jl @@ -8,28 +8,21 @@ ), ) - optimizer = COBREXA.Tulip.Optimizer biomass = findfirst(model.reactions, "BIOMASS_Ecoli_core_w_GAM") - cons = Dict("EX_glc__D_e" => (-12.0, -12.0)) - atts = Dict("IPM_IterationsLimit" => 110) - - sol = fba(model, optimizer; objective_func = biomass, constraints = cons) - - cons["BIOMASS_Ecoli_core_w_GAM"] = - (sol["BIOMASS_Ecoli_core_w_GAM"] * 0.99, sol["BIOMASS_Ecoli_core_w_GAM"]) + glucose = findfirst(model.reactions, "EX_glc__D_e") + cbm = flux_balance_analysis(model, Tulip.Optimizer; modifications=[modify_objective(biomass), modify_constraint(glucose, -12, -12), modify_solver_attribute("IPM_IterationsLimit", 500)]) + biomass_index = model[biomass] + λ = JuMP.value(cbm[:x][biomass_index]) + modify_constraint(biomass, 0.99*λ, λ)(model, cbm) samples = hit_and_run( 100_000, - model, - optimizer; + cbm, keepevery = 10, samplesize = 5000, - constraints = cons, - solver_attributes = atts, ) - # TODO - # @test isapprox(mean(samples[64, :]), 8.9, atol = 0.5) # only tests if the sampler approximately converged + @test isapprox(mean(samples[64, :]), 8.9, atol = 0.5) # only tests if the sampler approximately converged # samples = achr( # 100_000, diff --git a/test/types/model_test.jl b/test/types/model_test.jl index 579077905bd2ee4af3dde541bc5a8fe111f02d1b..2c5b7958d3cf9a8543475419cde150d04224621d 100644 --- a/test/types/model_test.jl +++ b/test/types/model_test.jl @@ -31,7 +31,7 @@ genes = [g1, g2, g3] rxns = [r1, r2, r3, r4] - model = CobraModel() + model = StandardModel() model.id = "model" model.reactions = rxns model.metabolites = mets