From e9f8ccb71c8b9ab43ef40e39f7f15cb9b59f6711 Mon Sep 17 00:00:00 2001 From: Dimitri Alston <123396563+DimitriAlston@users.noreply.github.com> Date: Sun, 27 Oct 2024 18:42:23 -0400 Subject: [PATCH] [docs] Add ModelingToolkit example (#136) --- docs/make.jl | 3 +- docs/src/assets/mtk_pfd.png | Bin 0 -> 27383 bytes docs/src/examples/modelingtoolkit.md | 377 +++++++++++++++++++++++++++ 3 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 docs/src/assets/mtk_pfd.png create mode 100644 docs/src/examples/modelingtoolkit.md diff --git a/docs/make.jl b/docs/make.jl index 7daccc80..97fdb0e4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -51,7 +51,8 @@ makedocs(modules = [EAGO, McCormick], "Examples" => Any["examples/explicit_ann.md", "examples/interval_bb.md", "examples/quasiconvex.md", - "examples/alpha_bb.md" + "examples/alpha_bb.md", + "examples/modelingtoolkit.md" ], "API Reference" => Any["dev/api_types.md", "dev/api_functions.md" diff --git a/docs/src/assets/mtk_pfd.png b/docs/src/assets/mtk_pfd.png new file mode 100644 index 0000000000000000000000000000000000000000..5f1467a1dc1532e4dc10bf4f2443c035520ccb1f GIT binary patch literal 27383 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 ayY^~S#kB1Mw(OUuPr16dJ0EeJvHm~g4R99# literal 0 HcmV?d00001 diff --git a/docs/src/examples/modelingtoolkit.md b/docs/src/examples/modelingtoolkit.md new file mode 100644 index 00000000..4f6b7aa4 --- /dev/null +++ b/docs/src/examples/modelingtoolkit.md @@ -0,0 +1,377 @@ +# ModelingToolkit Example + +This example is also provided [here as a Jupyter Notebook](https://github.com/PSORLab/EAGO-notebooks/blob/master/notebooks/mtk_system.ipynb). + +### Using ModelingToolkit NonlinearSystem models with EAGO + +This is a tutorial for exporting ModelingToolkit [[1](#References)] [`NonlinearSystem`](https://docs.sciml.ai/ModelingToolkit/stable/systems/NonlinearSystem/#ModelingToolkit.NonlinearSystem) models as standard Julia functions, which can then be used as EAGO-compatible equality constraints in JuMP models. + +## Defining Nonlinear System + +The system of interest is derived from an example originally presented by [[2](#References)] that involves a continuous stirred-tank reactor (CSTR) and separator train (with recycle) for the chlorination of benzene with the following reactions taking place: + +```math +\begin{array}{rcl} +\text{C}_{6} \text{H}_{6} + \text{Cl}_{2} &\rightarrow& \text{C}_{6} \text{H}_{5} \text{Cl} + \text{HCl}\\ +\text{C}_{6} \text{H}_{5} \text{Cl} + \text{Cl}_{2} &\rightarrow& \text{C}_{6} \text{H}_{4} \text{Cl}_{2} + \text{HCl} +\end{array} +``` + +where the rate constants ``k_{1}`` and ``k_{2}`` ``[h^{-1}]`` are known and the reactor volume ``V`` ``[m^{3}]`` and feed flow rate ``F_{1}`` ``[kmol/h]`` are considered free design variables. The CSTR is followed by a separation train for product purification and reactant recycle. + +![mtk_pfd](../assets/mtk_pfd.png) + +For simplicity, the reactions will be first-order (no dependence on ``\text{Cl}_{2}``) with respect to benzene (A) and chlorobenzene (B). The molar volumes of each species are: ``V_{A} = 8.937 \times 10^{-2} \; m^{3}/kmol``, ``V_{B} = 1.018 \times 10^{-1} \; m^{3}/kmol``, and ``V_{C} = 1.13 \times 10^{-1} \; m^{3}/kmol`` with dichlorobenzene as C. The feed is considered to be pure A (ignoring ``\text{Cl}_{2}``). The rate constants are ``k_{1} = 0.40 \; h^{-1}`` and ``k_{2} = 0.055 \; h^{-1}``. The steady-state model equations are: + +```math +\begin{array}{rclr} + F_{1} + F_{7} &=& F_{2} & (\text{mixer})\\ + y_{3,A} F_{3} &=& F_{2} - r_{1} V & (\text{reactor})\\ + y_{3,B} F_{3} &=& (r_{1} - r_{2}) V & (\text{reactor})\\ + y_{3,C} F_{3} &=& r_{2} V & (\text{reactor})\\ + F_{3} &=& F_{4} + F_{7} & (\text{separator 1})\\ + y_{3,B} F_{3} &=& y_{4,B} F_{4} & (\text{separator 1})\\ + y_{3,C} F_{3} &=& y_{4,C} F_{4} & (\text{separator 1})\\ + F_{4} &=& F_{5} + F_{6} & (\text{separator 2})\\ + y_{4,B} F_{4} &=& F_{5} & (\text{separator 2})\\ + y_{3,A} + y_{3,B} + y_{3,C} &=& 1 & (\text{relationship})\\ + y_{4,B} + y_{4,C} &=& 1 & (\text{relationship}) +\end{array} +``` + +where ``y_{i,j}`` is the mole fraction of ``j \in \{A,B,C\}`` in Stream ``i \in \{3,4\}`` (with ``y_{4,A} = 0``) and ``r_{1}`` and ``r_{2}`` are the reaction rates ``[kmol/(m^{3} h)]`` defined as: + +```math +\begin{array}{rcl} +r_{1} &=& k_{1} y_{3,A}/(y_{3,A} V_{A} + y_{3,B} V_{B} + y_{3,C} V_{C})\\ +r_{2} &=& k_{2} y_{3,B}/(y_{3,A} V_{A} + y_{3,B} V_{B} + y_{3,C} V_{C}) +\end{array} +``` + +with the variable vector ``\mathbf{x} \in \mathbb{R}^{12}``: + +```math +\mathbf{x} = (V, F_{1}, F_{2}, y_{3,A}, y_{3,B}, y_{3,C}, F_{3}, y_{4,B}, y_{4,C}, F_{4}, F_{6}, F_{7}). +``` + +### Performance Specifications + +We must produce at least 25 ``kmol/h`` of B (``F_{5} \ge 25``) and cannot have a reactor larger than 10 ``m^{3}`` due to space limitations. From a laboratory study, it was found that the residence time ``\tau`` ``[h]`` for the reactor must be at least 475 seconds. Residence time can be defined as: + +```math +\tau = \frac{V}{F_{3} (y_{3,A} V_{A} + y_{3,B} V_{B} + y_{3,C} V_{C})}. +``` + +### Design Problem + +We will consider an economic objective for our optimization problem as the total annualized costs of the unit operations. The total annualized cost of the CSTR is given by + +```math +f_{CSTR} = (25764 + 8178V)/2.5 +``` + +and the total annualized cost of the separator train is given by: + +```math +\begin{array}{rcl} +s_{1}^{cap} &=& 132718 + F_{3} (369 y_{3,A} - 1113.9 y_{3,B})\\ +s_{2}^{cap} &=& 25000 + F_{4} (6984.5 y_{4,B} - 3869.53 y_{4,C}^{2})\\ +s_{1}^{op} &=& F_{3} (3 + 36.11 y_{3,A} + 7.71 y_{3,B}) (26.32 \times 10^{-3})\\ +s_{2}^{op} &=& F_{4} (26.21 + 29.45 y_{4,B}) (26.32 \times 10^{-3})\\ +f_{sep} &=& (s_{1}^{cap} + s_{2}^{cap})/2.5 + 0.52 (s_{1}^{op} + s_{2}^{op}). +\end{array} +``` + +The total annualized cost of the complete system is then given by + +```math +f_{total} = f_{CSTR} + f_{sep}. +``` + +## Modeling and Simplifying Using ModelingToolkit + +```julia +using EAGO, GLPK, JuMP, ModelingToolkit +``` + +First, we define the state variables with `@variables` and model parameters with `@parameters`. Note that the design variables ``V`` and ``F_{1}`` are considered parameters so we have a square system (10 equations, 10 unknowns). + +```julia +# State variables +ModelingToolkit.@variables F₂ y_3A y_3B y_3C F₃ y_4B y_4C F₄ F₆ F₇ + +# Model parameters and design variables +ModelingToolkit.@parameters k₁ k₂ V_A V_B V_C V F₁ +``` + +Next, we can write symbolic expressions for ``r_{1}``, ``r_{2}``, and ``F_{5}`` to help simplify our model equations. + +```julia +# Symbolic expressions for reaction rates and F₅ +r₁ = (k₁*y_3A)/(y_3A*V_A + y_3B*V_B + y_3C*V_C) +r₂ = (k₂*y_3B)/(y_3A*V_A + y_3B*V_B + y_3C*V_C) +F₅ = (y_4B*F₄) +``` + +The variables, parameters, and steady-state model equations are then used to construct the `NonlinearSystem`. + +```julia +# Steady-state model equations +eqs = [ + F₁ + F₇ ~ F₂ + (y_3A*F₃) ~ F₂ - r₁*V + (y_3B*F₃) ~ (r₁ - r₂)*V + (y_3C*F₃) ~ r₂*V + F₃ ~ F₄ + F₇ + (y_3B*F₃) ~ (y_4B*F₄) + (y_3C*F₃) ~ (y_4C*F₄) + F₄ ~ F₅ + F₆ + y_3A + y_3B + y_3C ~ 1 + y_4B + y_4C ~ 1 +] + +# Variables and parameters +vars = [F₂, y_3A, y_3B, y_3C, F₃, y_4B, y_4C, F₄, F₆, F₇] +pars = [k₁, k₂, V_A, V_B, V_C, V, F₁] + +# Building and simplifying model +@mtkbuild ns = NonlinearSystem(eqs, vars, pars) +``` + +`@mtkbuild` simplifies the model down to 4 equations and 4 unknowns, significantly reducing the dimensionality of this system. The variables that were eliminated during the simplification process are called "observed variables," and their expressions can be obtained using `observed(::NonlinearSystem)`. + +## Converting to a Standard Function + +Now that we've simplified our model, we need to covert the symbolic model equations into standard Julia functions in order to use them with EAGO. This can be accomplished through the use of `Symbolics.build_function`, which generates a numerically-usable function from a symbolic expression. Since `Symbolics.jl` is already built into `ModelingToolkit.jl`, no additional packages need to be installed. + +To simplify the use of `build_function` for our purposes, we first create the function `ns_to_function()` that takes in a `NonlinearSystem`, a vector of parameters, and a vector of function inputs, and returns a vector of standard Julia functions corresponding to the simplified model equations in the nonlinear system. + +```julia +# Converts symbolic model equations into standard Julia functions +function ns_to_function(ns::NonlinearSystem, params::Vector{Pair{Num,Float64}}, vars::Vector{Num}) + function_holder = [] + # For each model equation, substitute parameter values and create function + for eqn in full_equations(ns) + expr = substitute(eqn.rhs, Dict{Num,Any}(params)) + new_func = build_function(expr, vars..., expression = Val{false}) + push!(function_holder, new_func) + end + return function_holder +end +``` + +To use this function, we first assign values to our known parameters, define our function inputs, and then call `ns_to_function()` to obtain our desired functions. + +```julia +# Assign parameter values +params = [ + k₁ => 0.40, + k₂ => 0.055, + V_A => 8.937e-2, + V_B => 1.018e-1, + V_C => 1.130e-1 +] + +# Function inputs (design and state variables) +x = [V, F₁, y_3B, y_3C, F₃, y_4C] + +# Create functions for each model equation as a function of design and state variables +f = ns_to_function(ns, params, x) +``` + +We now have standard Julia functions for each model equation which can be used as equality constraints in our JuMP [[3](#References)] model. + +We'll also create functions for our inequality constraints (``F_{5}``, ``\tau``) and objective function (``f_{total}``). Since these functions are not expressed in terms of the two design variables (``V`` and ``F_{1}``) and the four state variables (``y_{3,B}``, ``y_{3,C}``, ``F_{3}``, ``y_{4,C}``), we'll create a function `expr_to_function()` to substitute the `NonlinearSystem` observed variables into our input expression before calling `build_function`. + +All we have to do now is write the symbolic expressions for ``F_{5}``, ``\tau``, and ``f_{total}``, then call `expr_to_function()` for each expression. + +```julia +# Converts symbolic expression into standard Julia function, substituting parameter and observed variable expressions +function expr_to_function(expr::Num, ns::NonlinearSystem, params::Vector{Pair{Num,Float64}}, vars::Vector{Num}) + # Creates a dictionary of parameter and observed variable substitutions + substitute_dict = Dict{Num,Any}(params) + for eqn in observed(ns) + substitute_dict[eqn.lhs] = eqn.rhs + end + # Makes substitutions until no substitutions can be made + while ~isempty(intersect(string.(get_variables(expr)), string.(keys(substitute_dict)))) + expr = substitute(expr, substitute_dict) + end + return build_function(expr, vars..., expression = Val{false}) +end + +# Symbolic expression for F₅ +exprF5 = y_4B*F₄ + +# Symbolic expression for τ +exprTau = V/(F₃*(y_3A*V_A + y_3B*V_B + y_3C*V_C)) + +# Symbolic expressions for CSTR and separator costs +f_CSTR = (25764 + 8178*V)/2.5 +s1cap = 132718 + F₃*(369*y_3A - 1113.9*y_3B) +s2cap = 25000 + F₄*(6984.5*y_4B - 3869.53*y_4C^2) +s1op = F₃*(3+36.11*y_3A + 7.71*y_3B)*26.32e-3 +s2op = F₄*(26.21 + 29.45*y_4B)*26.32e-3; +f_Sep = (s1cap+s2cap)/2.5 + 0.52*(s1op+s2op) + +# Symbolic expression for total cost (objective function) +exprTotCost = f_CSTR + f_Sep + +# Create functions for each expression as a function of design and state variables +F5 = expr_to_function(exprF5, ns, params, x) +Tau = expr_to_function(exprTau, ns, params, x) +TotCost = expr_to_function(exprTotCost, ns, params, x) +``` + +## Solving the Optimization Problem with EAGO + +To formulate and solve the design problem, we'll begin by creating a JuMP model. We'll also use GLPK as a subsolver because it greatly accelerates convergence for this particular system. + +```julia +# Using GLPK subsolver for EAGO +factory = () -> EAGO.Optimizer(SubSolvers(; r = GLPK.Optimizer())) +m = Model(optimizer_with_attributes(factory, + "branch_cvx_factor" => 0.75, + "output_iterations" => 200)) +``` + +Next, we need to define bounds for our design variables (``V``, ``F_{1}``) and state variables (``y_{3,B}``, ``y_{3,C}``, ``F_{3}``, ``y_{4,C}``). This gives us a total of 6 decision variables in our optimization problem. + +```julia +# Define variable bounds +xL = [5.0, 25.0, 0.1, 0.001, 75.0, 0.01] +xU = [10.0, 50.0, 0.5, 0.1, 125.0, 0.1] +# x = (V, F₁, y_3B, y_3C, F₃, y_4C) +@variable(m, xL[i] <= x[i=1:6] <= xU[i]) +``` + +!!! note + + It's important to note that defining "good" bounds is especially important for deterministic global solvers like EAGO because of the set arithmetic and interval and convex analyses required to guarantee global optimality. Based on simple linear constraints and understanding what our variables physically represent, we can easily eliminate erroneous regions from the search space and drastically reduce computational cost. For example, we know the reactor volume must be less than or equal to 10 ``m^{3}``, but we also know it should be at least a few cubic meters large, so we bound ``V`` between 5 and 10. Similarly, we know we must produce at least 25 ``kmol/h`` of B, but this is only possible if there are at least 25 ``kmol/h`` of A entering the system since we are operating under steady-state conditions with one influent and two effluent streams. + +Once proper bounds are defined, we continue by registering our user-defined functions and defining our nonlinear constraints (model equations and performance specifications) and objective (minimizing total annualized costs). + +```julia +# Register user-defined functions +JuMP.register(m, :f1, 6, f[1], autodiff=true) +JuMP.register(m, :f2, 6, f[2], autodiff=true) +JuMP.register(m, :f3, 6, f[3], autodiff=true) +JuMP.register(m, :f4, 6, f[4], autodiff=true) +JuMP.register(m, :F5, 6, F5, autodiff=true) +JuMP.register(m, :Tau, 6, Tau, autodiff=true) +JuMP.register(m, :TotCost, 6, TotCost, autodiff=true) + +# Define model equation constraints +@NLconstraint(m, e1, f1(x...) == 0) +@NLconstraint(m, e2, f2(x...) == 0) +@NLconstraint(m, e3, f3(x...) == 0) +@NLconstraint(m, e4, f4(x...) == 0) + +# Define performance specification constraints +@NLconstraint(m, c1, F5(x...) ≥ 25.0) +@NLconstraint(m, c2, Tau(x...) ≥ 475.0/3600.0) + +# Define objective to minimize total annualized costs +@NLobjective(m, Min, TotCost(x...)) +``` + +We're now ready to solve the optimal design problem. We'll `@time` the solver so we can compare convergence times for reduced-space vs full-space formulations later. + +```julia +# Solve optimization problem +@time JuMP.optimize!(m) +``` + +We've identified a global optimal solution ``\mathbf{x}^* = (8.4594, 26.3167, 0.2631, 0.01386, 95.0215, 0.05003)``. Keep in mind that we can readily calculate values for the observed variables since we have their explicit expressions written in terms of these decision variables. + +## Full-Space Formulation + +To compare and showcase the full-space optimization formulation for this system, we'll quickly formulate and solve the design problem with all 10 model equations using the full variable vector + +```math +\mathbf{x} = (V, F_{1}, F_{2}, y_{3,A}, y_{3,B}, y_{3,C}, F_{3}, y_{4,B}, y_{4,C},F_{4}, F_{6}, F_{7}). +``` + +We can generate functions for each of the original model equations by first initializing the model using `@named` instead of `@mtkbuild` to exclude the structural simplification step. + +```julia +# Initialize model without structural simplification +@named fns = NonlinearSystem(eqs, vars, pars) +``` + +Then, we redefine our function inputs and generate our functions in the same way as before. + +```julia +# Full variable vector +x = [V, F₁, F₂, y_3A, y_3B, y_3C, F₃, y_4B, y_4C, F₄, F₆, F₇] + +# Create functions for each model equation as a function of the full variable vector +ff = ns_to_function(fns, params, x) + +# Create functions for each expression as a function of the full variable vector +fF5 = expr_to_function(exprF5, fns, params, x) +fTau = expr_to_function(exprTau, fns, params, x) +fTotCost = expr_to_function(exprTotCost, fns, params, x) +``` + +We'll define a new JuMP model with the same settings, define bounds for each variable, register functions, define constraints and objective, then solve the optimization problem. + +```julia +n = Model(optimizer_with_attributes(factory, + "branch_cvx_factor" => 0.75, + "output_iterations" => 200)) + +# Define variable bounds +# New variables: v v v v v v +xL = [ 5.0, 25.0, 75.0, 0.5, 0.1, 0.001, 75.0, 0.9, 0.01, 25.0, 0.0, 50.0] +xU = [10.0, 50.0, 125.0, 1.0, 0.5, 0.1, 125.0, 1.0, 0.1, 50.0, 10.0, 100.0] +# x = (V, F₁, F₂, y_3A, y_3B, y_3C, F₃, y_4B, y_4C, F₄, F₆, F₇) +@variable(n, xL[i] <= x[i=1:12] <= xU[i]) + +# Register user-defined functions +JuMP.register(n, :f1, 12, ff[1], autodiff=true) +JuMP.register(n, :f2, 12, ff[2], autodiff=true) +JuMP.register(n, :f3, 12, ff[3], autodiff=true) +JuMP.register(n, :f4, 12, ff[4], autodiff=true) +JuMP.register(n, :f5, 12, ff[5], autodiff=true) +JuMP.register(n, :f6, 12, ff[6], autodiff=true) +JuMP.register(n, :f7, 12, ff[7], autodiff=true) +JuMP.register(n, :f8, 12, ff[8], autodiff=true) +JuMP.register(n, :f9, 12, ff[9], autodiff=true) +JuMP.register(n, :f10, 12, ff[10], autodiff=true) +JuMP.register(n, :F5, 12, fF5, autodiff=true) +JuMP.register(n, :Tau, 12, fTau, autodiff=true) +JuMP.register(n, :TotCost, 12, fTotCost, autodiff=true) + +# Define model equation constraints +@NLconstraint(n, e1, f1(x...) == 0) +@NLconstraint(n, e2, f2(x...) == 0) +@NLconstraint(n, e3, f3(x...) == 0) +@NLconstraint(n, e4, f4(x...) == 0) +@NLconstraint(n, e5, f5(x...) == 0) +@NLconstraint(n, e6, f6(x...) == 0) +@NLconstraint(n, e7, f7(x...) == 0) +@NLconstraint(n, e8, f8(x...) == 0) +@NLconstraint(n, e9, f9(x...) == 0) +@NLconstraint(n, e10, f10(x...) == 0) + +# Define performance specification constraints +@NLconstraint(n, c1, F5(x...) ≥ 25.0) +@NLconstraint(n, c2, Tau(x...) ≥ 475.0/3600.0) + +# Define objective to minimize total annualized costs +@NLobjective(n, Min, TotCost(x...)) + +# Solve optimization problem +@time JuMP.optimize!(n) +``` + +It takes three times as many iterations and significantly more memory allocations for EAGO to converge with the full-space formulation, demonstrating the advantages of reduced-space formulations. Because global solvers suffer from the curse of dimensionality, reducing the number of decision variables through model simplifications can improve performance and therefore broaden the scope of problems for which global solvers can be applied. + +This tutorial introduces user-defined functions that provide an easy way to use ModelingToolkit's intuitive modeling interface and model simplification features to create reduced-space formulations in a way that is compatible with EAGO. + +## References + +1. Yingbo Ma and Shashi Gowda and Ranjan Anantharaman and Chris Laughman and Viral Shah and Chris Rackauckas, ModelingToolkit: A Composable Graph Transformation System For Equation-Based Modeling, *arXiv*, (2021). +2. A. C. Kokossis, C. A. Floudas. Synthesis of isothermal reactor—separator—recycle systems, *Chemical Engineering Science*, 46:5-6 (1991), pp. 1361-1383. +3. Iain Dunning and Joey Huchette and Miles Lubin. JuMP: A Modeling Language for Mathematical Optimization, *SIAM Review*, 59 (2017), pp. 295-320. \ No newline at end of file