{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## GEKKO Python Example Applications\n", "\n", "![GEKKO Optimization Suite](http://apmonitor.com/wiki/uploads/Main/gekko_optimization.png)\n", "\n", "GEKKO is optimization software for mixed-integer and differential algebraic equations. It is coupled with large-scale solvers for linear, quadratic, nonlinear, and mixed integer programming (LP, QP, NLP, MILP, MINLP). Modes of operation include data reconciliation, real-time optimization, dynamic simulation, and nonlinear predictive control. See the [GEKKO documentation](http://gekko.readthedocs.io/en/latest/overview.html) for additional information.\n", "\n", "1. Solver Selection\n", "2. Solve Linear Equations\n", "3. Solve Nonlinear Equations\n", "4. Interpolation with Cubic Spline\n", "5. Linear and Polynomial Regression\n", "6. Nonlinear Regression\n", "7. Machine Learning / Artificial Neural Network\n", "8. Solve Differential Equation(s)\n", "9. Nonlinear Programming Optimization\n", "10. Mixed Integer Nonlinear Programming\n", "11. Optimal Control with Integral Objective\n", "12. Optimal Control with Economic Objective\n", "13. Optimal Control: Minimize Final Time\n", "14. PID Control Tuning\n", "15. Process Simulator\n", "16. Moving Horizon Estimation\n", "17. Model Predictive Control\n", "18. Debugging Resources" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " # import gekko if installed\n", " from gekko import GEKKO\n", "except:\n", " # install gekko if error on try\n", " !pip install gekko\n", " from gekko import GEKKO\n", "\n", "# package information\n", "!pip show gekko\n", "\n", "# upgrade GEKKO to latest version\n", "# !pip install --upgrade gekko" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1: Solver selection\n", "\n", "Solve $y^2=1$ with APOPT solver. See [APMonitor documentation](https://apmonitor.com/wiki/index.php/Main/OptionApmSolver) or [GEKKO documentation](http://gekko.readthedocs.io/en/latest/global.html?highlight=solver#solver) for additional solver options." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = GEKKO() # create GEKKO model\n", "y = m.Var(value=2) # define new variable, initial value=2\n", "m.Equation(y**2==1) # define new equation\n", "m.options.SOLVER=1 # change solver (1=APOPT,3=IPOPT)\n", "m.solve(disp=False) # solve locally (remote=False)\n", "print('y: ' + str(y.value)) # print variable value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2: Solve Linear Equations\n", "\n", "$3\\,x + 2\\,y=1$\n", "\n", "$x+2\\,y=0$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = GEKKO() # create GEKKO model\n", "x = m.Var() # define new variable, default=0\n", "y = m.Var() # define new variable, default=0\n", "m.Equations([3*x+2*y==1, x+2*y==0]) # equations\n", "m.solve(disp=False) # solve\n", "print(x.value,y.value) # print solution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3: Solve Nonlinear Equations\n", "\n", "$x+2\\,y=0$\n", "\n", "$x^2+y^2=1$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = GEKKO() # create GEKKO model\n", "x = m.Var(value=0) # define new variable, initial value=0\n", "y = m.Var(value=1) # define new variable, initial value=1\n", "m.Equations([x + 2*y==0, x**2+y**2==1]) # equations\n", "m.solve(disp=False) # solve\n", "print([x.value[0],y.value[0]]) # print solution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4: Interpolation with Cubic Spline\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "xm = np.array([0,1,2,3,4,5])\n", "ym = np.array([0.1,0.2,0.3,0.5,1.0,0.9])\n", "\n", "m = GEKKO() # create GEKKO model\n", "m.options.IMODE = 2 # solution mode\n", "x = m.Param(value=np.linspace(-1,6)) # prediction points\n", "y = m.Var() # prediction results\n", "m.cspline(x, y, xm, ym) # cubic spline\n", "m.solve(disp=False) # solve\n", "\n", "# create plot\n", "plt.plot(xm,ym,'bo')\n", "plt.plot(x.value,y.value,'r--',label='cubic spline')\n", "plt.legend(loc='best')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5: Linear and Polynomial Regression" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "xm = np.array([0,1,2,3,4,5])\n", "ym = np.array([0.1,0.2,0.3,0.5,0.8,2.0])\n", "\n", "#### Solution\n", "m = GEKKO()\n", "m.options.IMODE=2\n", "# coefficients\n", "c = [m.FV(value=0) for i in range(4)]\n", "x = m.Param(value=xm)\n", "y = m.CV(value=ym)\n", "y.FSTATUS = 1\n", "# polynomial model\n", "m.Equation(y==c[0]+c[1]*x+c[2]*x**2+c[3]*x**3)\n", "\n", "# linear regression\n", "c[0].STATUS=1\n", "c[1].STATUS=1\n", "m.solve(disp=False)\n", "p1 = [c[1].value[0],c[0].value[0]]\n", "\n", "# quadratic\n", "c[2].STATUS=1\n", "m.solve(disp=False)\n", "p2 = [c[2].value[0],c[1].value[0],c[0].value[0]]\n", "\n", "# cubic\n", "c[3].STATUS=1\n", "m.solve(disp=False)\n", "p3 = [c[3].value[0],c[2].value[0],c[1].value[0],c[0].value[0]]\n", "\n", "# plot fit\n", "plt.plot(xm,ym,'ko',markersize=10)\n", "xp = np.linspace(0,5,100)\n", "plt.plot(xp,np.polyval(p1,xp),'b--',linewidth=2)\n", "plt.plot(xp,np.polyval(p2,xp),'r--',linewidth=3)\n", "plt.plot(xp,np.polyval(p3,xp),'g:',linewidth=2)\n", "plt.legend(['Data','Linear','Quadratic','Cubic'],loc='best')\n", "plt.xlabel('x')\n", "plt.ylabel('y')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6: Nonlinear Regression" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "# measurements\n", "xm = np.array([0,1,2,3,4,5])\n", "ym = np.array([0.1,0.2,0.3,0.5,0.8,2.0])\n", "\n", "# GEKKO model\n", "m = GEKKO()\n", "\n", "# parameters\n", "x = m.Param(value=xm)\n", "a = m.FV()\n", "a.STATUS=1\n", "\n", "# variables\n", "y = m.CV(value=ym)\n", "y.FSTATUS=1\n", "\n", "# regression equation\n", "m.Equation(y==0.1*m.exp(a*x))\n", "\n", "# regression mode\n", "m.options.IMODE = 2\n", "\n", "# optimize\n", "m.solve(disp=False)\n", "\n", "# print parameters\n", "print('Optimized, a = ' + str(a.value[0]))\n", "\n", "plt.plot(xm,ym,'bo')\n", "plt.plot(xm,y.value,'r-')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7: Machine Learning\n", "\n", "Approximate $y = sin(x)$ with an Artificial Neural Network\n", "\n", "__Trigonometric Function (select=True)__\n", "\n", "* Input: $x$\n", "* Layer 1: linear layer, 1 node, $l1 = w1 \\; x$\n", "* Layer 2: nonlinear layer, 1 node, cosine function, $l2 = \\cos(w2a+w2b\\;l1)$\n", "* Layer 3: linear layer, 1 node, $l3 = w3 \\; l2$\n", "* Output: $y = \\sum{l3}$\n", "\n", "__Artificial Neural Network Description (select=False)__\n", "\n", "* Input: $x$\n", "* Layer 1: linear layer, 2 nodes, $l1 = w1 \\; x$\n", "* Layer 2: nonlinear layer, 3 nodes, hyperbolic tangent activation function, $l2 = \\tanh(w2a+w2b\\;l1)$\n", "* Layer 3: linear layer, 2 nodes, $l3 = w3 \\; l2$\n", "* Output: $y = \\sum{l3}$\n", "\n", "See [Online Neural Network Demo](https://playground.tensorflow.org) with TensorFlow." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import brain\n", "import numpy as np\n", "import matplotlib.pyplot as plt \n", "\n", "# generate training data\n", "x = np.linspace(0.0,2*np.pi,20)\n", "y = np.sin(x)\n", "\n", "x = np.array(x)\n", "y = np.array(y)\n", "\n", "b = brain.Brain()\n", "b.input_layer(1)\n", "b.layer(linear=2)\n", "b.layer(tanh=3)\n", "b.layer(linear=2)\n", "b.output_layer(1)\n", "\n", "b.learn(x,y) # train\n", "xp = np.linspace(-2*np.pi,4*np.pi,100) \n", "yp = b.think(xp) # validate\n", "\n", "plt.figure()\n", "plt.plot(x,y,'bo')\n", "plt.plot(xp,yp[0],'r-')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8: Solve Differential Equation(s)\n", "\n", "Solve the following differential equation with initial condition $y(0) = 5$:\n", "\n", "$ k \\, \\frac{dy}{dt} = -t \\, y$\n", "\n", "where $k=10$. The solution of $y(t)$ should be reported from an initial time $0$ to final time $20$. Create of plot of the result for $y(t)$ versus $t$. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "m = GEKKO()\n", "m.time = np.linspace(0,20,100)\n", "k = 10\n", "y = m.Var(value=5.0)\n", "t = m.Param(value=m.time)\n", "m.Equation(k*y.dt()==-t*y)\n", "m.options.IMODE = 4\n", "m.solve(disp=False)\n", "\n", "plt.plot(m.time,y.value)\n", "plt.xlabel('time')\n", "plt.ylabel('y')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9: Nonlinear Programming Optimization\n", "\n", "Solve the following nonlinear optimization problem:\n", "\n", "$\\min x_1 x_4 \\left(x_1 + x_2 + x_3\\right) + x_3$\n", "\n", "$\\mathrm{s.t.} \\quad x_1 x_2 x_3 x_4 \\ge 25$\n", "\n", "$x_1^2 + x_2^2 + x_3^2 + x_4^2 = 40$\n", "\n", "$1\\le x_1, x_2, x_3, x_4 \\le 5$\n", "\n", "with initial conditions:\n", "\n", "$x_0 = (1,5,5,1)$\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "m = GEKKO() # Initialize gekko\n", "# Use IPOPT solver (default)\n", "m.options.SOLVER = 3\n", "# Change to parallel linear solver\n", "m.solver_options = ['linear_solver ma97']\n", "# Initialize variables\n", "x1 = m.Var(value=1,lb=1,ub=5)\n", "x2 = m.Var(value=5,lb=1,ub=5)\n", "x3 = m.Var(value=5,lb=1,ub=5)\n", "x4 = m.Var(value=1,lb=1,ub=5)\n", "# Equations\n", "m.Equation(x1*x2*x3*x4>=25)\n", "m.Equation(x1**2+x2**2+x3**2+x4**2==40)\n", "m.Obj(x1*x4*(x1+x2+x3)+x3) # Objective\n", "m.options.IMODE = 3 # Steady state optimization\n", "m.solve(disp=False) # Solve\n", "print('Results')\n", "print('x1: ' + str(x1.value))\n", "print('x2: ' + str(x2.value))\n", "print('x3: ' + str(x3.value))\n", "print('x4: ' + str(x4.value))\n", "print('Objective: ' + str(m.options.objfcnval))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 10: Mixed Integer Nonlinear Programming" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "m = GEKKO() # Initialize gekko\n", "m.options.SOLVER=1 # APOPT is an MINLP solver\n", "\n", "# optional solver settings with APOPT\n", "m.solver_options = ['minlp_maximum_iterations 500', \\\n", " # minlp iterations with integer solution\n", " 'minlp_max_iter_with_int_sol 10', \\\n", " # treat minlp as nlp\n", " 'minlp_as_nlp 0', \\\n", " # nlp sub-problem max iterations\n", " 'nlp_maximum_iterations 50', \\\n", " # 1 = depth first, 2 = breadth first\n", " 'minlp_branch_method 1', \\\n", " # maximum deviation from whole number\n", " 'minlp_integer_tol 0.05', \\\n", " # covergence tolerance\n", " 'minlp_gap_tol 0.01']\n", "\n", "# Initialize variables\n", "x1 = m.Var(value=1,lb=1,ub=5)\n", "x2 = m.Var(value=5,lb=1,ub=5)\n", "# Integer constraints for x3 and x4\n", "x3 = m.Var(value=5,lb=1,ub=5,integer=True)\n", "x4 = m.Var(value=1,lb=1,ub=5,integer=True)\n", "# Equations\n", "m.Equation(x1*x2*x3*x4>=25)\n", "m.Equation(x1**2+x2**2+x3**2+x4**2==40)\n", "m.Obj(x1*x4*(x1+x2+x3)+x3) # Objective\n", "m.solve(disp=False) # Solve\n", "print('Results')\n", "print('x1: ' + str(x1.value))\n", "print('x2: ' + str(x2.value))\n", "print('x3: ' + str(x3.value))\n", "print('x4: ' + str(x4.value))\n", "print('Objective: ' + str(m.options.objfcnval))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 11: Optimal Control with Integral Objective\n", "\n", "__Original Form__\n", "\n", "$\\min_u \\frac{1}{2} \\int_0^2 x_1^2(t) \\, dt$\n", "\n", "$\\mathrm{subject \\; to}$\n", "\n", "$\\frac{dx_1}{dt}=u$\n", "\n", "$x_1(0) = 1$\n", "\n", "$-1 \\le u(t) \\le 1$\n", "\n", "__Equivalent Form for GEKKO with new Variable $x_2$__\n", "\n", "$\\min_u x_2\\left(t_f\\right)$\n", "\n", "$\\mathrm{subject \\; to}$\n", "\n", "$\\frac{dx_1}{dt}=u$\n", "\n", "$\\frac{dx_2}{dt} = \\frac{1}{2} x_1^2(t)$\n", "\n", "$x_1(0) = 1$\n", "\n", "$x_2(0) = 0$\n", "\n", "$t_f = 2$\n", "\n", "$-1 \\le u(t) \\le 1$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "m = GEKKO() # initialize gekko\n", "nt = 101\n", "m.time = np.linspace(0,2,nt)\n", "# Variables\n", "x1 = m.Var(value=1)\n", "x2 = m.Var(value=0)\n", "u = m.Var(value=0,lb=-1,ub=1)\n", "p = np.zeros(nt) # mark final time point\n", "p[-1] = 1.0\n", "final = m.Param(value=p)\n", "# Equations\n", "m.Equation(x1.dt()==u)\n", "m.Equation(x2.dt()==0.5*x1**2)\n", "m.Obj(x2*final) # Objective function\n", "m.options.IMODE = 6 # optimal control mode\n", "m.solve(disp=False) # solve\n", "plt.figure(1) # plot results\n", "plt.plot(m.time,x1.value,'k-',label=r'$x_1$')\n", "plt.plot(m.time,x2.value,'b-',label=r'$x_2$')\n", "plt.plot(m.time,u.value,'r--',label=r'$u$')\n", "plt.legend(loc='best')\n", "plt.xlabel('Time')\n", "plt.ylabel('Value')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 12: Optimal Control with Economic Objective\n", "\n", "__Original Form__\n", "\n", "$\\max_{u(t)} \\int_0^{10} \\left(E-\\frac{c}{x}\\right) u \\, U_{max} \\, dt$\n", "\n", "$\\mathrm{subject \\; to}$\n", "\n", "$\\frac{dx}{dt}=r \\, x(t) \\left(1-\\frac{x(t)}{k}\\right)-u \\, U_{max}$\n", "\n", "$x(0) = 70$\n", "\n", "$0 \\le u(t) \\le 1$\n", "\n", "$E=1, \\, c=17.5, \\, r=0.71$\n", "\n", "$k=80.5, \\, U_{max}=20$\n", "\n", "__Equivalent Form for GEKKO__\n", "\n", "$\\min_{u(t)} -J\\left(t_f\\right)$\n", "\n", "$\\mathrm{subject \\; to}$\n", "\n", "$\\frac{dx}{dt}=r \\, x(t) \\left(1-\\frac{x(t)}{k}\\right)-u \\, U_{max}$\n", "\n", "$\\frac{dJ}{dt} = \\left(E-\\frac{c}{x}\\right) u \\, U_{max}$\n", "\n", "$x(0) = 70$\n", "\n", "$J(0) = 0$\n", "\n", "$0 \\le u(t) \\le 1$\n", "\n", "$t_f = 10, \\, E=1, \\, c=17.5$\n", "\n", "$r=0.71, \\, k=80.5, \\, U_{max}=20$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "# create GEKKO model\n", "m = GEKKO()\n", "# time points\n", "n=501\n", "m.time = np.linspace(0,10,n)\n", "# constants\n", "E,c,r,k,U_max = 1,17.5,0.71,80.5,20\n", "# fishing rate\n", "u = m.MV(value=1,lb=0,ub=1)\n", "u.STATUS = 1\n", "u.DCOST = 0\n", "x = m.Var(value=70) # fish population\n", "# fish population balance\n", "m.Equation(x.dt() == r*x*(1-x/k)-u*U_max)\n", "J = m.Var(value=0) # objective (profit)\n", "Jf = m.FV() # final objective\n", "Jf.STATUS = 1\n", "m.Connection(Jf,J,pos2='end')\n", "m.Equation(J.dt() == (E-c/x)*u*U_max)\n", "m.Obj(-Jf) # maximize profit\n", "m.options.IMODE = 6 # optimal control\n", "m.options.NODES = 3 # collocation nodes\n", "m.options.SOLVER = 3 # solver (IPOPT)\n", "m.solve(disp=False) # Solve\n", "print('Optimal Profit: ' + str(Jf.value[0]))\n", "plt.figure(1) # plot results\n", "plt.subplot(2,1,1)\n", "plt.plot(m.time,J.value,'r--',label='profit')\n", "plt.plot(m.time,x.value,'b-',label='fish')\n", "plt.legend()\n", "plt.subplot(2,1,2)\n", "plt.plot(m.time,u.value,'k--',label='rate')\n", "plt.xlabel('Time (yr)')\n", "plt.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 13: Optimal Control: Minimize Final Time\n", "\n", "__Original Form__\n", "\n", "$\\min_{u(t)} \\; t_f$\n", "\n", "$\\mathrm{subject \\; to}$\n", "\n", "$\\frac{dx_1}{dt}=u$\n", "\n", "$\\frac{dx_2}{dt}=\\cos\\left(x_1(t)\\right)$\n", "\n", "$\\frac{dx_3}{dt}=\\sin\\left(x_1(t)\\right)$\n", "\n", "$x(0) = \\left[\\pi/2,4,0\\right]$\n", "\n", "$x_2\\left(t_f\\right)=0$\n", "\n", "$x_3\\left(t_f\\right)=0$\n", "\n", "$-2 \\le u(t) \\le 2$\n", "\n", "__Equivalent Form for GEKKO__\n", "\n", "$\\min_{u(t), t_f} \\; t_f$\n", "\n", "$\\mathrm{subject \\; to}$\n", "\n", "$\\frac{dx_1}{dt}=t_f \\, u$\n", "\n", "$\\frac{dx_2}{dt}=t_f \\, \\cos\\left(x_1(t)\\right)$\n", "\n", "$\\frac{dx_3}{dt}=t_f \\, \\sin\\left(x_1(t)\\right)$\n", "\n", "$x(0) = \\left[\\pi/2,4,0\\right]$\n", "\n", "$x_2\\left(t_f\\right)=0$\n", "\n", "$x_3\\left(t_f\\right)=0$\n", "\n", "$-2 \\le u(t) \\le 2$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "m = GEKKO() # initialize GEKKO\n", "nt = 501\n", "m.time = np.linspace(0,1,nt)\n", "# Variables\n", "x1 = m.Var(value=np.pi/2.0)\n", "x2 = m.Var(value=4.0)\n", "x3 = m.Var(value=0.0)\n", "p = np.zeros(nt) # final time = 1\n", "p[-1] = 1.0\n", "final = m.Param(value=p)\n", "# optimize final time\n", "tf = m.FV(value=1.0,lb=0.1,ub=100.0)\n", "tf.STATUS = 1\n", "# control changes every time period\n", "u = m.MV(value=0,lb=-2,ub=2)\n", "u.STATUS = 1\n", "m.Equation(x1.dt()==u*tf)\n", "m.Equation(x2.dt()==m.cos(x1)*tf)\n", "m.Equation(x3.dt()==m.sin(x1)*tf)\n", "m.Equation(x2*final<=0)\n", "m.Equation(x3*final<=0)\n", "m.Obj(tf)\n", "m.options.IMODE = 6\n", "m.solve(disp=False)\n", "print('Final Time: ' + str(tf.value[0]))\n", "tm = np.linspace(0,tf.value[0],nt)\n", "plt.figure(1)\n", "plt.plot(tm,x1.value,'k-',label=r'$x_1$')\n", "plt.plot(tm,x2.value,'b-',label=r'$x_2$')\n", "plt.plot(tm,x3.value,'g--',label=r'$x_3$')\n", "plt.plot(tm,u.value,'r--',label=r'$u$')\n", "plt.legend(loc='best')\n", "plt.xlabel('Time')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 14: PID Control Tuning\n", "\n", "A [PID Controller](https://en.wikipedia.org/wiki/PID_controller) has proportional, integral, and derivative terms to determine the controller output ($OP$) based on the set point ($SP$) and process variable ($PV$). A standard PID form has constants $K_c$, $\\tau_I$, and $\\tau_D$.\n", "\n", "\n", "$err = SP-PV$\n", "\n", "$OP = OP_0 + K_c \\, err + \\frac{K_c}{\\tau_I} \\int err \\, dt - K_c \\, \\tau_D \\frac{d\\,PV}{dt}$ \n", "\n", "The effect of the tuning constants is shown with the [PID Tuning Notebook](http://nbviewer.jupyter.org/url/apmonitor.com/pdc/uploads/Main/pid_widget.ipynb). This example is an alternative implementation in GEKKO." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "m = GEKKO()\n", "tf = 40\n", "m.time = np.linspace(0,tf,2*tf+1)\n", "step = np.zeros(2*tf+1)\n", "step[3:40] = 2.0\n", "step[40:] = 5.0\n", "\n", "# Controller model\n", "Kc = 15.0 # controller gain\n", "tauI = 2.0 # controller reset time\n", "tauD = 1.0 # derivative constant\n", "OP_0 = m.Const(value=0.0) # OP bias\n", "OP = m.Var(value=0.0) # controller output\n", "PV = m.Var(value=0.0) # process variable\n", "SP = m.Param(value=step) # set point\n", "Intgl = m.Var(value=0.0) # integral of the error\n", "err = m.Intermediate(SP-PV) # set point error\n", "m.Equation(Intgl.dt()==err) # integral of the error\n", "m.Equation(OP == OP_0 + Kc*err + (Kc/tauI)*Intgl - PV.dt())\n", "\n", "# Process model\n", "Kp = 0.5 # process gain\n", "tauP = 10.0 # process time constant\n", "m.Equation(tauP*PV.dt() + PV == Kp*OP)\n", "\n", "m.options.IMODE=4\n", "m.solve(disp=False)\n", "\n", "plt.figure()\n", "plt.subplot(2,1,1)\n", "plt.plot(m.time,OP.value,'b:',label='OP')\n", "plt.ylabel('Output')\n", "plt.legend()\n", "plt.subplot(2,1,2)\n", "plt.plot(m.time,SP.value,'k-',label='SP')\n", "plt.plot(m.time,PV.value,'r--',label='PV')\n", "plt.xlabel('Time (sec)')\n", "plt.ylabel('Process')\n", "plt.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 15: Process Simulator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "# Generate \"data\" with process simulation\n", "nt = 51\n", "# input steps\n", "u_meas = np.zeros(nt)\n", "u_meas[3:10] = 1.0\n", "u_meas[10:20] = 2.0\n", "u_meas[20:40] = 0.5\n", "u_meas[40:] = 3.0\n", "# simulation model\n", "p = GEKKO()\n", "p.time = np.linspace(0,10,nt)\n", "n = 1 #process model order\n", "# Parameters\n", "steps = np.zeros(n)\n", "p.u = p.MV(value=u_meas)\n", "p.u.FSTATUS=1\n", "p.K = p.Param(value=1) #gain\n", "p.tau = p.Param(value=5) #time constant\n", "# Intermediate\n", "p.x = [p.Intermediate(p.u)]\n", "# Variables\n", "p.x.extend([p.Var() for _ in range(n)]) #state variables\n", "p.y = p.SV() #measurement\n", "# Equations\n", "p.Equations([p.tau/n * p.x[i+1].dt() == -p.x[i+1] + p.x[i] for i in range(n)])\n", "p.Equation(p.y == p.K * p.x[n])\n", "# Simulate\n", "p.options.IMODE = 4\n", "p.solve(disp=False)\n", "# add measurement noise\n", "y_meas = (np.random.rand(nt)-0.5)*0.2\n", "for i in range(nt):\n", " y_meas[i] += p.y.value[i]\n", "plt.plot(p.time,u_meas,'b:',label='Input (u) meas')\n", "plt.plot(p.time,y_meas,'ro',label='Output (y) meas')\n", "plt.plot(p.time,p.y.value,'k-',label='Output (y) actual')\n", "plt.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 16: Moving Horizon Estimation\n", "\n", "Run the Process Simulation cell above to generate the data. The MHE application uses a first order model while the process simulation is a second order system. This is done to emulate a realistic case with model mismatch and measurement noise.\n", "\n", "This demonstrates just one cycle of an MHE application. Typical MHE applications receive an additional measurements, re-optimize parameters and states, and re-inject the parameters into a controller. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "# Estimator Model\n", "m = GEKKO()\n", "m.time = p.time\n", "# Parameters\n", "m.u = m.MV(value=u_meas) #input\n", "m.K = m.FV(value=1, lb=1, ub=3) # gain\n", "m.tau = m.FV(value=5, lb=1, ub=10) # time constant\n", "# Variables\n", "m.x = m.SV() #state variable\n", "m.y = m.CV(value=y_meas) #measurement\n", "# Equations\n", "m.Equations([m.tau * m.x.dt() == -m.x + m.u, \n", " m.y == m.K * m.x])\n", "# Options\n", "m.options.IMODE = 5 #MHE\n", "m.options.EV_TYPE = 1\n", "# STATUS = 0, optimizer doesn't adjust value\n", "# STATUS = 1, optimizer can adjust\n", "m.u.STATUS = 0\n", "m.K.STATUS = 1\n", "m.tau.STATUS = 1\n", "m.y.STATUS = 1\n", "# FSTATUS = 0, no measurement\n", "# FSTATUS = 1, measurement used to update model\n", "m.u.FSTATUS = 1\n", "m.K.FSTATUS = 0\n", "m.tau.FSTATUS = 0\n", "m.y.FSTATUS = 1\n", "# DMAX = maximum movement each cycle\n", "m.K.DMAX = 2.0\n", "m.tau.DMAX = 4.0\n", "# MEAS_GAP = dead-band for measurement / model mismatch\n", "m.y.MEAS_GAP = 0.25\n", "\n", "# solve\n", "m.solve(disp=False)\n", "\n", "# Plot results\n", "plt.subplot(2,1,1)\n", "plt.plot(m.time,u_meas,'b:',label='Input (u) meas')\n", "plt.legend()\n", "plt.subplot(2,1,2)\n", "plt.plot(m.time,y_meas,'gx',label='Output (y) meas')\n", "plt.plot(p.time,p.y.value,'k-',label='Output (y) actual')\n", "plt.plot(m.time,m.y.value,'r--',label='Output (y) estimated')\n", "plt.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 17: Model Predictive Control" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "import numpy as np\n", "from random import random\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "m = GEKKO()\n", "m.time = np.linspace(0,20,41)\n", "\n", "# Parameters\n", "mass = 500\n", "b = m.Param(value=50)\n", "K = m.Param(value=0.8)\n", "\n", "# Manipulated variable\n", "p = m.MV(value=0, lb=0, ub=100)\n", "p.STATUS = 1 # allow optimizer to change\n", "p.DCOST = 0.1 # smooth out gas pedal movement\n", "p.DMAX = 20 # slow down change of gas pedal\n", "\n", "# Controlled Variable\n", "v = m.CV(value=0)\n", "v.STATUS = 1 # add the SP to the objective\n", "m.options.CV_TYPE = 2 # squared error\n", "v.SP = 40 # set point\n", "v.TR_INIT = 1 # set point trajectory\n", "v.TAU = 5 # time constant of trajectory\n", "\n", "# Process model\n", "m.Equation(mass*v.dt() == -v*b + K*b*p)\n", "\n", "m.options.IMODE = 6 # control\n", "m.solve(disp=False)\n", "\n", "# get additional solution information\n", "import json\n", "with open(m.path+'//results.json') as f:\n", " results = json.load(f)\n", "\n", "plt.figure()\n", "plt.subplot(2,1,1)\n", "plt.plot(m.time,p.value,'b-',label='MV Optimized')\n", "plt.legend()\n", "plt.ylabel('Input')\n", "plt.subplot(2,1,2)\n", "plt.plot(m.time,results['v1.tr'],'k-',label='Reference Trajectory')\n", "plt.plot(m.time,v.value,'r--',label='CV Response')\n", "plt.ylabel('Output')\n", "plt.xlabel('Time')\n", "plt.legend(loc='best')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 18: Debugging Resources\n", "\n", "Applications may need a more detailed inspection to find errors in programming syntax, errors in modeling assumptions, or to generate good initial guess values. The GEKKO or the solver solution reports syntax errors. Setting m.solve(disp=True) displays the solver output with a message on the line of code that is unsuccessful. Naming the variables such as name='state' is helpful to display the equations in a readable form.\n", "\n", "Other strategies for obtaining a successful solution include:\n", "\n", "* Increase the number of iterations with MAX_ITER (0-1000+)\n", "* Increase the diagnostic level with DIAGLEVEL (0-10)\n", "* Change the solver with SOLVER (1-5)\n", "* Calculate model SENSITIVITY (1)\n", "* Solve a square problem with # Variables = # Equations\n", "* Set COLDSTART to initialize problem (0-2)\n", "\n", "Additional [modeling](http://apmonitor.com/do/index.php/Main/ModelFormulation), [initialization](http://apmonitor.com/do/index.php/Main/ModelInitialization), and [decomposition](https://www.sciencedirect.com/science/article/pii/S0098135415001179) tips may be helpful. There is also an online [discussion group](http://apmonitor.com/wiki/index.php/Main/UsersGroup), [video playlist](https://www.youtube.com/playlist?list=PLLBUgWXdTBDjcqDl2e5F_hcBjEc6vjr1X), [GEKKO documentation](http://gekko.readthedocs.io/en/latest/), and [APMonitor documentation](http://apmonitor.com/wiki/index.php/Main/HomePage) as additional resources." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gekko import GEKKO\n", "\n", "m = GEKKO() # create GEKKO model\n", "\n", "print('--------- Follow local path to view files --------------')\n", "print(m.path) # show source file path\n", "print('--------------------------------------------------------')\n", "\n", "# test application\n", "u = m.FV(value=5,name='u') # define fixed value\n", "x = m.SV(name='state') # define state variable\n", "m.Equation(x==u) # define equation\n", "m.options.COLDSTART = 1 # coldstart option\n", "m.options.DIAGLEVEL = 0 # diagnostic level (0-10)\n", "m.options.MAX_ITER = 500 # adjust maximum iterations\n", "m.options.SENSITIVITY = 1 # sensitivity analysis\n", "m.options.SOLVER = 1 # change solver (1=APOPT,3=IPOPT)\n", "m.solve(disp=True) # solve locally (remote=False)\n", "print('x: ' + str(x.value)) # print variable value" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 1 }