From e65952958bd09230a235d364d96c78fe16bd924a Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 10 Jun 2025 19:45:41 +0200 Subject: [PATCH 01/37] Renamed current example file to "pmsm_ccs_mpc_dq_current_control.ipynb" and created new file for finite current control --- .../pmsm_ccs_mpc_dq_current_control.ipynb | 440 ++++++++++++++++++ .../pmsm_fcs_mpc_dq_current_control.ipynb | 405 ++++++++++++++++ .../pmsm_mpc_dq_current_control.ipynb | 392 ---------------- 3 files changed, 845 insertions(+), 392 deletions(-) create mode 100644 examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb create mode 100644 examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb delete mode 100644 examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb diff --git a/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb new file mode 100644 index 00000000..c62ece30 --- /dev/null +++ b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb @@ -0,0 +1,440 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# PMSM MPC dq current control" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", + "\n", + "![MPC2](img/mpc_structure.png)\n", + "\n", + "![MPC1](img/mpc_scheme.png)\n", + "\n", + "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", + "\n", + "![Limits](img/voltage_limits.png)\n", + "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation of the required toolboxes" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33m DEPRECATION: Building 'gekko' using the legacy setup.py bdist_wheel mechanism, which will be removed in a future version. pip 25.3 will enforce this behaviour change. A possible replacement is to use the standardized build interface by setting the `--use-pep517` option, (possibly combined with `--no-build-isolation`), or adding a `pyproject.toml` file to the source tree of 'gekko'. Discussion can be found at https://github.com/pypa/pip/issues/6334\u001b[0m\u001b[33m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", + "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import math\n", + "import matplotlib\n", + "from gekko import GEKKO\n", + "\n", + "import gym_electric_motor as gem\n", + "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", + "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", + "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", + " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", + "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", + "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of a general controller class" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class Controller:\n", + "\n", + " @classmethod\n", + " def make(cls, controller_type, environment, **controller_kwargs):\n", + " assert controller_type in _controllers.keys(), f'Controller {controller_type} unknown'\n", + " controller = _controllers[controller_type](environment, **controller_kwargs)\n", + " return controller\n", + "\n", + " def control(self, state, reference):\n", + " pass\n", + "\n", + " def reset(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the MPC class" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "class MPC(Controller):\n", + " def __init__(self, environment, ph=5, ref_idx_q=0, ref_idx_d=1):\n", + " # conversion of the coordinate systems\n", + " t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", + " q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", + " self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", + " \n", + " # indices\n", + " self.ref_idx_i_q = ref_idx_q\n", + " self.ref_idx_i_d = ref_idx_d\n", + " self.i_sd_idx = environment.get_wrapper_attr('state_names').index('i_sd')\n", + " self.i_sq_idx = environment.get_wrapper_attr('state_names').index('i_sq')\n", + " self.u_a_idx = environment.get_wrapper_attr('state_names').index('u_a')\n", + " self.u_b_idx = environment.get_wrapper_attr('state_names').index('u_b')\n", + " self.u_c_idx = environment.get_wrapper_attr('state_names').index('u_c')\n", + " self.u_sq_idx = environment.get_wrapper_attr('state_names').index('u_sq')\n", + " self.u_sd_idx = environment.get_wrapper_attr('state_names').index('u_sd')\n", + " self.omega_idx = environment.get_wrapper_attr('state_names').index('omega')\n", + " self.epsilon_idx = environment.get_wrapper_attr('state_names').index('epsilon')\n", + "\n", + " # motor parameters\n", + " self.tau = environment.get_wrapper_attr('physical_system').tau\n", + " self.limits = environment.get_wrapper_attr('physical_system').limits\n", + " self.l_q = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_q']\n", + " self.l_d = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_d']\n", + " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", + " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", + " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", + " self.ph_ = ph\n", + "\n", + " def control(self, state, reference):\n", + " # initialize variables\n", + " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", + " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", + "\n", + " ref_q = []\n", + " ref_d = []\n", + " eps = []\n", + " lim_a_up = []\n", + " lim_a_low = []\n", + " \n", + " for i in range(self.ph_):\n", + " ref_q.append(reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx])\n", + " ref_d.append(reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx])\n", + " \n", + " eps.append(epsilon_el + (i-1) * self.tau * omega)\n", + " lim_a_up.append(2 * self.limits[self.u_a_idx])\n", + " lim_a_low.append(-2 * self.limits[self.u_a_idx])\n", + " \n", + " m = GEKKO(remote=False)\n", + " \n", + " # defenition of the prediction Horizon\n", + " m.time = np.linspace(self.tau, self.tau * self.ph_, self.ph_)\n", + "\n", + " # defenition of the variables\n", + " u_d = m.MV(value=state[self.u_sd_idx] * self.limits[self.u_sd_idx])\n", + " u_q = m.MV(value=state[self.u_sq_idx] * self.limits[self.u_sq_idx])\n", + " u_d.STATUS = 1\n", + " u_q.STATUS = 1\n", + "\n", + " u_a_lim_up = m.Param(value=lim_a_up)\n", + " u_a_lim_low = m.Param(value=lim_a_low)\n", + " sq3 = math.sqrt(3)\n", + "\n", + " i_d = m.SV(value=state[self.i_sd_idx] * self.limits[self.i_sd_idx], lb=-self.limits[self.i_sd_idx], ub=self.limits[self.i_sd_idx] )\n", + " i_q = m.SV(value=state[self.i_sq_idx] * self.limits[self.i_sq_idx], lb=-self.limits[self.i_sq_idx], ub=self.limits[self.i_sq_idx])\n", + "\n", + " epsilon = m.Param(value=eps)\n", + " \n", + " # reference trajectory\n", + " traj_d = m.Param(value=ref_d)\n", + " traj_q = m.Param(value=ref_q)\n", + " \n", + " # defenition of the constants\n", + " omega = m.Const(value=omega)\n", + " psi = m.Const(value=self.psi_)\n", + " rs = m.Const(value=self.r_s)\n", + " ld = m.Const(value=self.l_d)\n", + " lq = m.Const(value=self.l_q)\n", + " \n", + " # control error\n", + " e_d = m.CV()\n", + " e_q = m.CV()\n", + " e_d.STATUS = 1\n", + " e_q.STATUS = 1\n", + " \n", + " # solver options\n", + " m.options.CV_TYPE = 2\n", + " m.options.IMODE = 6\n", + " m.options.solver = 3\n", + " m.options.WEB = 0\n", + " m.options.NODES = 2\n", + " \n", + " # differential equations\n", + " m.Equations([ld * i_d.dt() == u_d - rs * i_d + omega * lq * i_q,\n", + " lq * i_q.dt() == u_q - rs * i_q - omega * ld * i_d - omega * psi])\n", + " \n", + " # cost function\n", + " m.Equations([e_d == (i_d - traj_d), e_q == (i_q - traj_q)])\n", + " \n", + " # voltage limitations\n", + " m.Equation(u_a_lim_up >= 3/2 * m.cos(epsilon) * u_d - 3/2 * m.sin(epsilon) * u_q - sq3/2 * m.sin(epsilon) * u_d - sq3/2 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_low <= 3 / 2 * m.cos(epsilon) * u_d - 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_up >= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_low <= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_up >= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_low <= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", + " \n", + " # object to minimize\n", + " m.Obj(e_d)\n", + " m.Obj(e_q)\n", + " \n", + " # solving optimization problem\n", + " m.solve(disp=False)\n", + " \n", + " # additional voltage limitation\n", + " u_a, u_b, u_c = self._backward_transformation((u_q.NEWVAL, u_d.NEWVAL), epsilon_el)\n", + " u_max = max(np.absolute(u_a - u_b), np.absolute(u_b - u_c), np.absolute(u_c - u_a))\n", + " if u_max >= 2 * self.limits[self.u_a_idx]:\n", + " u_a = u_a / u_max * 2 * self.limits[self.u_a_idx]\n", + " u_b = u_b / u_max * 2 * self.limits[self.u_a_idx]\n", + " u_c = u_c / u_max * 2 * self.limits[self.u_a_idx]\n", + " \n", + " # Zero Point Shift\n", + " u_0 = 0.5 * (max(u_a, u_b, u_c) + min(u_a, u_b, u_c))\n", + " u_a -= u_0\n", + " u_b -= u_0\n", + " u_c -= u_0\n", + " \n", + " # normalization of the manipulated variables\n", + " u_a /= self.limits[self.u_a_idx]\n", + " u_b /= self.limits[self.u_b_idx]\n", + " u_c /= self.limits[self.u_c_idx]\n", + " \n", + " return u_a, u_b, u_c\n", + "\n", + " def reset(self):\n", + " None\n", + "\n", + "\n", + "_controllers = {\n", + " 'mpc': MPC\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the setting parameters of the motor and the limit values " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "motor_parameter = dict(r_s=15e-3, l_d=0.37e-3, l_q=1.2e-3, psi_p=65.6e-3, p=3, j_rotor=0.06)\n", + "limit_values = dict(i=160 * 1.41, omega=12000 * np.pi / 30, u=450)\n", + "nominal_values = {key: 0.7 * limit for key, limit in limit_values.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of reference generators for the d- and q- components of the current" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "q_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "d_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "reference_generator = MultipleReferenceGenerator([q_generator, d_generator])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, the PMSM is initialized with the previously defined motor parameters and reference generators. The MPC controller is initialized with the specified prediction horizon. Then the motor is simulated step by step. For this purpose the function for visualization is called. The manipulated variables are calculated by the MPC Controller and switched to the PMSM. This takes a long time, because the numerical effort of the MPC is large. Finally, the reward is output.\n", + "\n", + "![Graph](img/mpc_currents_voltages.png)\n", + "The graphs show exemplary the currents and voltages for the control of a reference profile." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib notebook\n", + "#%matplotlib widget\n", + "# use %matplotlib widget for execution in visual studio code\n", + "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", + "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", + " ControlType.CurrentControl,\n", + " ActionType.Continuous)\n", + "env = gem.make(motor.env_id(),\n", + " visualization=visu,\n", + " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", + " ode_solver=ScipySolveIvpSolver(), # ODE Solver of the simulation\n", + " reference_generator=reference_generator, # initialize the reference generators\n", + " \n", + " # defenitions for the reward function\n", + " reward_function=dict(\n", + " reward_weights={'i_sq': 1, 'i_sd': 1},\n", + " reward_power=0.5,\n", + " ),\n", + " \n", + " # definitions for the setting parameters\n", + " supply=dict(u_nominal=400),\n", + " motor=dict(\n", + " motor_parameter=motor_parameter,\n", + " limit_values=limit_values,\n", + " nominal_values=nominal_values\n", + " )\n", + " )\n", + "\n", + "visu.initialize()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:158: UserWarning: \u001b[33mWARN: The obs returned by the `step()` method is not within the observation space.\u001b[0m\n", + " logger.warn(f\"{pre} is not within the observation space.\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reward = -550.5100758083643\n" + ] + } + ], + "source": [ + "controller = Controller.make('mpc', env, ph=3) # initializing the MPC Controller\n", + "(state, reference), _ = env.reset()\n", + "cum_rew = 0\n", + "\n", + "for i in range(10000): # simulate the PMSM for 10000 steps (1 second)\n", + " env.render() # function for visualization\n", + " action = controller.control(state, reference) # calculation of the manipulated variables\n", + " (state, reference), reward, terminated, truncated, _ = env.step(action) # simulate one step of the PMSM\n", + " cum_rew += reward # adding up the Reward \n", + " if terminated:\n", + " (state, reference), _ = env.reset()\n", + " controller.reset()\n", + "print('Reward =', cum_rew)\n", + "env.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PE", + "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.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb new file mode 100644 index 00000000..3855a17f --- /dev/null +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -0,0 +1,405 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# PMSM MPC dq current control" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", + "\n", + "![MPC2](img/mpc_structure.png)\n", + "\n", + "![MPC1](img/mpc_scheme.png)\n", + "\n", + "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", + "\n", + "![Limits](img/voltage_limits.png)\n", + "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation of the required toolboxes" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", + "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import math\n", + "import matplotlib\n", + "from gekko import GEKKO\n", + "\n", + "import gym_electric_motor as gem\n", + "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", + "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", + "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", + " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", + "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", + "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of a general controller class" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "class Controller:\n", + "\n", + " @classmethod\n", + " def make(cls, controller_type, environment, **controller_kwargs):\n", + " assert controller_type in _controllers.keys(), f'Controller {controller_type} unknown'\n", + " controller = _controllers[controller_type](environment, **controller_kwargs)\n", + " return controller\n", + "\n", + " def control(self, state, reference):\n", + " pass\n", + "\n", + " def reset(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the MPC class" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "class FCS_MPC(Controller):\n", + " def __init__(self, environment, ph=1, ref_idx_q=0, ref_idx_d=1):\n", + " # conversion of the coordinate systems\n", + " #t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", + " #q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", + " #self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", + " # Manual transformation implementations\n", + "\n", + " def clarke(abc):\n", + " \"\"\"abc → αβ transformation (3-phase to 2-phase)\"\"\"\n", + " a, b, c = abc\n", + " alpha = a - 0.5*b - 0.5*c # = a (when a + b + c = 0)\n", + " beta = (np.sqrt(3)/2)*b - (np.sqrt(3)/2)*c\n", + " return np.array([alpha, beta]) * (2/3) # Power-invariant version\n", + " \n", + " def park(alpha_beta, theta):\n", + " \"\"\"αβ → dq transformation\"\"\"\n", + " alpha, beta = alpha_beta\n", + " d = alpha * np.cos(theta) + beta * np.sin(theta)\n", + " q = -alpha * np.sin(theta) + beta * np.cos(theta)\n", + " return np.array([d, q])\n", + " \n", + " \n", + " # Create transformation functions\n", + " self._forward_transformation = lambda abc, eps: park(clarke(abc), eps)\n", + " \n", + " \n", + " # indices\n", + " self.ref_idx_i_q = ref_idx_q\n", + " self.ref_idx_i_d = ref_idx_d\n", + " self.i_sd_idx = environment.get_wrapper_attr('state_names').index('i_sd')\n", + " self.i_sq_idx = environment.get_wrapper_attr('state_names').index('i_sq')\n", + " self.u_a_idx = environment.get_wrapper_attr('state_names').index('u_a')\n", + " self.u_b_idx = environment.get_wrapper_attr('state_names').index('u_b')\n", + " self.u_c_idx = environment.get_wrapper_attr('state_names').index('u_c')\n", + " self.u_sq_idx = environment.get_wrapper_attr('state_names').index('u_sq')\n", + " self.u_sd_idx = environment.get_wrapper_attr('state_names').index('u_sd')\n", + " self.omega_idx = environment.get_wrapper_attr('state_names').index('omega')\n", + " self.epsilon_idx = environment.get_wrapper_attr('state_names').index('epsilon')\n", + "\n", + " # motor parameters\n", + " self.tau = environment.get_wrapper_attr('physical_system').tau\n", + " self.limits = environment.get_wrapper_attr('physical_system').limits\n", + " self.l_q = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_q']\n", + " self.l_d = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_d']\n", + " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", + " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", + " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", + " self.ph_ = ph # Prediction horizon (typically 1 for FCS-MPC)\n", + " \n", + " # Define the finite set of voltage vectors (for a 2-level inverter)\n", + " # These are the 8 possible switching states (6 active + 2 zero vectors)\n", + " self.voltage_vectors = [ \n", + " (2/3, -1/3, -1/3), # V1\n", + " (1/3, 1/3, -2/3), # V2\n", + " (-1/3, 2/3, -1/3), # V3\n", + " (-2/3, 1/3, 1/3), # V4\n", + " (-1/3, -1/3, 2/3), # V5\n", + " (1/3, -2/3, 1/3), # V6 \n", + " (0, 0, 0), # V0\n", + " (1/3, 1/3, 1/3) # V7\n", + " ]\n", + " \n", + " # Scale vectors by maximum voltage\n", + " self.voltage_vectors = [\n", + " (v_a * self.limits[self.u_a_idx], \n", + " v_b * self.limits[self.u_b_idx], \n", + " v_c * self.limits[self.u_c_idx])\n", + " for v_a, v_b, v_c in self.voltage_vectors\n", + " ]\n", + "\n", + " def control(self, state, reference):\n", + " # Get current state values\n", + " i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx]\n", + " i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx]\n", + " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", + " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", + " \n", + " # Reference values\n", + " ref_i_q = reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx]\n", + " ref_i_d = reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx]\n", + " \n", + " # Initialize variables for tracking the best vector\n", + " min_cost = float('inf')\n", + " best_vector = (0, 0, 0)\n", + " \n", + " # Evaluate each possible voltage vector\n", + " for v_a, v_b, v_c in self.voltage_vectors:\n", + " # Convert to dq coordinates\n", + " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", + " v_d, v_q = v_dq[0], v_dq[1] # Now properly ordered\n", + " \n", + " # Predict next state using motor model (Euler approximation)\n", + " # d-axis current prediction\n", + " i_d_next = i_d + self.tau * (\n", + " (v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d\n", + " )\n", + " \n", + " # q-axis current prediction\n", + " i_q_next = i_q + self.tau * (\n", + " (v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q\n", + " )\n", + " \n", + " # Calculate cost function (typically squared error)\n", + " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", + " \n", + " # Track the vector with minimum cost\n", + " if cost < min_cost:\n", + " min_cost = cost\n", + " best_vector = (v_a, v_b, v_c)\n", + " \n", + " # Normalize the output to [-1, 1] range\n", + " u_a = best_vector[0] / self.limits[self.u_a_idx]\n", + " u_b = best_vector[1] / self.limits[self.u_b_idx]\n", + " u_c = best_vector[2] / self.limits[self.u_c_idx]\n", + " \n", + " return u_a, u_b, u_c\n", + "\n", + " def reset(self):\n", + " None\n", + "\n", + "\n", + "_controllers = { \n", + " 'fcs_mpc': FCS_MPC\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the setting parameters of the motor and the limit values " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "motor_parameter = dict(r_s=15e-3, l_d=0.37e-3, l_q=1.2e-3, psi_p=65.6e-3, p=3, j_rotor=0.06)\n", + "limit_values = dict(i=160 * 1.41, omega=12000 * np.pi / 30, u=450)\n", + "nominal_values = {key: 0.7 * limit for key, limit in limit_values.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of reference generators for the d- and q- components of the current" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "q_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "d_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "reference_generator = MultipleReferenceGenerator([q_generator, d_generator])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, the PMSM is initialized with the previously defined motor parameters and reference generators. The MPC controller is initialized with the specified prediction horizon. Then the motor is simulated step by step. For this purpose the function for visualization is called. The manipulated variables are calculated by the MPC Controller and switched to the PMSM. This takes a long time, because the numerical effort of the MPC is large. Finally, the reward is output.\n", + "\n", + "![Graph](img/mpc_currents_voltages.png)\n", + "The graphs show exemplary the currents and voltages for the control of a reference profile." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib notebook\n", + "#%matplotlib widget\n", + "# use %matplotlib widget for execution in visual studio code\n", + "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", + "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", + " ControlType.CurrentControl,\n", + " ActionType.Continuous)\n", + "env = gem.make(motor.env_id(),\n", + " visualization=visu,\n", + " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", + " ode_solver=ScipySolveIvpSolver(), # ODE Solver of the simulation\n", + " reference_generator=reference_generator, # initialize the reference generators\n", + " \n", + " # defenitions for the reward function\n", + " reward_function=dict(\n", + " reward_weights={'i_sq': 1, 'i_sd': 1},\n", + " reward_power=0.5,\n", + " ),\n", + " \n", + " # definitions for the setting parameters\n", + " supply=dict(u_nominal=400),\n", + " motor=dict(\n", + " motor_parameter=motor_parameter,\n", + " limit_values=limit_values,\n", + " nominal_values=nominal_values\n", + " )\n", + " )\n", + "\n", + "visu.initialize()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reward = -2034.393103107837\n" + ] + } + ], + "source": [ + "controller = Controller.make('fcs_mpc', env, ph=3) # initializing the MPC Controller\n", + "(state, reference), _ = env.reset()\n", + "cum_rew = 0\n", + "\n", + "for i in range(10000): # simulate the PMSM for 10000 steps (1 second)\n", + " env.render() # function for visualization\n", + " action = controller.control(state, reference) # calculation of the manipulated variables\n", + " (state, reference), reward, terminated, truncated, _ = env.step(action) # simulate one step of the PMSM\n", + " cum_rew += reward # adding up the Reward \n", + " if terminated:\n", + " (state, reference), _ = env.reset()\n", + " controller.reset()\n", + "print('Reward =', cum_rew)\n", + "env.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PE", + "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.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb deleted file mode 100644 index a358d52a..00000000 --- a/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb +++ /dev/null @@ -1,392 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# PMSM MPC dq current control" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", - "\n", - "![MPC2](img/mpc_structure.png)\n", - "\n", - "![MPC1](img/mpc_scheme.png)\n", - "\n", - "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", - "\n", - "![Limits](img/voltage_limits.png)\n", - "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Installation of the required toolboxes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", - "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import math\n", - "import matplotlib\n", - "from gekko import GEKKO\n", - "\n", - "import gym_electric_motor as gem\n", - "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", - "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", - "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", - " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", - "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", - "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of a general controller class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class Controller:\n", - "\n", - " @classmethod\n", - " def make(cls, controller_type, environment, **controller_kwargs):\n", - " assert controller_type in _controllers.keys(), f'Controller {controller_type} unknown'\n", - " controller = _controllers[controller_type](environment, **controller_kwargs)\n", - " return controller\n", - "\n", - " def control(self, state, reference):\n", - " pass\n", - "\n", - " def reset(self):\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of the MPC class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class MPC(Controller):\n", - " def __init__(self, environment, ph=5, ref_idx_q=0, ref_idx_d=1):\n", - " # conversion of the coordinate systems\n", - " t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", - " q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", - " self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", - " \n", - " # indices\n", - " self.ref_idx_i_q = ref_idx_q\n", - " self.ref_idx_i_d = ref_idx_d\n", - " self.i_sd_idx = environment.get_wrapper_attr('state_names').index('i_sd')\n", - " self.i_sq_idx = environment.get_wrapper_attr('state_names').index('i_sq')\n", - " self.u_a_idx = environment.get_wrapper_attr('state_names').index('u_a')\n", - " self.u_b_idx = environment.get_wrapper_attr('state_names').index('u_b')\n", - " self.u_c_idx = environment.get_wrapper_attr('state_names').index('u_c')\n", - " self.u_sq_idx = environment.get_wrapper_attr('state_names').index('u_sq')\n", - " self.u_sd_idx = environment.get_wrapper_attr('state_names').index('u_sd')\n", - " self.omega_idx = environment.get_wrapper_attr('state_names').index('omega')\n", - " self.epsilon_idx = environment.get_wrapper_attr('state_names').index('epsilon')\n", - "\n", - " # motor parameters\n", - " self.tau = environment.get_wrapper_attr('physical_system').tau\n", - " self.limits = environment.get_wrapper_attr('physical_system').limits\n", - " self.l_q = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_q']\n", - " self.l_d = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_d']\n", - " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", - " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", - " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", - " self.ph_ = ph\n", - "\n", - " def control(self, state, reference):\n", - " # initialize variables\n", - " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", - " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", - "\n", - " ref_q = []\n", - " ref_d = []\n", - " eps = []\n", - " lim_a_up = []\n", - " lim_a_low = []\n", - " \n", - " for i in range(self.ph_):\n", - " ref_q.append(reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx])\n", - " ref_d.append(reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx])\n", - " \n", - " eps.append(epsilon_el + (i-1) * self.tau * omega)\n", - " lim_a_up.append(2 * self.limits[self.u_a_idx])\n", - " lim_a_low.append(-2 * self.limits[self.u_a_idx])\n", - " \n", - " m = GEKKO(remote=False)\n", - " \n", - " # defenition of the prediction Horizon\n", - " m.time = np.linspace(self.tau, self.tau * self.ph_, self.ph_)\n", - "\n", - " # defenition of the variables\n", - " u_d = m.MV(value=state[self.u_sd_idx] * self.limits[self.u_sd_idx])\n", - " u_q = m.MV(value=state[self.u_sq_idx] * self.limits[self.u_sq_idx])\n", - " u_d.STATUS = 1\n", - " u_q.STATUS = 1\n", - "\n", - " u_a_lim_up = m.Param(value=lim_a_up)\n", - " u_a_lim_low = m.Param(value=lim_a_low)\n", - " sq3 = math.sqrt(3)\n", - "\n", - " i_d = m.SV(value=state[self.i_sd_idx] * self.limits[self.i_sd_idx], lb=-self.limits[self.i_sd_idx], ub=self.limits[self.i_sd_idx] )\n", - " i_q = m.SV(value=state[self.i_sq_idx] * self.limits[self.i_sq_idx], lb=-self.limits[self.i_sq_idx], ub=self.limits[self.i_sq_idx])\n", - "\n", - " epsilon = m.Param(value=eps)\n", - " \n", - " # reference trajectory\n", - " traj_d = m.Param(value=ref_d)\n", - " traj_q = m.Param(value=ref_q)\n", - " \n", - " # defenition of the constants\n", - " omega = m.Const(value=omega)\n", - " psi = m.Const(value=self.psi_)\n", - " rs = m.Const(value=self.r_s)\n", - " ld = m.Const(value=self.l_d)\n", - " lq = m.Const(value=self.l_q)\n", - " \n", - " # control error\n", - " e_d = m.CV()\n", - " e_q = m.CV()\n", - " e_d.STATUS = 1\n", - " e_q.STATUS = 1\n", - " \n", - " # solver options\n", - " m.options.CV_TYPE = 2\n", - " m.options.IMODE = 6\n", - " m.options.solver = 3\n", - " m.options.WEB = 0\n", - " m.options.NODES = 2\n", - " \n", - " # differential equations\n", - " m.Equations([ld * i_d.dt() == u_d - rs * i_d + omega * lq * i_q,\n", - " lq * i_q.dt() == u_q - rs * i_q - omega * ld * i_d - omega * psi])\n", - " \n", - " # cost function\n", - " m.Equations([e_d == (i_d - traj_d), e_q == (i_q - traj_q)])\n", - " \n", - " # voltage limitations\n", - " m.Equation(u_a_lim_up >= 3/2 * m.cos(epsilon) * u_d - 3/2 * m.sin(epsilon) * u_q - sq3/2 * m.sin(epsilon) * u_d - sq3/2 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_low <= 3 / 2 * m.cos(epsilon) * u_d - 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_up >= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_low <= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_up >= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_low <= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", - " \n", - " # object to minimize\n", - " m.Obj(e_d)\n", - " m.Obj(e_q)\n", - " \n", - " # solving optimization problem\n", - " m.solve(disp=False)\n", - " \n", - " # additional voltage limitation\n", - " u_a, u_b, u_c = self._backward_transformation((u_q.NEWVAL, u_d.NEWVAL), epsilon_el)\n", - " u_max = max(np.absolute(u_a - u_b), np.absolute(u_b - u_c), np.absolute(u_c - u_a))\n", - " if u_max >= 2 * self.limits[self.u_a_idx]:\n", - " u_a = u_a / u_max * 2 * self.limits[self.u_a_idx]\n", - " u_b = u_b / u_max * 2 * self.limits[self.u_a_idx]\n", - " u_c = u_c / u_max * 2 * self.limits[self.u_a_idx]\n", - " \n", - " # Zero Point Shift\n", - " u_0 = 0.5 * (max(u_a, u_b, u_c) + min(u_a, u_b, u_c))\n", - " u_a -= u_0\n", - " u_b -= u_0\n", - " u_c -= u_0\n", - " \n", - " # normalization of the manipulated variables\n", - " u_a /= self.limits[self.u_a_idx]\n", - " u_b /= self.limits[self.u_b_idx]\n", - " u_c /= self.limits[self.u_c_idx]\n", - " \n", - " return u_a, u_b, u_c\n", - "\n", - " def reset(self):\n", - " None\n", - "\n", - "\n", - "_controllers = {\n", - " 'mpc': MPC\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of the setting parameters of the motor and the limit values " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "motor_parameter = dict(r_s=15e-3, l_d=0.37e-3, l_q=1.2e-3, psi_p=65.6e-3, p=3, j_rotor=0.06)\n", - "limit_values = dict(i=160 * 1.41, omega=12000 * np.pi / 30, u=450)\n", - "nominal_values = {key: 0.7 * limit for key, limit in limit_values.items()}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of reference generators for the d- and q- components of the current" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "q_generator = SwitchedReferenceGenerator(\n", - " sub_generators=[\n", - " SinusoidalReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", - " StepReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.5)),\n", - " TriangularReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", - " ],\n", - " super_episode_length=(500, 1000)\n", - " )\n", - "\n", - "d_generator = SwitchedReferenceGenerator(\n", - " sub_generators=[\n", - " SinusoidalReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", - " StepReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.5)),\n", - " TriangularReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", - " ],\n", - " super_episode_length=(500, 1000)\n", - " )\n", - "\n", - "reference_generator = MultipleReferenceGenerator([q_generator, d_generator])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, the PMSM is initialized with the previously defined motor parameters and reference generators. The MPC controller is initialized with the specified prediction horizon. Then the motor is simulated step by step. For this purpose the function for visualization is called. The manipulated variables are calculated by the MPC Controller and switched to the PMSM. This takes a long time, because the numerical effort of the MPC is large. Finally, the reward is output.\n", - "\n", - "![Graph](img/mpc_currents_voltages.png)\n", - "The graphs show exemplary the currents and voltages for the control of a reference profile." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib notebook\n", - "#%matplotlib widget\n", - "# use %matplotlib widget for execution in visual studio code\n", - "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", - "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", - " ControlType.CurrentControl,\n", - " ActionType.Continuous)\n", - "env = gem.make(motor.env_id(),\n", - " visualization=visu,\n", - " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", - " ode_solver=ScipySolveIvpSolver(), # ODE Solver of the simulation\n", - " reference_generator=reference_generator, # initialize the reference generators\n", - " \n", - " # defenitions for the reward function\n", - " reward_function=dict(\n", - " reward_weights={'i_sq': 1, 'i_sd': 1},\n", - " reward_power=0.5,\n", - " ),\n", - " \n", - " # definitions for the setting parameters\n", - " supply=dict(u_nominal=400),\n", - " motor=dict(\n", - " motor_parameter=motor_parameter,\n", - " limit_values=limit_values,\n", - " nominal_values=nominal_values\n", - " )\n", - " )\n", - "\n", - "visu.initialize()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "controller = Controller.make('mpc', env, ph=3) # initializing the MPC Controller\n", - "(state, reference), _ = env.reset()\n", - "cum_rew = 0\n", - "\n", - "for i in range(10000): # simulate the PMSM for 10000 steps (1 second)\n", - " env.render() # function for visualization\n", - " action = controller.control(state, reference) # calculation of the manipulated variables\n", - " (state, reference), reward, terminated, truncated, _ = env.step(action) # simulate one step of the PMSM\n", - " cum_rew += reward # adding up the Reward \n", - " if terminated:\n", - " (state, reference), _ = env.reset()\n", - " controller.reset()\n", - "print('Reward =', cum_rew)\n", - "env.close()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "GEM2v0", - "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.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From 4c0f1ba63b81a53cd5193c4f3a949bc848571f9b Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 11 Jun 2025 09:52:37 +0200 Subject: [PATCH 02/37] renamed existing ccs mpc and added new file for fcs(finte control set) mpc. --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 3855a17f..00b438f8 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -161,7 +161,7 @@ " (-1/3, -1/3, 2/3), # V5\n", " (1/3, -2/3, 1/3), # V6 \n", " (0, 0, 0), # V0\n", - " (1/3, 1/3, 1/3) # V7\n", + " (0, 0, 0) # V7\n", " ]\n", " \n", " # Scale vectors by maximum voltage\n", From 8469ee1641c0858983f1865592d2fc62912aeddf Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 24 Jun 2025 23:17:29 +0200 Subject: [PATCH 03/37] Upadted the title of pmsm_ccs_mpc_dq_current_control.ipynb and implemented comments on pmsm_fcs_mpc_dq_current_control.ipynb file --- .../pmsm_ccs_mpc_dq_current_control.ipynb | 2 +- .../pmsm_fcs_mpc_dq_current_control.ipynb | 153 ++++++++++-------- 2 files changed, 88 insertions(+), 67 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb index c62ece30..88336519 100644 --- a/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# PMSM MPC dq current control" + "# PMSM CCS-MPC dq current control" ] }, { diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 00b438f8..5ab538ab 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -4,14 +4,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# PMSM MPC dq current control" + "# PMSM FCS-MPC dq current control" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", + "In this tutorial, we apply a finite-control set (FCS) model predictive control (MPC) approach to realize current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates.\n", + "Unlike continuous-control set (CCS) MPC, FCS-MPC directly evaluates a finite set of switching states to optimize the control input based on a cost function over a prediction horizon.\n", "\n", "![MPC2](img/mpc_structure.png)\n", "\n", @@ -32,24 +33,22 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ - "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import math\n", "import matplotlib\n", - "from gekko import GEKKO\n", "\n", "import gym_electric_motor as gem\n", "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", @@ -69,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -103,11 +102,7 @@ "source": [ "class FCS_MPC(Controller):\n", " def __init__(self, environment, ph=1, ref_idx_q=0, ref_idx_d=1):\n", - " # conversion of the coordinate systems\n", - " #t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", - " #q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", - " #self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", - " # Manual transformation implementations\n", + " # conversion of the coordinate systems \n", "\n", " def clarke(abc):\n", " \"\"\"abc → αβ transformation (3-phase to 2-phase)\"\"\"\n", @@ -171,6 +166,41 @@ " v_c * self.limits[self.u_c_idx])\n", " for v_a, v_b, v_c in self.voltage_vectors\n", " ]\n", + " \n", + " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth, max_depth):\n", + " min_cost = float('inf')\n", + " best_sequence = []\n", + "\n", + " for v_a, v_b, v_c in self.voltage_vectors:\n", + " # Convert to dq\n", + " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", + " v_d, v_q = v_dq[0], v_dq[1]\n", + "\n", + " # Predict next state\n", + " i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d)\n", + " i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q)\n", + "\n", + " # Cost for this step\n", + " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", + "\n", + " if depth == max_depth - 1:\n", + " total_cost = cost\n", + " sequence = [(v_a, v_b, v_c)]\n", + " else:\n", + " future_cost, future_sequence = self._simulate_sequence(\n", + " i_d_next, i_q_next, epsilon_el, omega, ref_i_d, ref_i_q, depth + 1, max_depth\n", + " )\n", + " total_cost = cost + future_cost\n", + " sequence = [(v_a, v_b, v_c)] + future_sequence\n", + "\n", + " if total_cost < min_cost:\n", + " min_cost = total_cost\n", + " best_sequence = sequence\n", + "\n", + " return min_cost, best_sequence\n", + "\n", + "\n", + "\n", "\n", " def control(self, state, reference):\n", " # Get current state values\n", @@ -178,47 +208,27 @@ " i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx]\n", " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", - " \n", + "\n", " # Reference values\n", " ref_i_q = reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx]\n", " ref_i_d = reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx]\n", - " \n", - " # Initialize variables for tracking the best vector\n", - " min_cost = float('inf')\n", - " best_vector = (0, 0, 0)\n", - " \n", - " # Evaluate each possible voltage vector\n", - " for v_a, v_b, v_c in self.voltage_vectors:\n", - " # Convert to dq coordinates\n", - " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", - " v_d, v_q = v_dq[0], v_dq[1] # Now properly ordered\n", - " \n", - " # Predict next state using motor model (Euler approximation)\n", - " # d-axis current prediction\n", - " i_d_next = i_d + self.tau * (\n", - " (v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d\n", - " )\n", - " \n", - " # q-axis current prediction\n", - " i_q_next = i_q + self.tau * (\n", - " (v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q\n", - " )\n", - " \n", - " # Calculate cost function (typically squared error)\n", - " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", - " \n", - " # Track the vector with minimum cost\n", - " if cost < min_cost:\n", - " min_cost = cost\n", - " best_vector = (v_a, v_b, v_c)\n", - " \n", - " # Normalize the output to [-1, 1] range\n", - " u_a = best_vector[0] / self.limits[self.u_a_idx]\n", - " u_b = best_vector[1] / self.limits[self.u_b_idx]\n", - " u_c = best_vector[2] / self.limits[self.u_c_idx]\n", - " \n", + "\n", + " # Run recursive simulation over all ph-length sequences\n", + " _, best_sequence = self._simulate_sequence(\n", + " i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0, max_depth=self.ph_\n", + " )\n", + "\n", + " # Take the first control input in the best sequence\n", + " best_v = best_sequence[0] if best_sequence else (0, 0, 0)\n", + "\n", + " # Normalize\n", + " u_a = best_v[0] / self.limits[self.u_a_idx]\n", + " u_b = best_v[1] / self.limits[self.u_b_idx]\n", + " u_c = best_v[2] / self.limits[self.u_c_idx]\n", + "\n", " return u_a, u_b, u_c\n", "\n", + "\n", " def reset(self):\n", " None\n", "\n", @@ -237,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -255,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -292,26 +302,37 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 9, "metadata": {}, "outputs": [ { - "data": { - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:35: UserWarning: \u001b[33mWARN: A Box observation space maximum and minimum values are equal.\u001b[0m\n", + " logger.warn(\"A Box observation space maximum and minimum values are equal.\")\n" + ] }, { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e6111102ae134545bacb562a47d5f829", + "version_major": 2, + "version_minor": 0 + }, + "image/png": "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", "text/html": [ - "
" + "\n", + "
\n", + "
\n", + " Time Plots\n", + "
\n", + " \n", + "
\n", + " " ], "text/plain": [ - "" + "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …" ] }, "metadata": {}, @@ -319,8 +340,8 @@ } ], "source": [ - "%matplotlib notebook\n", - "#%matplotlib widget\n", + "#%matplotlib notebook\n", + "%matplotlib widget\n", "# use %matplotlib widget for execution in visual studio code\n", "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", @@ -352,19 +373,19 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -2034.393103107837\n" + "Reward = -2059.299467864888\n" ] } ], "source": [ - "controller = Controller.make('fcs_mpc', env, ph=3) # initializing the MPC Controller\n", + "controller = Controller.make('fcs_mpc', env, ph=1) # initializing the MPC Controller\n", "(state, reference), _ = env.reset()\n", "cum_rew = 0\n", "\n", From abd859c9631de1e0876f7a31d867c250a46dcdc0 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 15 Jul 2025 20:51:08 +0200 Subject: [PATCH 04/37] Refactored FCS-MPC implementation to align with environment-provided utilities and improve prediction logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced manual abc → αβ → dq voltage transformations with environment's native method: - Updated voltage vector table to use finite set directly from the environment via: - Now storing and returning subaction **indices** instead of full abc voltage vectors for better integration and comparison - Updated epsilon handling in prediction loop: - Epsilon is now incremented as - dq-transformation uses averaged value: - Replaced hardcoded max_depth with (renamed from for clarity) Previously, I was doing manual calculations: converting abc voltages to αβ and then dq using custom logic. Now all transformations use the environment's own coordinate conversion function and finite action space, making the controller more accurate and modular. Thanks again for the helpful feedback! --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 118 +++++------------- 1 file changed, 31 insertions(+), 87 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 5ab538ab..6738c935 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -42,14 +42,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", - "import math\n", "import matplotlib\n", - "\n", "import gym_electric_motor as gem\n", "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", @@ -96,33 +94,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "class FCS_MPC(Controller):\n", - " def __init__(self, environment, ph=1, ref_idx_q=0, ref_idx_d=1):\n", - " # conversion of the coordinate systems \n", - "\n", - " def clarke(abc):\n", - " \"\"\"abc → αβ transformation (3-phase to 2-phase)\"\"\"\n", - " a, b, c = abc\n", - " alpha = a - 0.5*b - 0.5*c # = a (when a + b + c = 0)\n", - " beta = (np.sqrt(3)/2)*b - (np.sqrt(3)/2)*c\n", - " return np.array([alpha, beta]) * (2/3) # Power-invariant version\n", - " \n", - " def park(alpha_beta, theta):\n", - " \"\"\"αβ → dq transformation\"\"\"\n", - " alpha, beta = alpha_beta\n", - " d = alpha * np.cos(theta) + beta * np.sin(theta)\n", - " q = -alpha * np.sin(theta) + beta * np.cos(theta)\n", - " return np.array([d, q])\n", + " def __init__(self, environment, prediction_horizon=1, ref_idx_q=0, ref_idx_d=1): \n", " \n", - " \n", - " # Create transformation functions\n", - " self._forward_transformation = lambda abc, eps: park(clarke(abc), eps)\n", - " \n", - " \n", " # indices\n", " self.ref_idx_i_q = ref_idx_q\n", " self.ref_idx_i_d = ref_idx_d\n", @@ -144,54 +122,38 @@ " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", - " self.ph_ = ph # Prediction horizon (typically 1 for FCS-MPC)\n", - " \n", - " # Define the finite set of voltage vectors (for a 2-level inverter)\n", - " # These are the 8 possible switching states (6 active + 2 zero vectors)\n", - " self.voltage_vectors = [ \n", - " (2/3, -1/3, -1/3), # V1\n", - " (1/3, 1/3, -2/3), # V2\n", - " (-1/3, 2/3, -1/3), # V3\n", - " (-2/3, 1/3, 1/3), # V4\n", - " (-1/3, -1/3, 2/3), # V5\n", - " (1/3, -2/3, 1/3), # V6 \n", - " (0, 0, 0), # V0\n", - " (0, 0, 0) # V7\n", - " ]\n", + " self.abc_to_dq = environment.get_wrapper_attr('physical_system').abc_to_dq_space\n", + " self.prediction_horizon = prediction_horizon # Prediction horizon (typically 1 for FCS-MPC)\n", " \n", - " # Scale vectors by maximum voltage\n", - " self.voltage_vectors = [\n", - " (v_a * self.limits[self.u_a_idx], \n", - " v_b * self.limits[self.u_b_idx], \n", - " v_c * self.limits[self.u_c_idx])\n", - " for v_a, v_b, v_c in self.voltage_vectors\n", - " ]\n", + " # Get the finite set of voltage vectors from the environment\n", + " self.subactions = -np.power(-1, environment.get_wrapper_attr('physical_system')._converter._subactions)\n", " \n", - " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth, max_depth):\n", + " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth):\n", " min_cost = float('inf')\n", " best_sequence = []\n", "\n", - " for v_a, v_b, v_c in self.voltage_vectors:\n", - " # Convert to dq\n", - " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", + " for idx, (v_a, v_b, v_c) in enumerate(self.subactions):\n", + " # Convert to dq \n", + " v_dq = np.transpose(np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5*omega*self.tau)]))\n", " v_d, v_q = v_dq[0], v_dq[1]\n", "\n", " # Predict next state\n", + " epsilon_next = epsilon_el + omega*self.tau\n", " i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d)\n", " i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q)\n", "\n", " # Cost for this step\n", " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", "\n", - " if depth == max_depth - 1:\n", + " if depth == self.prediction_horizon - 1:\n", " total_cost = cost\n", - " sequence = [(v_a, v_b, v_c)]\n", + " sequence = [idx]\n", " else:\n", " future_cost, future_sequence = self._simulate_sequence(\n", - " i_d_next, i_q_next, epsilon_el, omega, ref_i_d, ref_i_q, depth + 1, max_depth\n", + " i_d_next, i_q_next, epsilon_next, omega, ref_i_d, ref_i_q, depth + 1\n", " )\n", " total_cost = cost + future_cost\n", - " sequence = [(v_a, v_b, v_c)] + future_sequence\n", + " sequence = [idx] + future_sequence\n", "\n", " if total_cost < min_cost:\n", " min_cost = total_cost\n", @@ -199,9 +161,6 @@ "\n", " return min_cost, best_sequence\n", "\n", - "\n", - "\n", - "\n", " def control(self, state, reference):\n", " # Get current state values\n", " i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx]\n", @@ -215,24 +174,17 @@ "\n", " # Run recursive simulation over all ph-length sequences\n", " _, best_sequence = self._simulate_sequence(\n", - " i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0, max_depth=self.ph_\n", + " i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0\n", " )\n", "\n", " # Take the first control input in the best sequence\n", - " best_v = best_sequence[0] if best_sequence else (0, 0, 0)\n", - "\n", - " # Normalize\n", - " u_a = best_v[0] / self.limits[self.u_a_idx]\n", - " u_b = best_v[1] / self.limits[self.u_b_idx]\n", - " u_c = best_v[2] / self.limits[self.u_c_idx]\n", - "\n", - " return u_a, u_b, u_c\n", - "\n", + " best_idx = best_sequence[0] if best_sequence else 0\n", + " \n", + " return best_idx\n", "\n", " def reset(self):\n", " None\n", "\n", - "\n", "_controllers = { \n", " 'fcs_mpc': FCS_MPC\n", "}" @@ -247,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -265,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -302,32 +254,24 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 17, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:35: UserWarning: \u001b[33mWARN: A Box observation space maximum and minimum values are equal.\u001b[0m\n", - " logger.warn(\"A Box observation space maximum and minimum values are equal.\")\n" - ] - }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e6111102ae134545bacb562a47d5f829", + "model_id": "47799e64eeb84ba88aaee9f1d299e191", "version_major": 2, "version_minor": 0 }, - "image/png": "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", + "image/png": "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", "text/html": [ "\n", "
\n", "
\n", " Time Plots\n", "
\n", - " \n", + " \n", "
\n", " " ], @@ -346,7 +290,7 @@ "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", " ControlType.CurrentControl,\n", - " ActionType.Continuous)\n", + " ActionType.Finite)\n", "env = gem.make(motor.env_id(),\n", " visualization=visu,\n", " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", @@ -373,19 +317,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -2059.299467864888\n" + "Reward = -1430.5877421997113\n" ] } ], "source": [ - "controller = Controller.make('fcs_mpc', env, ph=1) # initializing the MPC Controller\n", + "controller = Controller.make('fcs_mpc', env, prediction_horizon=1) # initializing the MPC Controller\n", "(state, reference), _ = env.reset()\n", "cum_rew = 0\n", "\n", From 4f29241317975caf18ff456d8f19fc96f636d7e1 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 15 Jul 2025 22:39:06 +0200 Subject: [PATCH 05/37] added (self.u_abc_k1 = self.u_lim * self.subactions) for calculation of u_abc from subaction state --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 6738c935..cd7d86e0 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -42,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -94,7 +94,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -124,15 +124,19 @@ " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", " self.abc_to_dq = environment.get_wrapper_attr('physical_system').abc_to_dq_space\n", " self.prediction_horizon = prediction_horizon # Prediction horizon (typically 1 for FCS-MPC)\n", + "\n", + " # limit values for the normalization\n", + " self.u_lim = environment.get_wrapper_attr('physical_system').limits[env.get_wrapper_attr('state_names').index('u_sd')]\n", " \n", " # Get the finite set of voltage vectors from the environment\n", " self.subactions = -np.power(-1, environment.get_wrapper_attr('physical_system')._converter._subactions)\n", + " self.u_abc_k1 = self.u_lim * self.subactions\n", " \n", " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth):\n", " min_cost = float('inf')\n", " best_sequence = []\n", "\n", - " for idx, (v_a, v_b, v_c) in enumerate(self.subactions):\n", + " for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1):\n", " # Convert to dq \n", " v_dq = np.transpose(np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5*omega*self.tau)]))\n", " v_d, v_q = v_dq[0], v_dq[1]\n", @@ -199,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -217,7 +221,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -254,13 +258,13 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "47799e64eeb84ba88aaee9f1d299e191", + "model_id": "793190cb48db401cb57950c71587cbe5", "version_major": 2, "version_minor": 0 }, @@ -317,14 +321,14 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -1430.5877421997113\n" + "Reward = -1041.746469033069\n" ] } ], From e18a4bf1bed91d8ba99673eeb41362e8c68ea770 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 23 Jul 2025 18:45:27 +0200 Subject: [PATCH 06/37] Revised Description for FCS-MPC --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index cd7d86e0..2336ea7a 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -18,10 +18,9 @@ "\n", "![MPC1](img/mpc_scheme.png)\n", "\n", - "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", + "With the help of the system model, the output variables are predicted for each possible switching state in the finite control set. The optimizer evaluates a cost function (typically the quadratic control error) for all possible switching combinations over the prediction horizon. The switching state that minimizes the cost function is selected and applied to the system in the next time step.\n", "\n", - "![Limits](img/voltage_limits.png)\n", - "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." + "Unlike CCS-MPC, FCS-MPC does not require an iterative numerical solver or barrier functions to handle constraints, as the voltage limits are inherently respected by evaluating only the physically realizable switching states of the converter. The computational efficiency of FCS-MPC comes from the direct enumeration and evaluation of the finite number of possible switching states, rather than solving an optimization problem with constraints.\n" ] }, { @@ -33,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -42,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -94,7 +93,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -203,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -221,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -258,13 +257,13 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "793190cb48db401cb57950c71587cbe5", + "model_id": "22faa601faf14fbd99256e8bb6eca063", "version_major": 2, "version_minor": 0 }, @@ -321,14 +320,22 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 8, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:158: UserWarning: \u001b[33mWARN: The obs returned by the `step()` method is not within the observation space.\u001b[0m\n", + " logger.warn(f\"{pre} is not within the observation space.\")\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -1041.746469033069\n" + "Reward = -1148.3054379267162\n" ] } ], From 4c3d80d9920f370785d88f037b3429180048c1bc Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 30 Jul 2025 01:25:39 +0200 Subject: [PATCH 07/37] added mpc controller in gem_controllers,though testing is pending --- src/gem_controllers/__init__.py | 2 + src/gem_controllers/gem_controller.py | 25 +++-- src/gem_controllers/mpc_current_controller.py | 97 +++++++++++++++++++ 3 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 src/gem_controllers/mpc_current_controller.py diff --git a/src/gem_controllers/__init__.py b/src/gem_controllers/__init__.py index 456c91cb..128d5dbc 100644 --- a/src/gem_controllers/__init__.py +++ b/src/gem_controllers/__init__.py @@ -9,3 +9,5 @@ from .pi_speed_controller import PISpeedController from .reference_plotter import ReferencePlotter from .torque_controller import TorqueController +from .mpc_current_controller import MPCCurrentController + diff --git a/src/gem_controllers/gem_controller.py b/src/gem_controllers/gem_controller.py index 8083c5e8..04f1c3b3 100644 --- a/src/gem_controllers/gem_controller.py +++ b/src/gem_controllers/gem_controller.py @@ -4,6 +4,7 @@ import gym_electric_motor.core + class GemController: """The GemController is the base for all motor controllers in the gem-control package. @@ -66,13 +67,23 @@ def make( control_task = gc.utils.get_control_task(env_id) tuner_kwargs = dict() - # Initialize the current control stage - controller = gc.PICurrentController( - env, - env_id, - base_current_controller=base_current_controller, - decoupling=decoupling, - ) + # Initialize the current control stage + if base_current_controller == "PI": + controller = gc.PICurrentController( + env, + env_id, + base_current_controller=base_current_controller, + decoupling=decoupling, + ) + tuner_kwargs["a"] = a + tuner_kwargs["plot_references"] = plot_references + + elif base_current_controller == "MPC": + controller = gc.MPCCurrentController(env, env_id) + + else: + raise NotImplementedError(f"Unsupported base_current_controller: {base_current_controller}") + tuner_kwargs["a"] = a tuner_kwargs["plot_references"] = plot_references if control_task in ["TC", "SC"]: diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py new file mode 100644 index 00000000..09030c4a --- /dev/null +++ b/src/gem_controllers/mpc_current_controller.py @@ -0,0 +1,97 @@ +import numpy as np + +class MPCCurrentController: + def __init__(self, env, env_id, prediction_horizon=1): + self.env_id = env_id + + # Get attributes from environment + self.state_names = env.get_wrapper_attr('state_names') + self.physical_system = env.get_wrapper_attr('physical_system') + self.tau = self.physical_system.tau + self.limits = self.physical_system.limits + + motor = self.physical_system.electrical_motor.motor_parameter + self.l_q = motor['l_q'] + self.l_d = motor['l_d'] + self.psi_ = motor['psi_p'] + self.r_s = motor['r_s'] + self.p = motor['p'] + + # Indices + self.i_sd_idx = self.state_names.index('i_sd') + self.i_sq_idx = self.state_names.index('i_sq') + self.omega_idx = self.state_names.index('omega') + self.epsilon_idx = self.state_names.index('epsilon') + + self.u_sd_idx = self.state_names.index('u_sd') + self.u_lim = self.limits[self.u_sd_idx] + + self.abc_to_dq = self.physical_system.abc_to_dq_space + + # Get finite set of voltage vectors (actions) + self.subactions = -np.power(-1, self.physical_system._converter._subactions) + self.u_abc_k1 = self.u_lim * self.subactions + + # Prediction horizon + self.prediction_horizon = prediction_horizon + print("MPCCurrentController initialized") + + + def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth): + min_cost = float('inf') + best_sequence = [] + + for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): + v_dq = np.transpose( + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5 * omega * self.tau)]) + ) + v_d, v_q = v_dq[0], v_dq[1] + + epsilon_next = epsilon_el + omega * self.tau + i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d) + i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q) + + cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2 + + if depth == self.prediction_horizon - 1: + total_cost = cost + sequence = [idx] + else: + future_cost, future_sequence = self._simulate_sequence( + i_d_next, i_q_next, epsilon_next, omega, ref_i_d, ref_i_q, depth + 1 + ) + total_cost = cost + future_cost + sequence = [idx] + future_sequence + + if total_cost < min_cost: + min_cost = total_cost + best_sequence = sequence + + return min_cost, best_sequence + + def control(self, state, reference): + print("MPC control called with state:", state) + i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx] + i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx] + epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx] + omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx] + + ref_i_q = reference[0] * self.limits[self.i_sq_idx] + ref_i_d = reference[1] * self.limits[self.i_sd_idx] + + _, best_sequence = self._simulate_sequence( + i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0 + ) + + best_idx = best_sequence[0] if best_sequence else 0 + return best_idx + + def reset(self): + pass + + def tune(self, env, env_id, **kwargs): + pass + + @property + def stages(self): + return [] From a456db59b9a5d92b5fb5cfeab2a9443549580cd8 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 26 Aug 2025 23:56:36 +0200 Subject: [PATCH 08/37] 1)In gym_controller file edited such that MPC controller should not wrap around GymElectricMotorAdapter class. 2)updated the MPC controller file to process directly from environment,PMSM works. --- src/gem_controllers/gem_controller.py | 5 +- src/gem_controllers/mpc_current_controller.py | 152 +++++++++++++----- .../stages/disc_output_stage.py | 1 + 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/gem_controllers/gem_controller.py b/src/gem_controllers/gem_controller.py index 04f1c3b3..2445e5c7 100644 --- a/src/gem_controllers/gem_controller.py +++ b/src/gem_controllers/gem_controller.py @@ -99,7 +99,10 @@ def make( base_speed_controller=base_speed_controller, ) # Wrap the controller with the adapter to map the inputs and outputs to the environment - controller = gc.GymElectricMotorAdapter(env, env_id, controller) + + if base_current_controller != "MPC": + + controller = gc.GymElectricMotorAdapter(env, env_id, controller) # Fit the controllers parameters to the environment controller.tune(env, env_id, **tuner_kwargs) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 09030c4a..a74f5827 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,64 +1,129 @@ import numpy as np +from .gem_controller import GemController -class MPCCurrentController: + +class MPCCurrentController(GemController): + """MPC current controller for finite action space control""" + + @property + def signal_names(self): + """Signal names of the calculated values""" + return ["u_MPC"] + + @property + def stages(self): + """List of stages (empty for MPC as it handles everything internally)""" + return [] + + @property + def references(self): + """Reference values of the current control stage""" + return dict() + + @property + def referenced_states(self): + """Referenced states of the current control stage""" + return ['i_sd', 'i_sq'] + + @property + def maximum_reference(self): + """Maximum reference values""" + return {'i_sd': 1.0, 'i_sq': 1.0} + def __init__(self, env, env_id, prediction_horizon=1): + """ + Initialize MPC current controller + + Args: + env: GEM environment + env_id: Environment ID + prediction_horizon: MPC prediction steps + """ + super().__init__() self.env_id = env_id - + # Get attributes from environment self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau self.limits = self.physical_system.limits - motor = self.physical_system.electrical_motor.motor_parameter - self.l_q = motor['l_q'] - self.l_d = motor['l_d'] - self.psi_ = motor['psi_p'] - self.r_s = motor['r_s'] - self.p = motor['p'] + #motor_type = self.physical_system.electrical_motor.motor_type + self.motor_params = self.physical_system.electrical_motor.motor_parameter + + # Store each parameter as an attribute dynamically + for key, value in self.motor_params.items(): + setattr(self, key, value) + - # Indices + # State indices self.i_sd_idx = self.state_names.index('i_sd') - self.i_sq_idx = self.state_names.index('i_sq') + self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.epsilon_idx = self.state_names.index('epsilon') - self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] + # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space - - # Get finite set of voltage vectors (actions) + + # Finite action set self.subactions = -np.power(-1, self.physical_system._converter._subactions) - self.u_abc_k1 = self.u_lim * self.subactions - - # Prediction horizon + # All possible voltage vectors in abc coordinates + self.u_abc_k1 = self.u_lim * self.subactions + self.prediction_horizon = prediction_horizon + print("MPCCurrentController initialized") - - - def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth): + # Model constants for MPC + self._model_constants = self.physical_system.electrical_motor._model_constants + motor_type = type(self.physical_system.electrical_motor).__name__ + + if motor_type == "PermanentMagnetSynchronousMotor": + self.motor_state_names = ["i_sd", "i_sq", "epsilon"] + elif motor_type == "SynchronousReluctanceMotor": + self.motor_state_names = ["i_sd", "i_sq", "epsilon"] + elif motor_type == "ExternallyExcitedSynchronousMotor": + self.motor_state_names = ["i_sd", "i_sq", "i_e", "epsilon"] + elif motor_type == "InductionMotor": + self.motor_state_names = ["i_salpha", "i_sbeta", "psi_ralpha", "psi_rbeta", "epsilon"] + else: + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + + + + def _simulate_sequence(self, x, A, g, omega, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): + # Predict next state v_dq = np.transpose( - np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5 * omega * self.tau)]) + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - epsilon_next = epsilon_el + omega * self.tau - i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d) - i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q) + # Voltage input contribution (from motor parameters) + u_contrib = np.array([ + float(v_d) / self.l_d, + float(v_q) / self.l_q, + 0.0 + ]) + + # Compute derivative + dx = A @ x + g + u_contrib + + # Euler integration for next step + x_next = x + self.tau * dx - cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2 + cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - i_d_next, i_q_next, epsilon_next, omega, ref_i_d, ref_i_q, depth + 1 + x_next, A, g, omega, ref_i_d, ref_i_q, depth + 1 ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -70,28 +135,39 @@ def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, dept return min_cost, best_sequence def control(self, state, reference): - print("MPC control called with state:", state) - i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx] - i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx] - epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx] + """Main control method matching PI controller interface""" + + # Denormalize state and references omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx] ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sd_idx] + ref_i_d = reference[1] * self.limits[self.i_sq_idx] + + # Build denormalized state vector for the motor (only relevant states) + state_vector = [] + for name in self.motor_state_names: + s_idx = self.state_names.index(name) + state_vector.append(state[s_idx] * self.limits[s_idx]) + x = np.array(state_vector) + + # Unpack electrical jacobian + A, g, dTdx = self.physical_system.electrical_motor.electrical_jacobian( + x, # full state vector + u_in=0, # inputs (or adapt for your motor) + omega=omega + ) + # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( - i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0 + x, A, g, omega, ref_i_d, ref_i_q, depth=0 ) - best_idx = best_sequence[0] if best_sequence else 0 return best_idx - def reset(self): - pass - def tune(self, env, env_id, **kwargs): + """Required tuning method (no tuning needed for MPC)""" pass - @property - def stages(self): - return [] + def reset(self): + """Reset controller state""" + pass \ No newline at end of file diff --git a/src/gem_controllers/stages/disc_output_stage.py b/src/gem_controllers/stages/disc_output_stage.py index 9fba43d1..e8cb0bb5 100644 --- a/src/gem_controllers/stages/disc_output_stage.py +++ b/src/gem_controllers/stages/disc_output_stage.py @@ -63,6 +63,7 @@ def to_discrete(multi_discrete_action): def to_b6_discrete(multi_discrete_action): """Returns the multi discrete action for a B6 brigde converter.""" raise NotImplementedError + @staticmethod def to_multi_discrete(multi_discrete_action): From ee8eabee2851404c94a3cbe6ef65f68674f8bf7e Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Sat, 6 Sep 2025 13:29:21 +0200 Subject: [PATCH 09/37] =?UTF-8?q?=1B[200~fix:=20avoid=20double-counting=20?= =?UTF-8?q?of=20cross-coupling=20terms=20in=20MPC=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gem_controllers/mpc_current_controller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index a74f5827..412f68f5 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -156,6 +156,12 @@ def control(self, state, reference): u_in=0, # inputs (or adapt for your motor) omega=omega ) + # Local indices for motor states + self.local_idx = {name: i for i, name in enumerate(self.motor_state_names)} + + # remove duplicated cross-coupling terms from g + g[0] -= self.p * self.l_q / self.l_d * x[self.local_idx['i_sq']] + g[1] += self.p * self.l_d / self.l_q * x[self.local_idx['i_sd']] # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( From fdb0ef80237289e0c1364bba67e4ca13c99a41f3 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Fri, 12 Sep 2025 15:18:45 +0200 Subject: [PATCH 10/37] I have updated my MPC controller that takes motor dynamics from model_constants --- src/gem_controllers/mpc_current_controller.py | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 412f68f5..de7f0e75 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -59,22 +59,22 @@ def __init__(self, env, env_id, prediction_horizon=1): # State indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') - self.omega_idx = self.state_names.index('omega') - self.epsilon_idx = self.state_names.index('epsilon') + self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space - - # Finite action set + self.subactions = -np.power(-1, self.physical_system._converter._subactions) + # All possible voltage vectors in abc coordinates self.u_abc_k1 = self.u_lim * self.subactions self.prediction_horizon = prediction_horizon print("MPCCurrentController initialized") + # Model constants for MPC self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ @@ -82,49 +82,57 @@ def __init__(self, env, env_id, prediction_horizon=1): if motor_type == "PermanentMagnetSynchronousMotor": self.motor_state_names = ["i_sd", "i_sq", "epsilon"] elif motor_type == "SynchronousReluctanceMotor": - self.motor_state_names = ["i_sd", "i_sq", "epsilon"] - elif motor_type == "ExternallyExcitedSynchronousMotor": - self.motor_state_names = ["i_sd", "i_sq", "i_e", "epsilon"] - elif motor_type == "InductionMotor": - self.motor_state_names = ["i_salpha", "i_sbeta", "psi_ralpha", "psi_rbeta", "epsilon"] + self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - def _simulate_sequence(self, x, A, g, omega, ref_i_d, ref_i_q, depth): + def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): - # Predict next state + # Transform voltages to dq frame v_dq = np.transpose( np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - # Voltage input contribution (from motor parameters) - u_contrib = np.array([ - float(v_d) / self.l_d, - float(v_q) / self.l_q, - 0.0 + # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] + exterded_vector = np.array([ + omega, + x[0], # i_d + x[1], # i_q + float(v_d), # u_d + float(v_q), # u_q + omega_Isd, + omega_Isq ]) # Compute derivative - dx = A @ x + g + u_contrib - + dx = model_constants @ exterded_vector + # Euler integration for next step x_next = x + self.tau * dx - cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 + # Extract next states + i_d_next, i_q_next, epsilon_next = x_next + omega_Isd_next = omega * i_d_next + omega_Isq_next = omega * i_q_next + + # Calculate cost + cost = (i_d_next - ref_i_d) ** 2 + (i_q_next - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - x_next, A, g, omega, ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, + omega_Isd_next, omega_Isq_next, + ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -135,37 +143,28 @@ def _simulate_sequence(self, x, A, g, omega, ref_i_d, ref_i_q, depth): return min_cost, best_sequence def control(self, state, reference): - """Main control method matching PI controller interface""" - - # Denormalize state and references - omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx] - - ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sq_idx] - + """Main control method matching PI controller interface""" + # Build denormalized state vector for the motor (only relevant states) state_vector = [] for name in self.motor_state_names: s_idx = self.state_names.index(name) state_vector.append(state[s_idx] * self.limits[s_idx]) - x = np.array(state_vector) + x = np.array(state_vector) - # Unpack electrical jacobian - A, g, dTdx = self.physical_system.electrical_motor.electrical_jacobian( - x, # full state vector - u_in=0, # inputs (or adapt for your motor) - omega=omega - ) - # Local indices for motor states - self.local_idx = {name: i for i, name in enumerate(self.motor_state_names)} - - # remove duplicated cross-coupling terms from g - g[0] -= self.p * self.l_q / self.l_d * x[self.local_idx['i_sq']] - g[1] += self.p * self.l_d / self.l_q * x[self.local_idx['i_sd']] + # Denormalize state and references + omega = state[self.omega_idx] * self.limits[self.omega_idx] + ref_i_q = reference[0] * self.limits[self.i_sq_idx] + ref_i_d = reference[1] * self.limits[self.i_sq_idx] + omega_Isd = omega * x[0] # omega * i_sd + omega_Isq = omega * x[1] # omega * i_sq + # Unpack model constants + model_constants= self.physical_system.electrical_motor._model_constants + # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( - x, A, g, omega, ref_i_d, ref_i_q, depth=0 + model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 ) best_idx = best_sequence[0] if best_sequence else 0 return best_idx From 7ca1004d8ddea2c44479c041b1a7e2ad945905b0 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 24 Sep 2025 19:35:30 +0200 Subject: [PATCH 11/37] Added MPC current controller with delay compensation --- src/gem_controllers/mpc_current_controller.py | 168 ++++++++++++------ 1 file changed, 112 insertions(+), 56 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index de7f0e75..2e39a81f 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,44 +1,30 @@ import numpy as np from .gem_controller import GemController - class MPCCurrentController(GemController): - """MPC current controller for finite action space control""" + """MPC current controller with delay compensation using extrapolation""" @property def signal_names(self): - """Signal names of the calculated values""" return ["u_MPC"] @property def stages(self): - """List of stages (empty for MPC as it handles everything internally)""" return [] @property def references(self): - """Reference values of the current control stage""" return dict() @property def referenced_states(self): - """Referenced states of the current control stage""" return ['i_sd', 'i_sq'] @property def maximum_reference(self): - """Maximum reference values""" return {'i_sd': 1.0, 'i_sq': 1.0} def __init__(self, env, env_id, prediction_horizon=1): - """ - Initialize MPC current controller - - Args: - env: GEM environment - env_id: Environment ID - prediction_horizon: MPC prediction steps - """ super().__init__() self.env_id = env_id @@ -48,14 +34,12 @@ def __init__(self, env, env_id, prediction_horizon=1): self.tau = self.physical_system.tau self.limits = self.physical_system.limits - #motor_type = self.physical_system.electrical_motor.motor_type self.motor_params = self.physical_system.electrical_motor.motor_parameter # Store each parameter as an attribute dynamically for key, value in self.motor_params.items(): setattr(self, key, value) - # State indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') @@ -65,7 +49,6 @@ def __init__(self, env, env_id, prediction_horizon=1): # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space - self.subactions = -np.power(-1, self.physical_system._converter._subactions) # All possible voltage vectors in abc coordinates @@ -73,8 +56,6 @@ def __init__(self, env, env_id, prediction_horizon=1): self.prediction_horizon = prediction_horizon - print("MPCCurrentController initialized") - # Model constants for MPC self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ @@ -84,26 +65,93 @@ def __init__(self, env, env_id, prediction_horizon=1): elif motor_type == "SynchronousReluctanceMotor": self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: - raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + # === DELAY COMPENSATION VARIABLES === + self.previous_voltage_idx = 0 # Store the previously calculated voltage + self.past_references = [] # Store past references for extrapolation + self.extrapolation_order = 2 # n=2 as recommended in the text + + print("MPCCurrentController with delay compensation initialized") + + def _extrapolate_reference(self, current_ref, n=2): + """Extrapolate future reference using Lagrange extrapolation (Eq. 12.8-12.10)""" + # Store current reference + self.past_references.append(current_ref.copy()) + + # Keep only the needed history (n+1 samples) + if len(self.past_references) > n + 1: + self.past_references.pop(0) + + # If we don't have enough history, return current reference + if len(self.past_references) < n + 1: + return current_ref + + # Extract references: i*(k), i*(k-1), i*(k-2), etc. + ref_k = self.past_references[-1] # i*(k) + ref_k_minus_1 = self.past_references[-2] # i*(k-1) + ref_k_minus_2 = self.past_references[-3] # i*(k-2) + + # Extrapolate i*(k+2) + ref_k_plus_2 = 6 * ref_k - 8 * ref_k_minus_1 + 3 * ref_k_minus_2 + + return ref_k_plus_2 + + def _estimate_currents(self, model_constants, x, omega, voltage_idx): + """Estimate currents at next sampling instant (Step 3 in flowchart)""" + # Get the voltage vector that was applied in the previous cycle + v_abc = self.u_abc_k1[voltage_idx] + + # Transform voltages to dq frame at the appropriate angle + v_dq = np.transpose( + np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) + ) + v_d, v_q = v_dq[0], v_dq[1] + + # Calculate omega * i terms + omega_Isd = omega * x[0] + omega_Isq = omega * x[1] + + # Build input vector for derivative calculation + exterded_vector = np.array([ + omega, + x[0], # i_d + x[1], # i_q + float(v_d), # u_d + float(v_q), # u_q + omega_Isd, + omega_Isq + ]) + # Compute derivative using the applied voltage + dx = model_constants @ exterded_vector + + # Euler integration to estimate next state + x_estimated = x + self.tau * dx + + return x_estimated - def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): + def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i_q, depth): + """Predict future states from estimated current (Step 4 in flowchart)""" min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): # Transform voltages to dq frame v_dq = np.transpose( - np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x_estimated[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] + # Calculate omega * i terms for estimated state + omega_Isd = omega * x_estimated[0] + omega_Isq = omega * x_estimated[1] + # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] exterded_vector = np.array([ omega, - x[0], # i_d - x[1], # i_q + x_estimated[0], # i_d (estimated) + x_estimated[1], # i_q (estimated) float(v_d), # u_d float(v_q), # u_q omega_Isd, @@ -113,26 +161,19 @@ def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d # Compute derivative dx = model_constants @ exterded_vector - # Euler integration for next step - x_next = x + self.tau * dx + # Euler integration for next step (predicting k+2 from estimated k+1) + x_next = x_estimated + self.tau * dx - # Extract next states - i_d_next, i_q_next, epsilon_next = x_next - omega_Isd_next = omega * i_d_next - omega_Isq_next = omega * i_q_next - - # Calculate cost - cost = (i_d_next - ref_i_d) ** 2 + (i_q_next - ref_i_q) ** 2 + # Calculate cost using future reference (k+2) + cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - model_constants, x_next, omega, - omega_Isd_next, omega_Isq_next, - ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -143,36 +184,51 @@ def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d return min_cost, best_sequence def control(self, state, reference): - """Main control method matching PI controller interface""" - - # Build denormalized state vector for the motor (only relevant states) + """Main control method with delay compensation""" + # Step 1: Measurement (k) state_vector = [] for name in self.motor_state_names: s_idx = self.state_names.index(name) state_vector.append(state[s_idx] * self.limits[s_idx]) - x = np.array(state_vector) + x_measured = np.array(state_vector) - # Denormalize state and references + # Denormalize state omega = state[self.omega_idx] * self.limits[self.omega_idx] - ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sq_idx] - omega_Isd = omega * x[0] # omega * i_sd - omega_Isq = omega * x[1] # omega * i_sq - - # Unpack model constants - model_constants= self.physical_system.electrical_motor._model_constants - - # Pass everything needed to _simulate_sequence + + # Step 2: Apply previously calculated voltage (k) + # (This happens automatically in the environment - we just track what was applied) + + # Step 3: Estimate currents at k+1 + x_estimated = self._estimate_currents( + self._model_constants, x_measured, omega, self.previous_voltage_idx + ) + + # Step 4: Extrapolate future references + ref_i_q_current = reference[0] * self.limits[self.i_sq_idx] + ref_i_d_current = reference[1] * self.limits[self.i_sq_idx] + current_ref = np.array([ref_i_d_current, ref_i_q_current]) + + # Get references for k+2 + ref_k_plus_2 = self._extrapolate_reference(current_ref) + ref_i_d_future, ref_i_q_future = ref_k_plus_2 # Use k+2 reference for prediction + + # Step 5: Predict for k+2 and evaluate cost function _, best_sequence = self._simulate_sequence( - model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 + self._model_constants, x_estimated, omega, + ref_i_d_future, ref_i_q_future, depth=0 ) + + # Step 6: Select optimal switching state best_idx = best_sequence[0] if best_sequence else 0 + self.previous_voltage_idx = best_idx # Store for next cycle + return best_idx def tune(self, env, env_id, **kwargs): - """Required tuning method (no tuning needed for MPC)""" + """Required tuning method""" pass def reset(self): - """Reset controller state""" - pass \ No newline at end of file + """Reset controller state including delay compensation variables""" + self.previous_voltage_idx = 0 + self.past_references = [] \ No newline at end of file From 96efeb8a9c32212b54e64d51cdddb0e06f26d4aa Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Sun, 28 Sep 2025 22:48:54 +0200 Subject: [PATCH 12/37] support weighted cost function (w_d, w_q) and forward args via GemController.make - Added w_d and w_q parameters to MPCCurrentController - Updated cost function to use weighted errors - Modified GemController.make to forward extra kwargs into MPC --- src/gem_controllers/gem_controller.py | 3 +- src/gem_controllers/mpc_current_controller.py | 179 +++++++----------- 2 files changed, 67 insertions(+), 115 deletions(-) diff --git a/src/gem_controllers/gem_controller.py b/src/gem_controllers/gem_controller.py index 2445e5c7..22114bd8 100644 --- a/src/gem_controllers/gem_controller.py +++ b/src/gem_controllers/gem_controller.py @@ -40,6 +40,7 @@ def make( plot_references: bool = True, block_diagram: bool = True, save_block_diagram_as: (str, tuple) = None, + **kwargs ): """A factory function that generates (and parameterizes) a matching GemController for a given gym-electric-motor environment `env`. @@ -79,7 +80,7 @@ def make( tuner_kwargs["plot_references"] = plot_references elif base_current_controller == "MPC": - controller = gc.MPCCurrentController(env, env_id) + controller = gc.MPCCurrentController(env, env_id, **kwargs) else: raise NotImplementedError(f"Unsupported base_current_controller: {base_current_controller}") diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 2e39a81f..9c822b3e 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,30 +1,46 @@ import numpy as np from .gem_controller import GemController + class MPCCurrentController(GemController): - """MPC current controller with delay compensation using extrapolation""" + """MPC current controller for finite action space control""" @property def signal_names(self): + """Signal names of the calculated values""" return ["u_MPC"] @property def stages(self): + """List of stages (empty for MPC as it handles everything internally)""" return [] @property def references(self): + """Reference values of the current control stage""" return dict() @property def referenced_states(self): + """Referenced states of the current control stage""" return ['i_sd', 'i_sq'] @property def maximum_reference(self): + """Maximum reference values""" return {'i_sd': 1.0, 'i_sq': 1.0} - def __init__(self, env, env_id, prediction_horizon=1): + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): + """ + Initialize MPC current controller + + Args: + env: GEM environment + env_id: Environment ID + prediction_horizon: MPC prediction steps + w_d: weight for d-axis current error + w_q: weight for q-axis current error + """ super().__init__() self.env_id = env_id @@ -34,12 +50,14 @@ def __init__(self, env, env_id, prediction_horizon=1): self.tau = self.physical_system.tau self.limits = self.physical_system.limits + #motor_type = self.physical_system.electrical_motor.motor_type self.motor_params = self.physical_system.electrical_motor.motor_parameter # Store each parameter as an attribute dynamically for key, value in self.motor_params.items(): setattr(self, key, value) + # State indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') @@ -49,13 +67,20 @@ def __init__(self, env, env_id, prediction_horizon=1): # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space + self.subactions = -np.power(-1, self.physical_system._converter._subactions) # All possible voltage vectors in abc coordinates self.u_abc_k1 = self.u_lim * self.subactions self.prediction_horizon = prediction_horizon - + + # Store weights + self.w_d = w_d + self.w_q = w_q + + print(f"MPCCurrentController initialized with w_d: {self.w_d}, w_q: {self.w_q}") + # Model constants for MPC self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ @@ -65,93 +90,26 @@ def __init__(self, env, env_id, prediction_horizon=1): elif motor_type == "SynchronousReluctanceMotor": self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: - raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - # === DELAY COMPENSATION VARIABLES === - self.previous_voltage_idx = 0 # Store the previously calculated voltage - self.past_references = [] # Store past references for extrapolation - self.extrapolation_order = 2 # n=2 as recommended in the text - - print("MPCCurrentController with delay compensation initialized") - - def _extrapolate_reference(self, current_ref, n=2): - """Extrapolate future reference using Lagrange extrapolation (Eq. 12.8-12.10)""" - # Store current reference - self.past_references.append(current_ref.copy()) - - # Keep only the needed history (n+1 samples) - if len(self.past_references) > n + 1: - self.past_references.pop(0) - - # If we don't have enough history, return current reference - if len(self.past_references) < n + 1: - return current_ref - - # Extract references: i*(k), i*(k-1), i*(k-2), etc. - ref_k = self.past_references[-1] # i*(k) - ref_k_minus_1 = self.past_references[-2] # i*(k-1) - ref_k_minus_2 = self.past_references[-3] # i*(k-2) - - # Extrapolate i*(k+2) - ref_k_plus_2 = 6 * ref_k - 8 * ref_k_minus_1 + 3 * ref_k_minus_2 - - return ref_k_plus_2 - - def _estimate_currents(self, model_constants, x, omega, voltage_idx): - """Estimate currents at next sampling instant (Step 3 in flowchart)""" - # Get the voltage vector that was applied in the previous cycle - v_abc = self.u_abc_k1[voltage_idx] - - # Transform voltages to dq frame at the appropriate angle - v_dq = np.transpose( - np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) - ) - v_d, v_q = v_dq[0], v_dq[1] - - # Calculate omega * i terms - omega_Isd = omega * x[0] - omega_Isq = omega * x[1] - # Build input vector for derivative calculation - exterded_vector = np.array([ - omega, - x[0], # i_d - x[1], # i_q - float(v_d), # u_d - float(v_q), # u_q - omega_Isd, - omega_Isq - ]) - - # Compute derivative using the applied voltage - dx = model_constants @ exterded_vector - - # Euler integration to estimate next state - x_estimated = x + self.tau * dx - - return x_estimated - def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i_q, depth): - """Predict future states from estimated current (Step 4 in flowchart)""" + def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): # Transform voltages to dq frame v_dq = np.transpose( - np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x_estimated[-1] + 0.5 * omega * self.tau)]) + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - # Calculate omega * i terms for estimated state - omega_Isd = omega * x_estimated[0] - omega_Isq = omega * x_estimated[1] - # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] exterded_vector = np.array([ omega, - x_estimated[0], # i_d (estimated) - x_estimated[1], # i_q (estimated) + x[0], # i_d + x[1], # i_q float(v_d), # u_d float(v_q), # u_q omega_Isd, @@ -161,19 +119,27 @@ def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i # Compute derivative dx = model_constants @ exterded_vector - # Euler integration for next step (predicting k+2 from estimated k+1) - x_next = x_estimated + self.tau * dx + # Euler integration for next step + x_next = x + self.tau * dx + + # Extract next states + i_d_next, i_q_next, epsilon_next = x_next + omega_Isd_next = omega * i_d_next + omega_Isq_next = omega * i_q_next + + # Calculate Weighted cost + cost = self.w_d * (i_d_next - ref_i_d) ** 2 + self.w_q * (i_q_next - ref_i_q) ** 2 - # Calculate cost using future reference (k+2) - cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - model_constants, x_next, omega, ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, + omega_Isd_next, omega_Isq_next, + ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -184,51 +150,36 @@ def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i return min_cost, best_sequence def control(self, state, reference): - """Main control method with delay compensation""" - # Step 1: Measurement (k) + """Main control method matching PI controller interface""" + + # Build denormalized state vector for the motor (only relevant states) state_vector = [] for name in self.motor_state_names: s_idx = self.state_names.index(name) state_vector.append(state[s_idx] * self.limits[s_idx]) - x_measured = np.array(state_vector) + x = np.array(state_vector) - # Denormalize state + # Denormalize state and references omega = state[self.omega_idx] * self.limits[self.omega_idx] - - # Step 2: Apply previously calculated voltage (k) - # (This happens automatically in the environment - we just track what was applied) - - # Step 3: Estimate currents at k+1 - x_estimated = self._estimate_currents( - self._model_constants, x_measured, omega, self.previous_voltage_idx - ) - - # Step 4: Extrapolate future references - ref_i_q_current = reference[0] * self.limits[self.i_sq_idx] - ref_i_d_current = reference[1] * self.limits[self.i_sq_idx] - current_ref = np.array([ref_i_d_current, ref_i_q_current]) - - # Get references for k+2 - ref_k_plus_2 = self._extrapolate_reference(current_ref) - ref_i_d_future, ref_i_q_future = ref_k_plus_2 # Use k+2 reference for prediction - - # Step 5: Predict for k+2 and evaluate cost function + ref_i_q = reference[0] * self.limits[self.i_sq_idx] + ref_i_d = reference[1] * self.limits[self.i_sq_idx] + omega_Isd = omega * x[0] # omega * i_sd + omega_Isq = omega * x[1] # omega * i_sq + + # Unpack model constants + model_constants= self.physical_system.electrical_motor._model_constants + + # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( - self._model_constants, x_estimated, omega, - ref_i_d_future, ref_i_q_future, depth=0 + model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 ) - - # Step 6: Select optimal switching state best_idx = best_sequence[0] if best_sequence else 0 - self.previous_voltage_idx = best_idx # Store for next cycle - return best_idx def tune(self, env, env_id, **kwargs): - """Required tuning method""" + """Required tuning method (no tuning needed for MPC)""" pass def reset(self): - """Reset controller state including delay compensation variables""" - self.previous_voltage_idx = 0 - self.past_references = [] \ No newline at end of file + """Reset controller state""" + pass \ No newline at end of file From 047b7f16211dd279b8e6d74ebeeca12b002a3047 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 30 Sep 2025 15:32:42 +0200 Subject: [PATCH 13/37] added configurable delay compensation feature --- src/gem_controllers/mpc_current_controller.py | 214 +++++++----------- 1 file changed, 87 insertions(+), 127 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 9c822b3e..8d43d69a 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,145 +1,109 @@ import numpy as np from .gem_controller import GemController - class MPCCurrentController(GemController): - """MPC current controller for finite action space control""" - - @property - def signal_names(self): - """Signal names of the calculated values""" - return ["u_MPC"] - - @property - def stages(self): - """List of stages (empty for MPC as it handles everything internally)""" - return [] - - @property - def references(self): - """Reference values of the current control stage""" - return dict() - - @property - def referenced_states(self): - """Referenced states of the current control stage""" - return ['i_sd', 'i_sq'] - - @property - def maximum_reference(self): - """Maximum reference values""" - return {'i_sd': 1.0, 'i_sq': 1.0} - - def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0, Deadtimpstep=0): """ - Initialize MPC current controller - Args: - env: GEM environment - env_id: Environment ID - prediction_horizon: MPC prediction steps - w_d: weight for d-axis current error - w_q: weight for q-axis current error + env: Gym environment instance + env_id: Environment ID string + prediction_horizon: Prediction horizon (N) + w_d: Weight for d-axis current error + w_q: Weight for q-axis current errory + Deadtimpstep = 0 -> without delay compensation + Deadtimpstep = 1 -> with delay compensation """ super().__init__() self.env_id = env_id - - # Get attributes from environment + self.prediction_horizon = prediction_horizon + self.w_d = w_d + self.w_q = w_q + self.step = Deadtimpstep + + # environment info self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau self.limits = self.physical_system.limits - - #motor_type = self.physical_system.electrical_motor.motor_type self.motor_params = self.physical_system.electrical_motor.motor_parameter - - # Store each parameter as an attribute dynamically for key, value in self.motor_params.items(): - setattr(self, key, value) - + setattr(self, key, value) - # State indices + # state indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] - # Coordinate transformation + # coordinate transforms self.abc_to_dq = self.physical_system.abc_to_dq_space - self.subactions = -np.power(-1, self.physical_system._converter._subactions) - - # All possible voltage vectors in abc coordinates self.u_abc_k1 = self.u_lim * self.subactions - - self.prediction_horizon = prediction_horizon - # Store weights - self.w_d = w_d - self.w_q = w_q - - print(f"MPCCurrentController initialized with w_d: {self.w_d}, w_q: {self.w_q}") - - # Model constants for MPC - self._model_constants = self.physical_system.electrical_motor._model_constants + # model constants + self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ - - if motor_type == "PermanentMagnetSynchronousMotor": + if motor_type in ["PermanentMagnetSynchronousMotor", "SynchronousReluctanceMotor"]: self.motor_state_names = ["i_sd", "i_sq", "epsilon"] - elif motor_type == "SynchronousReluctanceMotor": - self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: - raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - - - - def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + + # === delay compensation variables === + self.previous_voltage_idx = 0 + self.past_references = [] + self.extrapolation_order = 2 + + # -------- Delay Compensation Helpers ---------- + def _extrapolate_reference(self, current_ref, n=2): + """Extrapolate future reference.""" + self.past_references.append(current_ref.copy()) + if len(self.past_references) > n + 1: + self.past_references.pop(0) + if len(self.past_references) < n + 1: + return current_ref + ref_k = self.past_references[-1] + ref_km1 = self.past_references[-2] + ref_km2 = self.past_references[-3] + return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 + + def _estimate_currents(self, model_constants, x, omega, voltage_idx): + """Estimate currents at k+1 using the previous optimal voltage.""" + v_abc = self.u_abc_k1[voltage_idx] + v_dq = np.transpose( + np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) + ) + v_d, v_q = v_dq[0], v_dq[1] + omega_Isd = omega * x[0] + omega_Isq = omega * x[1] + ext_vec = np.array([omega, x[0], x[1], float(v_d), float(v_q), omega_Isd, omega_Isq]) + dx = model_constants @ ext_vec + return x + self.tau * dx + + # -------- Prediction / Cost Evaluation ---------- + def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): - # Transform voltages to dq frame v_dq = np.transpose( np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - - # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] - exterded_vector = np.array([ - omega, - x[0], # i_d - x[1], # i_q - float(v_d), # u_d - float(v_q), # u_q - omega_Isd, - omega_Isq - ]) - - # Compute derivative - dx = model_constants @ exterded_vector - - # Euler integration for next step + omega_Isd = omega * x[0] + omega_Isq = omega * x[1] + ext_vec = np.array([omega, x[0], x[1], float(v_d), float(v_q), omega_Isd, omega_Isq]) + dx = model_constants @ ext_vec x_next = x + self.tau * dx - # Extract next states - i_d_next, i_q_next, epsilon_next = x_next - omega_Isd_next = omega * i_d_next - omega_Isq_next = omega * i_q_next - - # Calculate Weighted cost - cost = self.w_d * (i_d_next - ref_i_d) ** 2 + self.w_q * (i_q_next - ref_i_q) ** 2 - + cost = self.w_d * (x_next[0] - ref_i_d) ** 2 + self.w_q * (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: - total_cost = cost - sequence = [idx] + total_cost, sequence = cost, [idx] else: future_cost, future_sequence = self._simulate_sequence( - model_constants, x_next, omega, - omega_Isd_next, omega_Isq_next, - ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -149,37 +113,33 @@ def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d return min_cost, best_sequence + # -------- Control Interface ---------- def control(self, state, reference): - """Main control method matching PI controller interface""" - - # Build denormalized state vector for the motor (only relevant states) - state_vector = [] - for name in self.motor_state_names: - s_idx = self.state_names.index(name) - state_vector.append(state[s_idx] * self.limits[s_idx]) - x = np.array(state_vector) - - # Denormalize state and references - omega = state[self.omega_idx] * self.limits[self.omega_idx] + # build measured state + x_measured = np.array([state[self.state_names.index(n)] * self.limits[self.state_names.index(n)] + for n in self.motor_state_names]) + omega = state[self.omega_idx] * self.limits[self.omega_idx] ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sq_idx] - omega_Isd = omega * x[0] # omega * i_sd - omega_Isq = omega * x[1] # omega * i_sq - - # Unpack model constants - model_constants= self.physical_system.electrical_motor._model_constants - - # Pass everything needed to _simulate_sequence - _, best_sequence = self._simulate_sequence( - model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 - ) + ref_i_d = reference[1] * self.limits[self.i_sd_idx] + + if self.step == 0: + # === without delay compensation === + _, best_sequence = self._simulate_sequence( + self._model_constants, x_measured, omega, ref_i_d, ref_i_q, depth=0 + ) + else: + # === with delay compensation === + x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) + ref_k_plus_2 = self._extrapolate_reference(np.array([ref_i_d, ref_i_q])) + ref_i_d_future, ref_i_q_future = ref_k_plus_2 + _, best_sequence = self._simulate_sequence( + self._model_constants, x_est, omega, ref_i_d_future, ref_i_q_future, depth=0 + ) + best_idx = best_sequence[0] if best_sequence else 0 + self.previous_voltage_idx = best_idx return best_idx - def tune(self, env, env_id, **kwargs): - """Required tuning method (no tuning needed for MPC)""" - pass - def reset(self): - """Reset controller state""" - pass \ No newline at end of file + self.previous_voltage_idx = 0 + self.past_references = [] From 6822bb4ee31297c672eab3893681e7de9ea14d8d Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 14 Oct 2025 21:47:06 +0200 Subject: [PATCH 14/37] added DeadTimeProcessor from environment and removed Extrapolation of reference --- src/gem_controllers/mpc_current_controller.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 8d43d69a..d3935919 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -2,23 +2,27 @@ from .gem_controller import GemController class MPCCurrentController(GemController): - def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0, Deadtimpstep=0): + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): """ Args: env: Gym environment instance env_id: Environment ID string prediction_horizon: Prediction horizon (N) w_d: Weight for d-axis current error - w_q: Weight for q-axis current errory - Deadtimpstep = 0 -> without delay compensation - Deadtimpstep = 1 -> with delay compensation + w_q: Weight for q-axis current error """ super().__init__() self.env_id = env_id self.prediction_horizon = prediction_horizon self.w_d = w_d - self.w_q = w_q - self.step = Deadtimpstep + self.w_q = w_q + + + # Assign self.step from the wrapper + ps_wrapper = env.unwrapped.physical_system + self.step = getattr(ps_wrapper, 'dead_time', 0) # default to 0 if no DeadTimeProcessor + print(f"DeadTimeProcessor steps: {self.step}") + # environment info self.state_names = env.get_wrapper_attr('state_names') @@ -55,8 +59,10 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0, Deadtimp self.extrapolation_order = 2 # -------- Delay Compensation Helpers ---------- + """ + #Extrapolation of reference for delay compensation. This can be used when working the sine-wave reference for e.g. alfa-beta current control. def _extrapolate_reference(self, current_ref, n=2): - """Extrapolate future reference.""" + self.past_references.append(current_ref.copy()) if len(self.past_references) > n + 1: self.past_references.pop(0) @@ -67,6 +73,9 @@ def _extrapolate_reference(self, current_ref, n=2): ref_km2 = self.past_references[-3] return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 + """ + + #current estimation for delay compensation. def _estimate_currents(self, model_constants, x, omega, voltage_idx): """Estimate currents at k+1 using the previous optimal voltage.""" v_abc = self.u_abc_k1[voltage_idx] @@ -129,11 +138,9 @@ def control(self, state, reference): ) else: # === with delay compensation === - x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) - ref_k_plus_2 = self._extrapolate_reference(np.array([ref_i_d, ref_i_q])) - ref_i_d_future, ref_i_q_future = ref_k_plus_2 + x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) _, best_sequence = self._simulate_sequence( - self._model_constants, x_est, omega, ref_i_d_future, ref_i_q_future, depth=0 + self._model_constants, x_est, omega, ref_i_d, ref_i_q, depth=0 ) best_idx = best_sequence[0] if best_sequence else 0 From af50dc776576fa0b544bdf027f07a61e1e2496e9 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Mon, 27 Oct 2025 20:33:10 +0100 Subject: [PATCH 15/37] added comments more clearly and corrected the sequence of Id and Iq --- src/gem_controllers/mpc_current_controller.py | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index d3935919..a34d9982 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -17,14 +17,12 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): self.w_d = w_d self.w_q = w_q - - # Assign self.step from the wrapper + """Assign self.step from the environment wrapper, default to 0 if no DeadTimeProcessor""" ps_wrapper = env.unwrapped.physical_system - self.step = getattr(ps_wrapper, 'dead_time', 0) # default to 0 if no DeadTimeProcessor + self.step = getattr(ps_wrapper, 'dead_time', 0) print(f"DeadTimeProcessor steps: {self.step}") - - # environment info + """Retrieve environment info and motor parameters""" self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau @@ -33,19 +31,19 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): for key, value in self.motor_params.items(): setattr(self, key, value) - # state indices + """Identify indices of key states and inputs""" self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] - # coordinate transforms + """Setup coordinate transforms and precompute voltage combinations""" self.abc_to_dq = self.physical_system.abc_to_dq_space self.subactions = -np.power(-1, self.physical_system._converter._subactions) self.u_abc_k1 = self.u_lim * self.subactions - # model constants + """Load motor model constants and motor-specific state names""" self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ if motor_type in ["PermanentMagnetSynchronousMotor", "SynchronousReluctanceMotor"]: @@ -53,16 +51,15 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): else: raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - # === delay compensation variables === + """Initialize delay compensation variables""" self.previous_voltage_idx = 0 self.past_references = [] self.extrapolation_order = 2 # -------- Delay Compensation Helpers ---------- """ - #Extrapolation of reference for delay compensation. This can be used when working the sine-wave reference for e.g. alfa-beta current control. + # Extrapolate reference for delay compensation using past references. (works for sinusodia only for e.g. alfa-beta frames) def _extrapolate_reference(self, current_ref, n=2): - self.past_references.append(current_ref.copy()) if len(self.past_references) > n + 1: self.past_references.pop(0) @@ -72,12 +69,10 @@ def _extrapolate_reference(self, current_ref, n=2): ref_km1 = self.past_references[-2] ref_km2 = self.past_references[-3] return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 - """ - #current estimation for delay compensation. + """Estimate currents at next timestep using previous voltage for delay compensation""" def _estimate_currents(self, model_constants, x, omega, voltage_idx): - """Estimate currents at k+1 using the previous optimal voltage.""" v_abc = self.u_abc_k1[voltage_idx] v_dq = np.transpose( np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) @@ -90,6 +85,7 @@ def _estimate_currents(self, model_constants, x, omega, voltage_idx): return x + self.tau * dx # -------- Prediction / Cost Evaluation ---------- + """Simulate all possible voltage sequences to find the one minimizing the cost""" def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] @@ -105,6 +101,7 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth) dx = model_constants @ ext_vec x_next = x + self.tau * dx + """Compute cost based on tracking error""" cost = self.w_d * (x_next[0] - ref_i_d) ** 2 + self.w_q * (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: @@ -123,21 +120,21 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth) return min_cost, best_sequence # -------- Control Interface ---------- + """Compute the best voltage index based on current state and reference""" def control(self, state, reference): - # build measured state x_measured = np.array([state[self.state_names.index(n)] * self.limits[self.state_names.index(n)] for n in self.motor_state_names]) - omega = state[self.omega_idx] * self.limits[self.omega_idx] - ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sd_idx] + omega = state[self.omega_idx] * self.limits[self.omega_idx] + ref_i_d = reference[0] * self.limits[self.i_sd_idx] + ref_i_q = reference[1] * self.limits[self.i_sq_idx] if self.step == 0: - # === without delay compensation === + """Without delay compensation""" _, best_sequence = self._simulate_sequence( self._model_constants, x_measured, omega, ref_i_d, ref_i_q, depth=0 ) else: - # === with delay compensation === + """With delay compensation""" x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) _, best_sequence = self._simulate_sequence( self._model_constants, x_est, omega, ref_i_d, ref_i_q, depth=0 @@ -147,6 +144,7 @@ def control(self, state, reference): self.previous_voltage_idx = best_idx return best_idx + """Reset delay compensation state""" def reset(self): self.previous_voltage_idx = 0 self.past_references = [] From 50f730a5f08d4946ae0c347842bccfb99f0aeae2 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 10 Jun 2025 19:45:41 +0200 Subject: [PATCH 16/37] Renamed current example file to "pmsm_ccs_mpc_dq_current_control.ipynb" and created new file for finite current control --- .../pmsm_ccs_mpc_dq_current_control.ipynb | 440 ++++++++++++++++++ .../pmsm_fcs_mpc_dq_current_control.ipynb | 405 ++++++++++++++++ .../pmsm_mpc_dq_current_control.ipynb | 392 ---------------- 3 files changed, 845 insertions(+), 392 deletions(-) create mode 100644 examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb create mode 100644 examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb delete mode 100644 examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb diff --git a/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb new file mode 100644 index 00000000..c62ece30 --- /dev/null +++ b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb @@ -0,0 +1,440 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# PMSM MPC dq current control" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", + "\n", + "![MPC2](img/mpc_structure.png)\n", + "\n", + "![MPC1](img/mpc_scheme.png)\n", + "\n", + "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", + "\n", + "![Limits](img/voltage_limits.png)\n", + "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation of the required toolboxes" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33m DEPRECATION: Building 'gekko' using the legacy setup.py bdist_wheel mechanism, which will be removed in a future version. pip 25.3 will enforce this behaviour change. A possible replacement is to use the standardized build interface by setting the `--use-pep517` option, (possibly combined with `--no-build-isolation`), or adding a `pyproject.toml` file to the source tree of 'gekko'. Discussion can be found at https://github.com/pypa/pip/issues/6334\u001b[0m\u001b[33m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", + "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import math\n", + "import matplotlib\n", + "from gekko import GEKKO\n", + "\n", + "import gym_electric_motor as gem\n", + "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", + "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", + "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", + " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", + "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", + "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of a general controller class" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class Controller:\n", + "\n", + " @classmethod\n", + " def make(cls, controller_type, environment, **controller_kwargs):\n", + " assert controller_type in _controllers.keys(), f'Controller {controller_type} unknown'\n", + " controller = _controllers[controller_type](environment, **controller_kwargs)\n", + " return controller\n", + "\n", + " def control(self, state, reference):\n", + " pass\n", + "\n", + " def reset(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the MPC class" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "class MPC(Controller):\n", + " def __init__(self, environment, ph=5, ref_idx_q=0, ref_idx_d=1):\n", + " # conversion of the coordinate systems\n", + " t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", + " q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", + " self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", + " \n", + " # indices\n", + " self.ref_idx_i_q = ref_idx_q\n", + " self.ref_idx_i_d = ref_idx_d\n", + " self.i_sd_idx = environment.get_wrapper_attr('state_names').index('i_sd')\n", + " self.i_sq_idx = environment.get_wrapper_attr('state_names').index('i_sq')\n", + " self.u_a_idx = environment.get_wrapper_attr('state_names').index('u_a')\n", + " self.u_b_idx = environment.get_wrapper_attr('state_names').index('u_b')\n", + " self.u_c_idx = environment.get_wrapper_attr('state_names').index('u_c')\n", + " self.u_sq_idx = environment.get_wrapper_attr('state_names').index('u_sq')\n", + " self.u_sd_idx = environment.get_wrapper_attr('state_names').index('u_sd')\n", + " self.omega_idx = environment.get_wrapper_attr('state_names').index('omega')\n", + " self.epsilon_idx = environment.get_wrapper_attr('state_names').index('epsilon')\n", + "\n", + " # motor parameters\n", + " self.tau = environment.get_wrapper_attr('physical_system').tau\n", + " self.limits = environment.get_wrapper_attr('physical_system').limits\n", + " self.l_q = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_q']\n", + " self.l_d = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_d']\n", + " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", + " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", + " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", + " self.ph_ = ph\n", + "\n", + " def control(self, state, reference):\n", + " # initialize variables\n", + " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", + " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", + "\n", + " ref_q = []\n", + " ref_d = []\n", + " eps = []\n", + " lim_a_up = []\n", + " lim_a_low = []\n", + " \n", + " for i in range(self.ph_):\n", + " ref_q.append(reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx])\n", + " ref_d.append(reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx])\n", + " \n", + " eps.append(epsilon_el + (i-1) * self.tau * omega)\n", + " lim_a_up.append(2 * self.limits[self.u_a_idx])\n", + " lim_a_low.append(-2 * self.limits[self.u_a_idx])\n", + " \n", + " m = GEKKO(remote=False)\n", + " \n", + " # defenition of the prediction Horizon\n", + " m.time = np.linspace(self.tau, self.tau * self.ph_, self.ph_)\n", + "\n", + " # defenition of the variables\n", + " u_d = m.MV(value=state[self.u_sd_idx] * self.limits[self.u_sd_idx])\n", + " u_q = m.MV(value=state[self.u_sq_idx] * self.limits[self.u_sq_idx])\n", + " u_d.STATUS = 1\n", + " u_q.STATUS = 1\n", + "\n", + " u_a_lim_up = m.Param(value=lim_a_up)\n", + " u_a_lim_low = m.Param(value=lim_a_low)\n", + " sq3 = math.sqrt(3)\n", + "\n", + " i_d = m.SV(value=state[self.i_sd_idx] * self.limits[self.i_sd_idx], lb=-self.limits[self.i_sd_idx], ub=self.limits[self.i_sd_idx] )\n", + " i_q = m.SV(value=state[self.i_sq_idx] * self.limits[self.i_sq_idx], lb=-self.limits[self.i_sq_idx], ub=self.limits[self.i_sq_idx])\n", + "\n", + " epsilon = m.Param(value=eps)\n", + " \n", + " # reference trajectory\n", + " traj_d = m.Param(value=ref_d)\n", + " traj_q = m.Param(value=ref_q)\n", + " \n", + " # defenition of the constants\n", + " omega = m.Const(value=omega)\n", + " psi = m.Const(value=self.psi_)\n", + " rs = m.Const(value=self.r_s)\n", + " ld = m.Const(value=self.l_d)\n", + " lq = m.Const(value=self.l_q)\n", + " \n", + " # control error\n", + " e_d = m.CV()\n", + " e_q = m.CV()\n", + " e_d.STATUS = 1\n", + " e_q.STATUS = 1\n", + " \n", + " # solver options\n", + " m.options.CV_TYPE = 2\n", + " m.options.IMODE = 6\n", + " m.options.solver = 3\n", + " m.options.WEB = 0\n", + " m.options.NODES = 2\n", + " \n", + " # differential equations\n", + " m.Equations([ld * i_d.dt() == u_d - rs * i_d + omega * lq * i_q,\n", + " lq * i_q.dt() == u_q - rs * i_q - omega * ld * i_d - omega * psi])\n", + " \n", + " # cost function\n", + " m.Equations([e_d == (i_d - traj_d), e_q == (i_q - traj_q)])\n", + " \n", + " # voltage limitations\n", + " m.Equation(u_a_lim_up >= 3/2 * m.cos(epsilon) * u_d - 3/2 * m.sin(epsilon) * u_q - sq3/2 * m.sin(epsilon) * u_d - sq3/2 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_low <= 3 / 2 * m.cos(epsilon) * u_d - 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_up >= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_low <= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_up >= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", + " m.Equation(u_a_lim_low <= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", + " \n", + " # object to minimize\n", + " m.Obj(e_d)\n", + " m.Obj(e_q)\n", + " \n", + " # solving optimization problem\n", + " m.solve(disp=False)\n", + " \n", + " # additional voltage limitation\n", + " u_a, u_b, u_c = self._backward_transformation((u_q.NEWVAL, u_d.NEWVAL), epsilon_el)\n", + " u_max = max(np.absolute(u_a - u_b), np.absolute(u_b - u_c), np.absolute(u_c - u_a))\n", + " if u_max >= 2 * self.limits[self.u_a_idx]:\n", + " u_a = u_a / u_max * 2 * self.limits[self.u_a_idx]\n", + " u_b = u_b / u_max * 2 * self.limits[self.u_a_idx]\n", + " u_c = u_c / u_max * 2 * self.limits[self.u_a_idx]\n", + " \n", + " # Zero Point Shift\n", + " u_0 = 0.5 * (max(u_a, u_b, u_c) + min(u_a, u_b, u_c))\n", + " u_a -= u_0\n", + " u_b -= u_0\n", + " u_c -= u_0\n", + " \n", + " # normalization of the manipulated variables\n", + " u_a /= self.limits[self.u_a_idx]\n", + " u_b /= self.limits[self.u_b_idx]\n", + " u_c /= self.limits[self.u_c_idx]\n", + " \n", + " return u_a, u_b, u_c\n", + "\n", + " def reset(self):\n", + " None\n", + "\n", + "\n", + "_controllers = {\n", + " 'mpc': MPC\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the setting parameters of the motor and the limit values " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "motor_parameter = dict(r_s=15e-3, l_d=0.37e-3, l_q=1.2e-3, psi_p=65.6e-3, p=3, j_rotor=0.06)\n", + "limit_values = dict(i=160 * 1.41, omega=12000 * np.pi / 30, u=450)\n", + "nominal_values = {key: 0.7 * limit for key, limit in limit_values.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of reference generators for the d- and q- components of the current" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "q_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "d_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "reference_generator = MultipleReferenceGenerator([q_generator, d_generator])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, the PMSM is initialized with the previously defined motor parameters and reference generators. The MPC controller is initialized with the specified prediction horizon. Then the motor is simulated step by step. For this purpose the function for visualization is called. The manipulated variables are calculated by the MPC Controller and switched to the PMSM. This takes a long time, because the numerical effort of the MPC is large. Finally, the reward is output.\n", + "\n", + "![Graph](img/mpc_currents_voltages.png)\n", + "The graphs show exemplary the currents and voltages for the control of a reference profile." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib notebook\n", + "#%matplotlib widget\n", + "# use %matplotlib widget for execution in visual studio code\n", + "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", + "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", + " ControlType.CurrentControl,\n", + " ActionType.Continuous)\n", + "env = gem.make(motor.env_id(),\n", + " visualization=visu,\n", + " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", + " ode_solver=ScipySolveIvpSolver(), # ODE Solver of the simulation\n", + " reference_generator=reference_generator, # initialize the reference generators\n", + " \n", + " # defenitions for the reward function\n", + " reward_function=dict(\n", + " reward_weights={'i_sq': 1, 'i_sd': 1},\n", + " reward_power=0.5,\n", + " ),\n", + " \n", + " # definitions for the setting parameters\n", + " supply=dict(u_nominal=400),\n", + " motor=dict(\n", + " motor_parameter=motor_parameter,\n", + " limit_values=limit_values,\n", + " nominal_values=nominal_values\n", + " )\n", + " )\n", + "\n", + "visu.initialize()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:158: UserWarning: \u001b[33mWARN: The obs returned by the `step()` method is not within the observation space.\u001b[0m\n", + " logger.warn(f\"{pre} is not within the observation space.\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reward = -550.5100758083643\n" + ] + } + ], + "source": [ + "controller = Controller.make('mpc', env, ph=3) # initializing the MPC Controller\n", + "(state, reference), _ = env.reset()\n", + "cum_rew = 0\n", + "\n", + "for i in range(10000): # simulate the PMSM for 10000 steps (1 second)\n", + " env.render() # function for visualization\n", + " action = controller.control(state, reference) # calculation of the manipulated variables\n", + " (state, reference), reward, terminated, truncated, _ = env.step(action) # simulate one step of the PMSM\n", + " cum_rew += reward # adding up the Reward \n", + " if terminated:\n", + " (state, reference), _ = env.reset()\n", + " controller.reset()\n", + "print('Reward =', cum_rew)\n", + "env.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PE", + "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.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb new file mode 100644 index 00000000..3855a17f --- /dev/null +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -0,0 +1,405 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# PMSM MPC dq current control" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", + "\n", + "![MPC2](img/mpc_structure.png)\n", + "\n", + "![MPC1](img/mpc_scheme.png)\n", + "\n", + "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", + "\n", + "![Limits](img/voltage_limits.png)\n", + "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation of the required toolboxes" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", + "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import math\n", + "import matplotlib\n", + "from gekko import GEKKO\n", + "\n", + "import gym_electric_motor as gem\n", + "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", + "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", + "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", + " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", + "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", + "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of a general controller class" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "class Controller:\n", + "\n", + " @classmethod\n", + " def make(cls, controller_type, environment, **controller_kwargs):\n", + " assert controller_type in _controllers.keys(), f'Controller {controller_type} unknown'\n", + " controller = _controllers[controller_type](environment, **controller_kwargs)\n", + " return controller\n", + "\n", + " def control(self, state, reference):\n", + " pass\n", + "\n", + " def reset(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the MPC class" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "class FCS_MPC(Controller):\n", + " def __init__(self, environment, ph=1, ref_idx_q=0, ref_idx_d=1):\n", + " # conversion of the coordinate systems\n", + " #t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", + " #q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", + " #self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", + " # Manual transformation implementations\n", + "\n", + " def clarke(abc):\n", + " \"\"\"abc → αβ transformation (3-phase to 2-phase)\"\"\"\n", + " a, b, c = abc\n", + " alpha = a - 0.5*b - 0.5*c # = a (when a + b + c = 0)\n", + " beta = (np.sqrt(3)/2)*b - (np.sqrt(3)/2)*c\n", + " return np.array([alpha, beta]) * (2/3) # Power-invariant version\n", + " \n", + " def park(alpha_beta, theta):\n", + " \"\"\"αβ → dq transformation\"\"\"\n", + " alpha, beta = alpha_beta\n", + " d = alpha * np.cos(theta) + beta * np.sin(theta)\n", + " q = -alpha * np.sin(theta) + beta * np.cos(theta)\n", + " return np.array([d, q])\n", + " \n", + " \n", + " # Create transformation functions\n", + " self._forward_transformation = lambda abc, eps: park(clarke(abc), eps)\n", + " \n", + " \n", + " # indices\n", + " self.ref_idx_i_q = ref_idx_q\n", + " self.ref_idx_i_d = ref_idx_d\n", + " self.i_sd_idx = environment.get_wrapper_attr('state_names').index('i_sd')\n", + " self.i_sq_idx = environment.get_wrapper_attr('state_names').index('i_sq')\n", + " self.u_a_idx = environment.get_wrapper_attr('state_names').index('u_a')\n", + " self.u_b_idx = environment.get_wrapper_attr('state_names').index('u_b')\n", + " self.u_c_idx = environment.get_wrapper_attr('state_names').index('u_c')\n", + " self.u_sq_idx = environment.get_wrapper_attr('state_names').index('u_sq')\n", + " self.u_sd_idx = environment.get_wrapper_attr('state_names').index('u_sd')\n", + " self.omega_idx = environment.get_wrapper_attr('state_names').index('omega')\n", + " self.epsilon_idx = environment.get_wrapper_attr('state_names').index('epsilon')\n", + "\n", + " # motor parameters\n", + " self.tau = environment.get_wrapper_attr('physical_system').tau\n", + " self.limits = environment.get_wrapper_attr('physical_system').limits\n", + " self.l_q = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_q']\n", + " self.l_d = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_d']\n", + " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", + " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", + " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", + " self.ph_ = ph # Prediction horizon (typically 1 for FCS-MPC)\n", + " \n", + " # Define the finite set of voltage vectors (for a 2-level inverter)\n", + " # These are the 8 possible switching states (6 active + 2 zero vectors)\n", + " self.voltage_vectors = [ \n", + " (2/3, -1/3, -1/3), # V1\n", + " (1/3, 1/3, -2/3), # V2\n", + " (-1/3, 2/3, -1/3), # V3\n", + " (-2/3, 1/3, 1/3), # V4\n", + " (-1/3, -1/3, 2/3), # V5\n", + " (1/3, -2/3, 1/3), # V6 \n", + " (0, 0, 0), # V0\n", + " (1/3, 1/3, 1/3) # V7\n", + " ]\n", + " \n", + " # Scale vectors by maximum voltage\n", + " self.voltage_vectors = [\n", + " (v_a * self.limits[self.u_a_idx], \n", + " v_b * self.limits[self.u_b_idx], \n", + " v_c * self.limits[self.u_c_idx])\n", + " for v_a, v_b, v_c in self.voltage_vectors\n", + " ]\n", + "\n", + " def control(self, state, reference):\n", + " # Get current state values\n", + " i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx]\n", + " i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx]\n", + " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", + " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", + " \n", + " # Reference values\n", + " ref_i_q = reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx]\n", + " ref_i_d = reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx]\n", + " \n", + " # Initialize variables for tracking the best vector\n", + " min_cost = float('inf')\n", + " best_vector = (0, 0, 0)\n", + " \n", + " # Evaluate each possible voltage vector\n", + " for v_a, v_b, v_c in self.voltage_vectors:\n", + " # Convert to dq coordinates\n", + " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", + " v_d, v_q = v_dq[0], v_dq[1] # Now properly ordered\n", + " \n", + " # Predict next state using motor model (Euler approximation)\n", + " # d-axis current prediction\n", + " i_d_next = i_d + self.tau * (\n", + " (v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d\n", + " )\n", + " \n", + " # q-axis current prediction\n", + " i_q_next = i_q + self.tau * (\n", + " (v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q\n", + " )\n", + " \n", + " # Calculate cost function (typically squared error)\n", + " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", + " \n", + " # Track the vector with minimum cost\n", + " if cost < min_cost:\n", + " min_cost = cost\n", + " best_vector = (v_a, v_b, v_c)\n", + " \n", + " # Normalize the output to [-1, 1] range\n", + " u_a = best_vector[0] / self.limits[self.u_a_idx]\n", + " u_b = best_vector[1] / self.limits[self.u_b_idx]\n", + " u_c = best_vector[2] / self.limits[self.u_c_idx]\n", + " \n", + " return u_a, u_b, u_c\n", + "\n", + " def reset(self):\n", + " None\n", + "\n", + "\n", + "_controllers = { \n", + " 'fcs_mpc': FCS_MPC\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of the setting parameters of the motor and the limit values " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "motor_parameter = dict(r_s=15e-3, l_d=0.37e-3, l_q=1.2e-3, psi_p=65.6e-3, p=3, j_rotor=0.06)\n", + "limit_values = dict(i=160 * 1.41, omega=12000 * np.pi / 30, u=450)\n", + "nominal_values = {key: 0.7 * limit for key, limit in limit_values.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Definition of reference generators for the d- and q- components of the current" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "q_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "d_generator = SwitchedReferenceGenerator(\n", + " sub_generators=[\n", + " SinusoidalReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", + " StepReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.5)),\n", + " TriangularReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", + " ],\n", + " super_episode_length=(500, 1000)\n", + " )\n", + "\n", + "reference_generator = MultipleReferenceGenerator([q_generator, d_generator])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, the PMSM is initialized with the previously defined motor parameters and reference generators. The MPC controller is initialized with the specified prediction horizon. Then the motor is simulated step by step. For this purpose the function for visualization is called. The manipulated variables are calculated by the MPC Controller and switched to the PMSM. This takes a long time, because the numerical effort of the MPC is large. Finally, the reward is output.\n", + "\n", + "![Graph](img/mpc_currents_voltages.png)\n", + "The graphs show exemplary the currents and voltages for the control of a reference profile." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib notebook\n", + "#%matplotlib widget\n", + "# use %matplotlib widget for execution in visual studio code\n", + "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", + "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", + " ControlType.CurrentControl,\n", + " ActionType.Continuous)\n", + "env = gem.make(motor.env_id(),\n", + " visualization=visu,\n", + " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", + " ode_solver=ScipySolveIvpSolver(), # ODE Solver of the simulation\n", + " reference_generator=reference_generator, # initialize the reference generators\n", + " \n", + " # defenitions for the reward function\n", + " reward_function=dict(\n", + " reward_weights={'i_sq': 1, 'i_sd': 1},\n", + " reward_power=0.5,\n", + " ),\n", + " \n", + " # definitions for the setting parameters\n", + " supply=dict(u_nominal=400),\n", + " motor=dict(\n", + " motor_parameter=motor_parameter,\n", + " limit_values=limit_values,\n", + " nominal_values=nominal_values\n", + " )\n", + " )\n", + "\n", + "visu.initialize()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reward = -2034.393103107837\n" + ] + } + ], + "source": [ + "controller = Controller.make('fcs_mpc', env, ph=3) # initializing the MPC Controller\n", + "(state, reference), _ = env.reset()\n", + "cum_rew = 0\n", + "\n", + "for i in range(10000): # simulate the PMSM for 10000 steps (1 second)\n", + " env.render() # function for visualization\n", + " action = controller.control(state, reference) # calculation of the manipulated variables\n", + " (state, reference), reward, terminated, truncated, _ = env.step(action) # simulate one step of the PMSM\n", + " cum_rew += reward # adding up the Reward \n", + " if terminated:\n", + " (state, reference), _ = env.reset()\n", + " controller.reset()\n", + "print('Reward =', cum_rew)\n", + "env.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PE", + "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.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb deleted file mode 100644 index a358d52a..00000000 --- a/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb +++ /dev/null @@ -1,392 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# PMSM MPC dq current control" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", - "\n", - "![MPC2](img/mpc_structure.png)\n", - "\n", - "![MPC1](img/mpc_scheme.png)\n", - "\n", - "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", - "\n", - "![Limits](img/voltage_limits.png)\n", - "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Installation of the required toolboxes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", - "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import math\n", - "import matplotlib\n", - "from gekko import GEKKO\n", - "\n", - "import gym_electric_motor as gem\n", - "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", - "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", - "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", - " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", - "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", - "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of a general controller class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class Controller:\n", - "\n", - " @classmethod\n", - " def make(cls, controller_type, environment, **controller_kwargs):\n", - " assert controller_type in _controllers.keys(), f'Controller {controller_type} unknown'\n", - " controller = _controllers[controller_type](environment, **controller_kwargs)\n", - " return controller\n", - "\n", - " def control(self, state, reference):\n", - " pass\n", - "\n", - " def reset(self):\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of the MPC class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class MPC(Controller):\n", - " def __init__(self, environment, ph=5, ref_idx_q=0, ref_idx_d=1):\n", - " # conversion of the coordinate systems\n", - " t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", - " q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", - " self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", - " \n", - " # indices\n", - " self.ref_idx_i_q = ref_idx_q\n", - " self.ref_idx_i_d = ref_idx_d\n", - " self.i_sd_idx = environment.get_wrapper_attr('state_names').index('i_sd')\n", - " self.i_sq_idx = environment.get_wrapper_attr('state_names').index('i_sq')\n", - " self.u_a_idx = environment.get_wrapper_attr('state_names').index('u_a')\n", - " self.u_b_idx = environment.get_wrapper_attr('state_names').index('u_b')\n", - " self.u_c_idx = environment.get_wrapper_attr('state_names').index('u_c')\n", - " self.u_sq_idx = environment.get_wrapper_attr('state_names').index('u_sq')\n", - " self.u_sd_idx = environment.get_wrapper_attr('state_names').index('u_sd')\n", - " self.omega_idx = environment.get_wrapper_attr('state_names').index('omega')\n", - " self.epsilon_idx = environment.get_wrapper_attr('state_names').index('epsilon')\n", - "\n", - " # motor parameters\n", - " self.tau = environment.get_wrapper_attr('physical_system').tau\n", - " self.limits = environment.get_wrapper_attr('physical_system').limits\n", - " self.l_q = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_q']\n", - " self.l_d = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['l_d']\n", - " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", - " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", - " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", - " self.ph_ = ph\n", - "\n", - " def control(self, state, reference):\n", - " # initialize variables\n", - " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", - " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", - "\n", - " ref_q = []\n", - " ref_d = []\n", - " eps = []\n", - " lim_a_up = []\n", - " lim_a_low = []\n", - " \n", - " for i in range(self.ph_):\n", - " ref_q.append(reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx])\n", - " ref_d.append(reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx])\n", - " \n", - " eps.append(epsilon_el + (i-1) * self.tau * omega)\n", - " lim_a_up.append(2 * self.limits[self.u_a_idx])\n", - " lim_a_low.append(-2 * self.limits[self.u_a_idx])\n", - " \n", - " m = GEKKO(remote=False)\n", - " \n", - " # defenition of the prediction Horizon\n", - " m.time = np.linspace(self.tau, self.tau * self.ph_, self.ph_)\n", - "\n", - " # defenition of the variables\n", - " u_d = m.MV(value=state[self.u_sd_idx] * self.limits[self.u_sd_idx])\n", - " u_q = m.MV(value=state[self.u_sq_idx] * self.limits[self.u_sq_idx])\n", - " u_d.STATUS = 1\n", - " u_q.STATUS = 1\n", - "\n", - " u_a_lim_up = m.Param(value=lim_a_up)\n", - " u_a_lim_low = m.Param(value=lim_a_low)\n", - " sq3 = math.sqrt(3)\n", - "\n", - " i_d = m.SV(value=state[self.i_sd_idx] * self.limits[self.i_sd_idx], lb=-self.limits[self.i_sd_idx], ub=self.limits[self.i_sd_idx] )\n", - " i_q = m.SV(value=state[self.i_sq_idx] * self.limits[self.i_sq_idx], lb=-self.limits[self.i_sq_idx], ub=self.limits[self.i_sq_idx])\n", - "\n", - " epsilon = m.Param(value=eps)\n", - " \n", - " # reference trajectory\n", - " traj_d = m.Param(value=ref_d)\n", - " traj_q = m.Param(value=ref_q)\n", - " \n", - " # defenition of the constants\n", - " omega = m.Const(value=omega)\n", - " psi = m.Const(value=self.psi_)\n", - " rs = m.Const(value=self.r_s)\n", - " ld = m.Const(value=self.l_d)\n", - " lq = m.Const(value=self.l_q)\n", - " \n", - " # control error\n", - " e_d = m.CV()\n", - " e_q = m.CV()\n", - " e_d.STATUS = 1\n", - " e_q.STATUS = 1\n", - " \n", - " # solver options\n", - " m.options.CV_TYPE = 2\n", - " m.options.IMODE = 6\n", - " m.options.solver = 3\n", - " m.options.WEB = 0\n", - " m.options.NODES = 2\n", - " \n", - " # differential equations\n", - " m.Equations([ld * i_d.dt() == u_d - rs * i_d + omega * lq * i_q,\n", - " lq * i_q.dt() == u_q - rs * i_q - omega * ld * i_d - omega * psi])\n", - " \n", - " # cost function\n", - " m.Equations([e_d == (i_d - traj_d), e_q == (i_q - traj_q)])\n", - " \n", - " # voltage limitations\n", - " m.Equation(u_a_lim_up >= 3/2 * m.cos(epsilon) * u_d - 3/2 * m.sin(epsilon) * u_q - sq3/2 * m.sin(epsilon) * u_d - sq3/2 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_low <= 3 / 2 * m.cos(epsilon) * u_d - 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_up >= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_low <= sq3 * m.sin(epsilon) * u_d + sq3 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_up >= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", - " m.Equation(u_a_lim_low <= -3 / 2 * m.cos(epsilon) * u_d + 3 / 2 * m.sin(epsilon) * u_q - sq3 / 2 * m.sin(epsilon) * u_d - sq3 / 2 * m.cos(epsilon) * u_q)\n", - " \n", - " # object to minimize\n", - " m.Obj(e_d)\n", - " m.Obj(e_q)\n", - " \n", - " # solving optimization problem\n", - " m.solve(disp=False)\n", - " \n", - " # additional voltage limitation\n", - " u_a, u_b, u_c = self._backward_transformation((u_q.NEWVAL, u_d.NEWVAL), epsilon_el)\n", - " u_max = max(np.absolute(u_a - u_b), np.absolute(u_b - u_c), np.absolute(u_c - u_a))\n", - " if u_max >= 2 * self.limits[self.u_a_idx]:\n", - " u_a = u_a / u_max * 2 * self.limits[self.u_a_idx]\n", - " u_b = u_b / u_max * 2 * self.limits[self.u_a_idx]\n", - " u_c = u_c / u_max * 2 * self.limits[self.u_a_idx]\n", - " \n", - " # Zero Point Shift\n", - " u_0 = 0.5 * (max(u_a, u_b, u_c) + min(u_a, u_b, u_c))\n", - " u_a -= u_0\n", - " u_b -= u_0\n", - " u_c -= u_0\n", - " \n", - " # normalization of the manipulated variables\n", - " u_a /= self.limits[self.u_a_idx]\n", - " u_b /= self.limits[self.u_b_idx]\n", - " u_c /= self.limits[self.u_c_idx]\n", - " \n", - " return u_a, u_b, u_c\n", - "\n", - " def reset(self):\n", - " None\n", - "\n", - "\n", - "_controllers = {\n", - " 'mpc': MPC\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of the setting parameters of the motor and the limit values " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "motor_parameter = dict(r_s=15e-3, l_d=0.37e-3, l_q=1.2e-3, psi_p=65.6e-3, p=3, j_rotor=0.06)\n", - "limit_values = dict(i=160 * 1.41, omega=12000 * np.pi / 30, u=450)\n", - "nominal_values = {key: 0.7 * limit for key, limit in limit_values.items()}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Definition of reference generators for the d- and q- components of the current" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "q_generator = SwitchedReferenceGenerator(\n", - " sub_generators=[\n", - " SinusoidalReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", - " StepReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.5)),\n", - " TriangularReferenceGenerator(reference_state='i_sq', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", - " ],\n", - " super_episode_length=(500, 1000)\n", - " )\n", - "\n", - "d_generator = SwitchedReferenceGenerator(\n", - " sub_generators=[\n", - " SinusoidalReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2)),\n", - " StepReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.5)),\n", - " TriangularReferenceGenerator(reference_state='i_sd', amplitude_range=(0, 0.3), offset_range=(0, 0.2))\n", - " ],\n", - " super_episode_length=(500, 1000)\n", - " )\n", - "\n", - "reference_generator = MultipleReferenceGenerator([q_generator, d_generator])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, the PMSM is initialized with the previously defined motor parameters and reference generators. The MPC controller is initialized with the specified prediction horizon. Then the motor is simulated step by step. For this purpose the function for visualization is called. The manipulated variables are calculated by the MPC Controller and switched to the PMSM. This takes a long time, because the numerical effort of the MPC is large. Finally, the reward is output.\n", - "\n", - "![Graph](img/mpc_currents_voltages.png)\n", - "The graphs show exemplary the currents and voltages for the control of a reference profile." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib notebook\n", - "#%matplotlib widget\n", - "# use %matplotlib widget for execution in visual studio code\n", - "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", - "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", - " ControlType.CurrentControl,\n", - " ActionType.Continuous)\n", - "env = gem.make(motor.env_id(),\n", - " visualization=visu,\n", - " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", - " ode_solver=ScipySolveIvpSolver(), # ODE Solver of the simulation\n", - " reference_generator=reference_generator, # initialize the reference generators\n", - " \n", - " # defenitions for the reward function\n", - " reward_function=dict(\n", - " reward_weights={'i_sq': 1, 'i_sd': 1},\n", - " reward_power=0.5,\n", - " ),\n", - " \n", - " # definitions for the setting parameters\n", - " supply=dict(u_nominal=400),\n", - " motor=dict(\n", - " motor_parameter=motor_parameter,\n", - " limit_values=limit_values,\n", - " nominal_values=nominal_values\n", - " )\n", - " )\n", - "\n", - "visu.initialize()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "controller = Controller.make('mpc', env, ph=3) # initializing the MPC Controller\n", - "(state, reference), _ = env.reset()\n", - "cum_rew = 0\n", - "\n", - "for i in range(10000): # simulate the PMSM for 10000 steps (1 second)\n", - " env.render() # function for visualization\n", - " action = controller.control(state, reference) # calculation of the manipulated variables\n", - " (state, reference), reward, terminated, truncated, _ = env.step(action) # simulate one step of the PMSM\n", - " cum_rew += reward # adding up the Reward \n", - " if terminated:\n", - " (state, reference), _ = env.reset()\n", - " controller.reset()\n", - "print('Reward =', cum_rew)\n", - "env.close()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "GEM2v0", - "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.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From 5e08efbae3199c0fbf5c7b3d1deda6ede8da57e6 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 11 Jun 2025 09:52:37 +0200 Subject: [PATCH 17/37] renamed existing ccs mpc and added new file for fcs(finte control set) mpc. --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 3855a17f..00b438f8 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -161,7 +161,7 @@ " (-1/3, -1/3, 2/3), # V5\n", " (1/3, -2/3, 1/3), # V6 \n", " (0, 0, 0), # V0\n", - " (1/3, 1/3, 1/3) # V7\n", + " (0, 0, 0) # V7\n", " ]\n", " \n", " # Scale vectors by maximum voltage\n", From 720f08bf0c95a87d2388110c5990c3266df74ea6 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 24 Jun 2025 23:17:29 +0200 Subject: [PATCH 18/37] Upadted the title of pmsm_ccs_mpc_dq_current_control.ipynb and implemented comments on pmsm_fcs_mpc_dq_current_control.ipynb file --- .../pmsm_ccs_mpc_dq_current_control.ipynb | 2 +- .../pmsm_fcs_mpc_dq_current_control.ipynb | 153 ++++++++++-------- 2 files changed, 88 insertions(+), 67 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb index c62ece30..88336519 100644 --- a/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_ccs_mpc_dq_current_control.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# PMSM MPC dq current control" + "# PMSM CCS-MPC dq current control" ] }, { diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 00b438f8..5ab538ab 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -4,14 +4,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# PMSM MPC dq current control" + "# PMSM FCS-MPC dq current control" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutorial we apply a continuous-control set (CCS) model predictive control (MPC) approach to realize the current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates. We achieve this by utilizing the GEKKO toolbox [https://gekko.readthedocs.io/en/latest/].\n", + "In this tutorial, we apply a finite-control set (FCS) model predictive control (MPC) approach to realize current control of a permanent magnet synchronous motor (PMSM) in rotor field-oriented coordinates.\n", + "Unlike continuous-control set (CCS) MPC, FCS-MPC directly evaluates a finite set of switching states to optimize the control input based on a cost function over a prediction horizon.\n", "\n", "![MPC2](img/mpc_structure.png)\n", "\n", @@ -32,24 +33,22 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ - "!pip install -q git+https://github.com/BYU-PRISM/GEKKO.git\n", "!pip install -q git+https://github.com/upb-lea/gym-electric-motor" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import math\n", "import matplotlib\n", - "from gekko import GEKKO\n", "\n", "import gym_electric_motor as gem\n", "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", @@ -69,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -103,11 +102,7 @@ "source": [ "class FCS_MPC(Controller):\n", " def __init__(self, environment, ph=1, ref_idx_q=0, ref_idx_d=1):\n", - " # conversion of the coordinate systems\n", - " #t32 = environment.get_wrapper_attr('physical_system').electrical_motor.t_32\n", - " #q = environment.get_wrapper_attr('physical_system').electrical_motor.q\n", - " #self._backward_transformation = (lambda quantities, eps: t32(q(quantities[::-1], eps)))\n", - " # Manual transformation implementations\n", + " # conversion of the coordinate systems \n", "\n", " def clarke(abc):\n", " \"\"\"abc → αβ transformation (3-phase to 2-phase)\"\"\"\n", @@ -171,6 +166,41 @@ " v_c * self.limits[self.u_c_idx])\n", " for v_a, v_b, v_c in self.voltage_vectors\n", " ]\n", + " \n", + " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth, max_depth):\n", + " min_cost = float('inf')\n", + " best_sequence = []\n", + "\n", + " for v_a, v_b, v_c in self.voltage_vectors:\n", + " # Convert to dq\n", + " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", + " v_d, v_q = v_dq[0], v_dq[1]\n", + "\n", + " # Predict next state\n", + " i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d)\n", + " i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q)\n", + "\n", + " # Cost for this step\n", + " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", + "\n", + " if depth == max_depth - 1:\n", + " total_cost = cost\n", + " sequence = [(v_a, v_b, v_c)]\n", + " else:\n", + " future_cost, future_sequence = self._simulate_sequence(\n", + " i_d_next, i_q_next, epsilon_el, omega, ref_i_d, ref_i_q, depth + 1, max_depth\n", + " )\n", + " total_cost = cost + future_cost\n", + " sequence = [(v_a, v_b, v_c)] + future_sequence\n", + "\n", + " if total_cost < min_cost:\n", + " min_cost = total_cost\n", + " best_sequence = sequence\n", + "\n", + " return min_cost, best_sequence\n", + "\n", + "\n", + "\n", "\n", " def control(self, state, reference):\n", " # Get current state values\n", @@ -178,47 +208,27 @@ " i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx]\n", " epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx]\n", " omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx]\n", - " \n", + "\n", " # Reference values\n", " ref_i_q = reference[self.ref_idx_i_q] * self.limits[self.i_sq_idx]\n", " ref_i_d = reference[self.ref_idx_i_d] * self.limits[self.i_sd_idx]\n", - " \n", - " # Initialize variables for tracking the best vector\n", - " min_cost = float('inf')\n", - " best_vector = (0, 0, 0)\n", - " \n", - " # Evaluate each possible voltage vector\n", - " for v_a, v_b, v_c in self.voltage_vectors:\n", - " # Convert to dq coordinates\n", - " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", - " v_d, v_q = v_dq[0], v_dq[1] # Now properly ordered\n", - " \n", - " # Predict next state using motor model (Euler approximation)\n", - " # d-axis current prediction\n", - " i_d_next = i_d + self.tau * (\n", - " (v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d\n", - " )\n", - " \n", - " # q-axis current prediction\n", - " i_q_next = i_q + self.tau * (\n", - " (v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q\n", - " )\n", - " \n", - " # Calculate cost function (typically squared error)\n", - " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", - " \n", - " # Track the vector with minimum cost\n", - " if cost < min_cost:\n", - " min_cost = cost\n", - " best_vector = (v_a, v_b, v_c)\n", - " \n", - " # Normalize the output to [-1, 1] range\n", - " u_a = best_vector[0] / self.limits[self.u_a_idx]\n", - " u_b = best_vector[1] / self.limits[self.u_b_idx]\n", - " u_c = best_vector[2] / self.limits[self.u_c_idx]\n", - " \n", + "\n", + " # Run recursive simulation over all ph-length sequences\n", + " _, best_sequence = self._simulate_sequence(\n", + " i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0, max_depth=self.ph_\n", + " )\n", + "\n", + " # Take the first control input in the best sequence\n", + " best_v = best_sequence[0] if best_sequence else (0, 0, 0)\n", + "\n", + " # Normalize\n", + " u_a = best_v[0] / self.limits[self.u_a_idx]\n", + " u_b = best_v[1] / self.limits[self.u_b_idx]\n", + " u_c = best_v[2] / self.limits[self.u_c_idx]\n", + "\n", " return u_a, u_b, u_c\n", "\n", + "\n", " def reset(self):\n", " None\n", "\n", @@ -237,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -255,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -292,26 +302,37 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 9, "metadata": {}, "outputs": [ { - "data": { - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:35: UserWarning: \u001b[33mWARN: A Box observation space maximum and minimum values are equal.\u001b[0m\n", + " logger.warn(\"A Box observation space maximum and minimum values are equal.\")\n" + ] }, { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e6111102ae134545bacb562a47d5f829", + "version_major": 2, + "version_minor": 0 + }, + "image/png": "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", "text/html": [ - "
" + "\n", + "
\n", + "
\n", + " Time Plots\n", + "
\n", + " \n", + "
\n", + " " ], "text/plain": [ - "" + "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …" ] }, "metadata": {}, @@ -319,8 +340,8 @@ } ], "source": [ - "%matplotlib notebook\n", - "#%matplotlib widget\n", + "#%matplotlib notebook\n", + "%matplotlib widget\n", "# use %matplotlib widget for execution in visual studio code\n", "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", @@ -352,19 +373,19 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -2034.393103107837\n" + "Reward = -2059.299467864888\n" ] } ], "source": [ - "controller = Controller.make('fcs_mpc', env, ph=3) # initializing the MPC Controller\n", + "controller = Controller.make('fcs_mpc', env, ph=1) # initializing the MPC Controller\n", "(state, reference), _ = env.reset()\n", "cum_rew = 0\n", "\n", From 676d691c5f62df14f57488a88cb2e287bbed7d02 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 15 Jul 2025 20:51:08 +0200 Subject: [PATCH 19/37] Refactored FCS-MPC implementation to align with environment-provided utilities and improve prediction logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced manual abc → αβ → dq voltage transformations with environment's native method: - Updated voltage vector table to use finite set directly from the environment via: - Now storing and returning subaction **indices** instead of full abc voltage vectors for better integration and comparison - Updated epsilon handling in prediction loop: - Epsilon is now incremented as - dq-transformation uses averaged value: - Replaced hardcoded max_depth with (renamed from for clarity) Previously, I was doing manual calculations: converting abc voltages to αβ and then dq using custom logic. Now all transformations use the environment's own coordinate conversion function and finite action space, making the controller more accurate and modular. Thanks again for the helpful feedback! --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 118 +++++------------- 1 file changed, 31 insertions(+), 87 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 5ab538ab..6738c935 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -42,14 +42,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", - "import math\n", "import matplotlib\n", - "\n", "import gym_electric_motor as gem\n", "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", @@ -96,33 +94,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "class FCS_MPC(Controller):\n", - " def __init__(self, environment, ph=1, ref_idx_q=0, ref_idx_d=1):\n", - " # conversion of the coordinate systems \n", - "\n", - " def clarke(abc):\n", - " \"\"\"abc → αβ transformation (3-phase to 2-phase)\"\"\"\n", - " a, b, c = abc\n", - " alpha = a - 0.5*b - 0.5*c # = a (when a + b + c = 0)\n", - " beta = (np.sqrt(3)/2)*b - (np.sqrt(3)/2)*c\n", - " return np.array([alpha, beta]) * (2/3) # Power-invariant version\n", - " \n", - " def park(alpha_beta, theta):\n", - " \"\"\"αβ → dq transformation\"\"\"\n", - " alpha, beta = alpha_beta\n", - " d = alpha * np.cos(theta) + beta * np.sin(theta)\n", - " q = -alpha * np.sin(theta) + beta * np.cos(theta)\n", - " return np.array([d, q])\n", + " def __init__(self, environment, prediction_horizon=1, ref_idx_q=0, ref_idx_d=1): \n", " \n", - " \n", - " # Create transformation functions\n", - " self._forward_transformation = lambda abc, eps: park(clarke(abc), eps)\n", - " \n", - " \n", " # indices\n", " self.ref_idx_i_q = ref_idx_q\n", " self.ref_idx_i_d = ref_idx_d\n", @@ -144,54 +122,38 @@ " self.psi_ = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['psi_p']\n", " self.r_s = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['r_s']\n", " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", - " self.ph_ = ph # Prediction horizon (typically 1 for FCS-MPC)\n", - " \n", - " # Define the finite set of voltage vectors (for a 2-level inverter)\n", - " # These are the 8 possible switching states (6 active + 2 zero vectors)\n", - " self.voltage_vectors = [ \n", - " (2/3, -1/3, -1/3), # V1\n", - " (1/3, 1/3, -2/3), # V2\n", - " (-1/3, 2/3, -1/3), # V3\n", - " (-2/3, 1/3, 1/3), # V4\n", - " (-1/3, -1/3, 2/3), # V5\n", - " (1/3, -2/3, 1/3), # V6 \n", - " (0, 0, 0), # V0\n", - " (0, 0, 0) # V7\n", - " ]\n", + " self.abc_to_dq = environment.get_wrapper_attr('physical_system').abc_to_dq_space\n", + " self.prediction_horizon = prediction_horizon # Prediction horizon (typically 1 for FCS-MPC)\n", " \n", - " # Scale vectors by maximum voltage\n", - " self.voltage_vectors = [\n", - " (v_a * self.limits[self.u_a_idx], \n", - " v_b * self.limits[self.u_b_idx], \n", - " v_c * self.limits[self.u_c_idx])\n", - " for v_a, v_b, v_c in self.voltage_vectors\n", - " ]\n", + " # Get the finite set of voltage vectors from the environment\n", + " self.subactions = -np.power(-1, environment.get_wrapper_attr('physical_system')._converter._subactions)\n", " \n", - " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth, max_depth):\n", + " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth):\n", " min_cost = float('inf')\n", " best_sequence = []\n", "\n", - " for v_a, v_b, v_c in self.voltage_vectors:\n", - " # Convert to dq\n", - " v_dq = self._forward_transformation(np.array([v_a, v_b, v_c]), epsilon_el)\n", + " for idx, (v_a, v_b, v_c) in enumerate(self.subactions):\n", + " # Convert to dq \n", + " v_dq = np.transpose(np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5*omega*self.tau)]))\n", " v_d, v_q = v_dq[0], v_dq[1]\n", "\n", " # Predict next state\n", + " epsilon_next = epsilon_el + omega*self.tau\n", " i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d)\n", " i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q)\n", "\n", " # Cost for this step\n", " cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2\n", "\n", - " if depth == max_depth - 1:\n", + " if depth == self.prediction_horizon - 1:\n", " total_cost = cost\n", - " sequence = [(v_a, v_b, v_c)]\n", + " sequence = [idx]\n", " else:\n", " future_cost, future_sequence = self._simulate_sequence(\n", - " i_d_next, i_q_next, epsilon_el, omega, ref_i_d, ref_i_q, depth + 1, max_depth\n", + " i_d_next, i_q_next, epsilon_next, omega, ref_i_d, ref_i_q, depth + 1\n", " )\n", " total_cost = cost + future_cost\n", - " sequence = [(v_a, v_b, v_c)] + future_sequence\n", + " sequence = [idx] + future_sequence\n", "\n", " if total_cost < min_cost:\n", " min_cost = total_cost\n", @@ -199,9 +161,6 @@ "\n", " return min_cost, best_sequence\n", "\n", - "\n", - "\n", - "\n", " def control(self, state, reference):\n", " # Get current state values\n", " i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx]\n", @@ -215,24 +174,17 @@ "\n", " # Run recursive simulation over all ph-length sequences\n", " _, best_sequence = self._simulate_sequence(\n", - " i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0, max_depth=self.ph_\n", + " i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0\n", " )\n", "\n", " # Take the first control input in the best sequence\n", - " best_v = best_sequence[0] if best_sequence else (0, 0, 0)\n", - "\n", - " # Normalize\n", - " u_a = best_v[0] / self.limits[self.u_a_idx]\n", - " u_b = best_v[1] / self.limits[self.u_b_idx]\n", - " u_c = best_v[2] / self.limits[self.u_c_idx]\n", - "\n", - " return u_a, u_b, u_c\n", - "\n", + " best_idx = best_sequence[0] if best_sequence else 0\n", + " \n", + " return best_idx\n", "\n", " def reset(self):\n", " None\n", "\n", - "\n", "_controllers = { \n", " 'fcs_mpc': FCS_MPC\n", "}" @@ -247,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -265,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -302,32 +254,24 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 17, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:35: UserWarning: \u001b[33mWARN: A Box observation space maximum and minimum values are equal.\u001b[0m\n", - " logger.warn(\"A Box observation space maximum and minimum values are equal.\")\n" - ] - }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e6111102ae134545bacb562a47d5f829", + "model_id": "47799e64eeb84ba88aaee9f1d299e191", "version_major": 2, "version_minor": 0 }, - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAlMFJREFUeJzs3XtcVHX+P/DXzHAbVEBBEEzRNsU7rtISlJJX1LyWeVtTNigvYZa4/ryslVJf/ZrV7lfT0hC2Wis3b3gJZQ3zgoYREK3mLUgRkPAyQFxmYD6/P04zcGRQhIEZnNfz8TgPmfe8z5n3zOfM8T3nnDmjEEIIEBEREZHNUFq6ACIiIiJqXmwAiYiIiGwMG0AiIiIiG8MGkIiIiMjGsAEkIiIisjFsAImIiIhsDBtAIiIiIhvDBpCIiIjIxrABJCIiIrIxbACJiIiIbAwbQCIiIiIbwwaQiIiIyMawASQiIiKyMWwAiYiIiGwMG0AiIiIiG8MGkIiIiMjGsAEkIiIisjFsAImIiIhsDBtAIiIiIhvDBpCIiIjIxrABJCIiIrIxbACJiIiIbAwbQCIiIiIbwwaQiIiIyMawASQiIiKyMWwAiYiIiGwMG0AiIiIiG8MGkIiIiMjGsAEkIiIisjFsAImIiIhsDBtAIiIiIhvDBpCIiIjIxrABJCIiIrIxbACJiIiIbAwbQCIiIiIbwwaQiIiIyMawASQiIiKyMWwAiYiIiGwMG0AiIiIiG8MGkIiIiMjGsAEkIiIisjFsAImIiIhsDBtAIiIiIhvDBpCIiIjIxrABJCIiIrIxbACJiIiIbAwbQCIiIiIbwwaQiIiIyMawASQiIiKyMWwAiYiIiGwMG0AiIiIiG8MGkIiIiMjGsAEkIiIisjFsAImIiIhsDBtAIiIiIhvTIhvANWvW4NFHH0WbNm3g6emJiRMn4vz587KcsLAwKBQK2fTYY4/JcioqKrBgwQJ4eHigVatWGD9+PHJycprzqRARERE1O4UQQli6iPs1atQoTJs2DY8++igqKyuxYsUKZGZm4uzZs2jVqhUAqQG8fv06YmNjjfM5ODigXbt2xtvz5s3Dvn37EBcXB3d3d0RFReHmzZtITU2FSqW6Zx16vR65ublo06YNFAqF+Z8oERERmZ0QAsXFxfDx8YFS2SL3hTWeeAAUFBQIAOKbb74xxmbPni0mTJhQ5zy3b98W9vb24vPPPzfGrl27JpRKpUhISKjX4169elUA4MSJEydOnDi1wOnq1asN7j1aOjs8ADQaDQDI9u4BwNGjR+Hp6Qk3NzeEhITgrbfegqenJwAgNTUVOp0OI0eONOb7+PigT58+SE5ORmho6D0ft02bNgCArKysWo9NzUun0+Hw4cMYOXIk7O3tLV2OTeNYWBeOh/XgWFiPoqIidOrUyfj/uC1q8Q2gEAKLFi3CE088gT59+hjjo0ePxrPPPgtfX19kZWVh5cqVGDp0KFJTU+Ho6Ij8/Hw4ODigbdu2suV5eXkhPz/f5GNVVFSgoqLCeLu4uBgA4OTkBLVa3QTPjurLzs4Ozs7OUKvV3LBaGMfCunA8rAfHwnrodDoAsOnTt1p8AxgZGYkffvgBJ06ckMWnTp1q/LtPnz4ICAiAr68vDhw4gKeffrrO5Qkh6lwh1qxZg1WrVtWKJyUlwdnZuYHPgMwpMTHR0iXQ7zgW1oXjYT04FpZXWlpq6RIsrkU3gAsWLEB8fDyOHTuGhx566K653t7e8PX1xcWLFwEAHTp0gFarxa1bt2R7AQsKChAcHGxyGcuWLcOiRYuMtw27kIcMGQJ3d3czPCNqKJ1Oh8TERIwYMYKfrC2MY2FdOB7Wg2NhPYqKiixdgsW1yAZQCIEFCxZg9+7dOHr0KLp27XrPeW7cuIGrV6/C29sbADBw4EDY29sjMTERU6ZMAQDk5eXhxx9/xLp160wuw9HREY6OjrXi9r9PAACVClAqgd93L1cn2QNVVYBeXx27n1ylUso3lavXS/nmzLWzA4QwnVtZKd13t1yFQoo3NheQ4vfKVakAIeRjcbdchcL0ck2N0Z25QOPH80Ee+9+/UWevUED2X1xTjf39rieNGfsWvJ7I3hvWsJ5YYhtR13g25zYCd/yfcbfcB3kbYY71BGjwNsLehg/9Gln4SygNMm/ePOHq6iqOHj0q8vLyjFNpaakQQoji4mIRFRUlkpOTRVZWlkhKShJBQUGiY8eOoqioyLicuXPnioceekj85z//Ed9//70YOnSo8Pf3F5WVlfWqQ6PRCABCI61q0vTpp9KddnZCAEKvUAht376irKxMlG3cKMp8faunf/5TivfpUx3r31+KffSRPPeDD6T4o49Wx3r0kGLbt8tz33tPig8eLI+XlYmy3bvlsf/5HykeGiqP37ghyr76Sh5buVLKnThRHr92TZR98408tnixlDt9ujx+6ZIoS0mRx156Scp9/nl5/IcfpKlm7PnnpdyXXpLFi5OTRWJcnDx3+nQpd/Fi6XbnzkLbtq3QJyUJUVBQPWaAEMHB0rgtXCiP798vREmJPObvL+UuXy6P79ghxWvGHnlEir35pjweGyvFnZ2rYz4+Uuy99+S5GzdKcQ+P6pirqxTbskWeu26dFPf1rY7Z2UmxTz+V577+uhTv1Use12qF2L1bHlu8WMoNCJDHb90S4vBhWaxy7lyxZ88eUTV4sDw3J0eIEyfksdmzpeWOGSOPX7ggRFqaPDZ5spQ7ebI8npYm5deMjRkj5c6eLY+fOCHVUTMWEiLlzp8vjx8+LD2/mrGAACl38WJ5fPdu6XWrGevVS8p9/XV5/I5thACk8RJCGr+auVu2SHFX1+qYh4cU27hRnvvee1Lcx6c65uwstFqtSF2wQJ775ptS7iOPyONCSOtxzdjy5VLc318eLymR3h81YwsXSrnBwfJ4QYEQSUnyWESElDt8uDyelSVESoo8NmOGlDthgjz+44/SVDNmuPrDjBnyeEqKtOyaseHDpdyICHm8ibYRuu3bxZ49e+S5NriNEPPnS7khIfJ4M24jNCNGCABCo9EIW9UirwNY1zl6sbGxCAsLQ1lZGSZOnIi0tDTcvn0b3t7eGDJkCKKjo9GpUydjfnl5Of76179i+/btKCsrw7Bhw7Bp0yZZzt0UFRXB1dUVhXl51YeAa3xq0+p0yLt+HaVlZdInE1Mvtak4c6tjQL2WIQCUlZVB7eQkXz9M5Do7O8Pb2xsONa/91IL37Fjbp3tdVRUOHjqEMSNHwt7wybuu5XIPoPly6xhPHYCD+/djTGho9WFHK1hPbHEPoE6vx8GEBIy58xCwjW0jrGEPYFFxMVzd3aHRaODi4gJb1GIPAd+NWq3GoUOH7rkcJycnbNiwARs2bGhcQfb20lSDXqVC1s8/Q6VSwadjRzg4ONj0t42aml6vR0lJCVq3bl3nRT2FENBqtfj111+RlZ2Nbt261c5VqYyHaWRMna9jDblKpfGQq1lzFQrTuXYmNhl3y71z2fez3Lpqa6pcaxjPpsjV6aTX3MR2ymrWk8bk1lWbNa4nhkbK1FjY6jaiMbl11Vaf3LpybAhfgSai1Wqh1+vRqVMnfkO4Gej1emi1Wjg5Od31qu6Gyy/88ssvxnwiIiJbY6O/f9J8bPYnZqwYx4SIiGwd/yckIiIisjFsAImIiIhsDBtAMmnOnDmYMWOGpcsgIiKiJsAGkExas2YNtm7d2uD5w8LCsHTpUlksOTkZKpUKo0aNamx5RERE1AhsAMmkdu3aoVWrVg2aV6/X48CBA5gwYYIsvm3bNixYsAAnTpzAlStXzFEmERERNQAbQKolOzsbCoUCv/zyS4PmP3nyJJRKJQIDA42x3377DTt27MC8efMwduxYxMXFmalaIiIiul9sAKmW9PR0uLm5wdfXt0Hzx8fHY9y4cbLLrXzxxRfw8/ODn58fZs6cidjY2Hte0JuIiIiaBhtAqiUjIwP+/v4Nnj8+Pr7W4d+YmBjMnDkTADBq1CiUlJTgyJEjjaqTiIiIGoa/BGJh32XfxLpDP6G4vPLeyfXQxskeS0L9ENClXYOXkZ6e3uAG8Ny5c8jJycHw4cONsfPnzyMlJQW7du0CANjZ2WHq1KnYtm2bLI+IiIiaBxtAC9t6/GekZN0y+zIb0wBmZGRg/PjxKCkpweTJk3Ht2jUAwPr16xEaGoqVK1fiyy+/xMMPPwwhBObPn4+xY8cCkPb+jRgxAmq12ri8mJgYVFZWomPHjsaYEAL29va4desW2rZt2+BaiYiI6P6xAbSwFwY9jFulWrPuAXxh0MMNnr+oqAjZ2dnw9/fHoUOH4O7ujoSEBAghUFxcjJSUFCQkJCAjIwM3btxAz549MX/+fOP8e/fuRUREhPF2ZWUlPv74Y7zzzjsYOXKk7LGeeeYZ/Otf/0JkZGSD6yUiIqL7xwbQwgK6tMOOOcGWLsMoIyMDKpUKvXv3RuvWrfHqq69iyZIlmDRpEoKCgpCcnIxJkybBwcEB3t7eGDp0qHHegoICnDlzBnv27DHG9u/fj1u3biE8PByurq6yx5o8eTJiYmLYABIRETUzfgmEZDIyMtCjRw84Ojqie/fuSEtLQ58+fbBw4UJs3LgRQggoFAqT8+7btw+BgYHw9PQ0xmJiYjB8+PBazR8g7QFMT0/H999/32TPh4iIiGpjA0gykZGRyMzMBADk5uaiVatWmDVrFhYuXIj09HQ8/vjj2L17N7RaLfLz85GUlGScd+/evRg/frxsefv27cOBAwdMPtaAAQMghMCAAQOa7gkRERFRLTwETHXKzMzE4sWLoVKpoFarERMTg169eiE0NBT9+vWDn58fBg8ebMx/4oknMH36dAtWTERERPVhlgYwPT0d/fv3N8eiyIqEhoYiNDS0Vjw6OhrR0dEApN/8NViyZElzlUZERESN0OBDwBqNBps2bcKAAQMwcOBAc9ZERERERE3ovvcAfv3119i2bRt27doFX19fPPPMM4iJiWmK2qgF4G/6EhERtTz1agBzcnIQFxeHbdu24bfffsOUKVOg0+mwc+dO9OrVq6lrJCIiIiIzuuch4DFjxqBXr144e/YsNmzYgNzcXGzYsKE5aiMiIiKiJnDPPYCHDx/Gyy+/jHnz5qFbt27NURMRERERNaF77gE8fvw4iouLERAQgMDAQGzcuBG//vprc9RGRERERE3gng1gUFAQtm7diry8PMyZMweff/45OnbsCL1ej8TERBQXFzdHnURERERkJvW+DIyzszOef/55nDhxApmZmYiKisLatWvh6elZ69cfiIiIiMh6Neg6gH5+fli3bh1ycnLw2WefmbumZrVp0yZ07doVTk5OGDhwII4fP27pkoiIiIia1D0bwOXLlyMlJcXkfSqVChMnTkR8fLzZC2sOX3zxBV555RWsWLECaWlpGDRoEEaPHo0rV65YujQiIiKiJnPPBjAvLw9jx46Ft7c3XnzxRRw4cAAVFRXNUVuTe/fddxEeHo6IiAj07NkTf//739GpUyds3rzZ0qURERERNZl7NoCxsbG4fv06duzYATc3N0RFRcHDwwNPP/004uLiUFhY2Bx1mp1Wq0VqaipGjhwpi48cORLJyckWqoqIiIio6dXrl0AUCgUGDRqEQYMGYd26dTh37hz27duHrVu3Ys6cOQgMDMT48eMxffp0dOzYsalrNovCwkJUVVXBy8tLFvfy8kJ+fr7JeSoqKmR7P4uKigAAOp0OOp1OlqvT6SCEgF6vh16vN3P1dCchhPHfe73eer0eQgjodDqoVKrmKM+mGN4Ld74nyDI4HtaDY2E9OAYN+C1gAOjZsyd69uyJJUuW4Ndff0V8fLzxPMDFixebtcCmplAoZLeFELViBmvWrMGqVatqxZOSkuDs7CyL2dnZoUOHDigpKYFWqzVfwS2A4TVcu3Ytli5detfX1Nzqc1kirVaLsrIyHDt2DJWVlc1QlW1KTEy0dAlUA8fDenAsLK+0tNTSJVicQhh2ndgYrVYLZ2dn/Pvf/8akSZOM8YULFyI9PR3ffPNNrXlM7QHs1KkT8vLy4O7uLsstLy/H1atX0aVLFzg5OTXdE7FCmzdvhkqlwqVLl6BSqTBq1CiEhIQ06WMKIVBcXIw2bdrcs9ksLy9HdnY2OnXqZHNj0xx0Oh0SExMxYsQI2NvbW7ocm8fxsB4cC+tRVFQEDw8PaDQauLi4WLoci2jQHkAAOHjw4F3vHzNmTEMX3SwcHBwwcOBAJCYmyhrAxMRETJgwweQ8jo6OcHR0rBW3t7ev9WauqqqCQqGAUqmEUtmgq+1Y1Jw5c1BcXIzt27ff97wvvfQS3n77bWzYsAFff/01Hn/88SaoUM5w2Nfwmt+NUqmEQqEwOW5kPnx9rQvHw3pwLCyPr38jGsD3338fycnJGDZsGIQQSEpKQkhICNzc3KBQKKy+AQSARYsW4bnnnkNAQACCgoKwZcsWXLlyBXPnzrV0aRa3Zs0ak81ufXzwwQdwdXXFyy+/jP3790Ov12PQoEG18sLCwtChQwesXbvWGEtOTsagQYMwYsQIJCQkNLh+IiIiqluDG0ClUolz586hQ4cOAID8/HzMmzcPsbGxZiuuqU2dOhU3btzA6tWrkZeXhz59+uDgwYPw9fW1dGkW165duwbPO2fOHCgUCrzxxht44403YOosA71ejwMHDtS6huS2bduwYMECfPTRR7hy5Qo6d+7c4DqIiIjItAYfm7x8+TLat29vvO3u7o7z58+bpajmNH/+fGRnZ6OiogKpqakYPHiwpUuyuOzsbCgUCvzyyy8Nmt9wDt4bb7whu13TyZMnoVQqERgYaIz99ttv2LFjB+bNm4exY8ciLi6uQY9PREREd9fgPYDPPPMMgoODjefP7d69G5MnTzZbYWQ56enpcHNza9I9ofHx8Rg3bpzsfL0vvvgCfn5+8PPzw8yZM7FgwQKsXLmy2b5BTEREZCsavAcwOjoaGzduhFqthpOTE95//32sXr3anLWRhWRkZMDf379JHyM+Pr7Wl21iYmIwc+ZMAMCoUaNQUlKCI0eONGkdREREtqjBewATExMRFBSERx99FO+//z62bNmC1q1bo0ePHuas78F35TTwn1VAxb2vX1cvTi7AsNeAzo81eBHp6elN2gCeO3cOOTk5GD58uDF2/vx5pKSkYNeuXQCk6yhOnToV27Ztk+URERFR4zW4AVy8eDEyMjJw+vRpfPLJJ3j55ZcRHh6OkydPmrO+B1/yBuCKmX96LnlDoxrAjIwMjB8/3owFycXHx2PEiBFQq9XGWExMDCorK2W/JCOEgL29PW7duoW2bds2WT1ERES2psENoMGePXsQGRmJGTNm4O233zZHTbYleAFQetO8ewCDFzR49qKiImRnZ8Pf3x8lJSWYPHkyrl27BgBYv349QkNDsXLlSnz55Zd4+OGHIYTA/PnzMXbs2Ho/xt69exEREWG8XVlZiY8//hjvvPNOrd9mfuaZZ/Cvf/0LkZGRDX5OREREJNfgBtDHxwfPPfccjh8/jrS0NFRUVKCqqsqctdmGzo8Bz39l6SqMMjIyoFKp0Lt3b+zfvx/u7u5ISEgw/tJGSkoKEhISkJGRgRs3bqBnz56YP39+vZdfUFCAM2fOYM+ePcbY/v37cevWLYSHh8PV1VWWP3nyZMTExLABJCIiMqO7fglk5syZKCsrAwBcvXpVdt+XX36JSZMmITExEW3btsXNmzexfv36pquUmkVGRgZ69OgBR0dH9O3bF8ePH8eSJUtw+vRpuLi4IDk5GZMmTYKDgwO8vb0xdOjQ+1r+vn37EBgYCE9PT2MsJiYGw4cPr9X8AdIewPT0dHz//feNfm5EREQkuesewNatW6OiogJqtRq+vr5o27Yt/P394e/vj/79+8Pf3x9dunQBAHh7e8Pb27s5aqYmFBkZadzb1r17d6SlpeHAgQNYuHAhZs2aBSFEoy7Lsnfv3lrnF+7bt6/O/AEDBpi8kDQRERE13F33AH7wwQdwc3MDAGRlZWHbtm148skn8csvv2D16tUYOHAgWrdu3eSXDCHLyM3NRatWrTBr1iwsXLgQ6enpePzxx7F7925otVrk5+cjKSnpvpb5xBNPYPr06U1UMREREdVHvc8B9PX1ha+vr+zabcXFxUhPT8cPP/zQJMWRZWVmZmLx4sVQqVRQq9WIiYlBr169EBoain79+sHPz+++fzllyZIlTVQtERER1VejvgXcpk0bDBo0CIMGDTJXPWRFQkNDERoaWiseHR2N6OhoAEBYWFgzV0VERESN1eBfAiEiIiKilqnR1wEk2xYXF2fpEoiIiOg+cQ8gERERkY1hA0hERERkY9gAEhEREdkYNoBERERENoYNIBEREZGNYQNIREREZGPYABIRERHZGDaARERERDaGDSARERGRjWEDSERERGRj2ACSSXPmzMGMGTMsXQYRERE1ATaAZNKaNWuwdetWS5dRS1hYGJYuXWq8nZycDJVKhdGjR1uwKiIiopaFDSCZ1K5dO7Rq1crSZcjo9XocOHAAEyZMMMa2bduGBQsW4OTJk7h69aoFqyMiImo57CxdwP3Kzs5GdHQ0vv76a+Tn58PHxwczZ87EihUr4ODgYMxTKBS15t28eTPmzp1rvJ2ZmYnIyEikpKSgXbt2mDNnDlauXGly3rvS6aQJAFQqQKmUbgsB6PXSpFRKt4Wonk+hkCa9Xr48C+dmZ2ej6x/+gOysLPh27lw719QyANPLNWPuyePHoVQqERgYCAiB30pKsGPHDpz59lvk5eXhs+3b8eabb1Y/l7qWa/hbp5PGy5BrZwdUVclfN5VKuq+yUv5a2tubzjWMvTlzlUop31SuXi/lmzPXzk56jUzlVlbKX0tTuYa/73zNTOUaXndTy71zGebINTWe9zP2LXE9AaS8mnFrWE/uNp5NsZ7cbTzNvZ4ApsfI8LetbyPMtZ4ADd9G3Dletki0MF999ZUICwsThw4dEpcvXxZ79+4Vnp6eIioqSpYHQMTGxoq8vDzjVFpaarxfo9EILy8vMW3aNJGZmSl27twp2rRpI9avX1/vWjQajQAgNNUthhCffiqEEKLsD38QZ7/6SpSdOSNERoY0Q16eEGfOVE8FBVL8+++rY2lpUuz6dXlufr4UT0+vjqWmSrFff5Xn5uZK8R9+kMeFEOLGDXksJ0eK//ijMbb77beFm5ubELduyXN/+UXKPXdOHtdqhSgqkseysqTc8+fl8fJyIUpK5LHLl6Xcixfl8dJSafr99uKZM0X4s89KuZcvi5iVK0VAz55CnDkj4nfsEJ07dxb6lJTq+c+fl3KzsmTLLSssFGczM0WZr2/1uAUHS7kLF1bHACH275fqrRnz95dyly+Xx3fsMKx81dMjj0ixN9+Ux2Njpbizc3XMx0eKvfeePHfjRinu4VEdc3WVYlu2yHPXrZPiNZ+bnZ0U+/RTee7rr0vxXr3kca1WiN275bHFi6XcgAB5/NYtIQ4flsUq584Ve/bsEVWDB8tzc3KEOHFCHps9W1rumDHy+IUL0nuhZmzyZCl38mR5PC1Nyq8ZGzNGyp09Wx4/cUKqo2YsJETKnT9fHj98WHp+NWMBAVLu4sXy+O7d0utWM9arl5T7+uvy+O/bCGFnVx3z9ZVi69bJc7dskeKurtUxDw8ptnGjPPe996S4j091zNlZaLVakbpggTz3zTel3EcekceFkNbjmrHly6W4v788XlIivT9qxhYulHKDg+XxggIhkpLksYgIKXf4cHk8K0uIlBR5bMYMKXfCBHn8xx+lqWZswgQpd8YMeTwlRVp2zdjw4VJuRIQ8npQk1VwzZoZthG77drFnzx55rg1uI8T8+VJuSIg83ozbCM2IEQKA0Gg0wlYphBDCwj1oo7399tvYvHkzfv75Z2NMoVBg9+7dmDhxosl5Nm/ejGXLluH69etwdHQEAKxduxYbNmxATk5OvfYCFhUVwdXVFYV5eXB3d5eCv39qKy8uRtbVq+japQucnJwsvlfvfnJXrV6NpKNHcTQpyXSuqWUATZ7r17Mn1r/9NsaNHw8IgcefeAJTnn0WC19+GdrKSvj4+GD7v/6FkSNG3HW55RUVyMrORteHHpLGxpDb0vbsWOmne11VFQ4eOoQxI0fC3s7u7svlHkDz5dYxnjoAB/fvx5jQUNjb2981l3sA65HbiLHX6fU4mJCAMSNGVI9FHbkP8jbCGvYAFhUXw9XdHRqNBi4uLrBFLe4QsCkajQbt2rWrFY+MjERERAS6du2K8PBwvPjii1AqpdMeT506hZCQEGPzBwChoaFYtmyZdAi0a9f6F2BvL013xhQKaUX//TGNDcmdhDDdrJnaoNwrt+bjGf6tqa4aauSmZ2TA39+/7ty7xZso99y5c8jJycHw35u78xcuICUlBbt27QKUStjZ2WHSpEmIjYvDyNDQuy/X8LepcVOpqg8L13RnnrXk1hxvc+Ya1t072ZnYZNwt985l389y66qtqXKtYTybIlenk17zurZTd7LEetKY3Lpqs8b1xNBINee2x9q3EY3Jrau2+uTWlWNDWvwrcPnyZWzYsAHvvPOOLB4dHY1hw4ZBrVbjyJEjiIqKQmFhIf72t78BAPLz89GlSxfZPF5eXsb7TDWAFRUVqKioMN4uKioCAOh0Ouju/NSt00EIAb1eD/2dDdsdFKtXQ7F6tfG2/uOPgT//GQpnZyh+b+yEry/Ezz8D77wD5f/7f9W5H3wAvPACFO3bQ6HRQLz2GsTrr9/18e4lIyMDY8eORVFREZ599lnk5uYCANatW4fQ0FC89tpr2LlzJ7p27QohBObNm4exY8ciOzsbkyZNQv/+/XHmzBkMHjwYI0eOxP/+7/+ipKQEu3btQrdu3QAA48ePR15eHioqKvDGG2/g6aefRnJyMqKionDixAkUFhYiJCQEx44dg6enJ/bu3Yvhw4fD0dERer0eH330ESorK9GxY0dj3UII2Nvb48aNG2jbtm2dz0+v10MIAZ1OB5WpjSg1iuG9cOd7giyD42E9OBbWg2MAWM0h4DfeeAOrVq26a86ZM2cQEBBgvJ2bm4uQkBCEhITgo48+uuu877zzDlavXg2NRgMAGDlyJLp27YoPP/zQmHPt2jU89NBDOHXqFB577LF617h9+3Y4OzvLYnZ2dujQoQM6deok+3KKSeY8RGTYtd5ARUVF6NKlC5KSkvDLL79g37592Lp1K4QQKC4uxsWLF/HXv/4VCQkJuHnzJgIDA/Hhhx9i1KhRuHLlCgICAnDy5Ek8/PDDCA4OxsiRIxEdHY1t27bhwoULWLt2LQDg1q1baNu2LTQaDUaMGIFvv/0WCoUCK1asgIeHB1JTUzF+/HhMmTIFgDRes2bNwsyZM1FZWYnevXvj5ZdfxpAhQ2T1z549Gy+88AJefPHFOp+jVqvF1atXkZ+fj0qeCExEZHNKS0sxY8YMHgK2BpGRkZg2bdpdc2ruscvNzcWQIUMQFBSELVu23HP5jz32GIqKinD9+nV4eXmhQ4cOyM/Pl+UUFBQAqN4TeKdly5Zh0aJFxttFRUXo1KkThgwZUn0O4O/Ky8tx9epVtG7duvo8sxYgIyMDKpUKf/rTn+Dl5YW//e1veOuttzBx4kQEBQVh586deOaZZ+Dh4QEPDw8MHToUzs7OcHFxQevWreHn54eBAwcCAHr16oUxY8bAxcUFf/rTn3D06FHjG239+vXYt28fACAnJwelpaXw9vbGunXr0L9/f/j5+SEiIgKANC5paWmIj4+Hi4sL9uzZg9u3b2P+/PlwdXUFAGODOnnyZHz22WdYvHhxnc+xvLwcarUagwcPblFj01LodDokJiZixJ3nOZFFcDysB8fCehiO4Nkyq2kADQ1FfVy7dg1DhgzBwIEDERsbazyv727S0tLg5OQENzc3AEBQUBCWL18OrVZr3EN3+PBh+Pj41Do0bODo6Cg7Z9Cw87S8vBxlZWWyXK1WCyGEcWopMjIy0KNHDzg4OKBbt2747rvvcPDgQbzyyit47rnnjIezaz6nms/T0dHReJ9SqYSDgwOEEFAoFKiqqoIQAklJSTh58iROnjwJtVqN3r17o7y8HEIIXL9+HZWVlSgsLERlZSVUKhX27duHP/3pT2jfvj2EENi2bRuGDRsGFxcX42MJIVBVVYVJkyZh7dq1SE1NxYABA0w+R0OtFRUVLWpsWgqdTofS0lKUlZVxD6sV4HhYD46F9TD8n23L/wdYTQNYX7m5uXjyySfRuXNnrF+/Hr/++qvxvg4dOgAA9u3bh/z8fAQFBUGtViMpKQkrVqzAiy++aGzgZsyYgVWrViEsLAzLly/HxYsX8T//8z947bXX6n0dwBs3bgCAyfMFfX198cEHH9RqDK1dcHAwgoODkZaWhl9//RUuLi7o06cPxo8fj6+//hqTJk3CunXrMHToUGg0Ghw5cgSDBw9GWloacnNzUVpairS0NADA7du3cenSJbi6uuLChQsoKipCWlqacS/jTz/9hP/+97+4cOEC/vvf/+LWrVuIjIzEwoULkZycjL/+9a947rnn8Mknn2DgwIHG5b7xxhsAYLxdk1KpxJkzZ+q836CwsBBPPfUUfvnlFzO/gkRE1FIUFxcbjyTZmhbXAB4+fBiXLl3CpUuX8NBDD8nuM3Ty9vb22LRpExYtWgS9Xo+HH34Yq1evxksvvWTMdXV1RWJiIl566SUEBASgbdu2WLRokewQ770Yvnl85cqVWiuQVqvF9evX0cVwGZgW6NChQ5g7dy5UKhWcnJywdetW9OrVCxcuXMBf/vIXdO/eHU8++SQefvhh/PGPf0Tbtm3h7OyMP/7xjwAANzc3PPLII/jjH/+IiooKuLi44I9//CN69uyJhIQEREREoF+/fujbty969+6NI0eO4JFHHsGCBQvwl7/8BUFBQZg3bx7GjBmDadOmoVOnTnXWWlVVhR9++AH9+vW75xc7ysvLkZ2dje++++7e52fSfTOcGnH16lWbPbfGmnA8rAfHwnoYThvy8fGxdCkWYzVfAmmJDNcBNHUSaXl5ObKystC1a9cW2wDWR1hYGCZPnoyxY8datI6qqiqkpaXhj3/8Y70aQFsYG0u52/uCmh/Hw3pwLMia8LeAiYiIiGxMizsETNYlLi7O0iUQERHRfeIewEZwdHTE66+/LvtmMFmGQqGAj49Pvb/AQ02H7wvrwvGwHhwLsiY8B7CJ8Dwz68WxISIiW8c9gEREREQ2hg0gERERkY1hA0hERERkY9gANjGeYml9OCZERGTr2AA2EcMPfZeWllq4ErqTYUz4Y+xERGSreB3AJqJSqeDm5oaCggIAgLOzMy9RYmFCCJSWlqKgoABubm73/MUQIiKiBxUvA9OEhBDIz8/H7du3LV0K1eDm5oYOHTqwISciIpvFBrAZVFVVQafTWboMgnTYl3v+iIjI1rXIBnDNmjXYtWsXfvrpJ6jVagQHB+N///d/4efnZ8wJCwvDP//5T9l8gYGBOH36tPF2RUUFFi9ejM8++wxlZWUYNmwYNm3ahIceeqhedej1euTm5qJNmzbcm0RERNRCCCFQXFwMHx8fKJW2+XWIFtkAjho1CtOmTcOjjz6KyspKrFixApmZmTh79ixatWoFQGoAr1+/jtjYWON8Dg4OaNeunfH2vHnzsG/fPsTFxcHd3R1RUVG4efMmUlNT67WXKCcnB506dTL/EyQiIqImd/Xq1Xrv9HnQtMgG8E6//vorPD098c0332Dw4MEApAbw9u3b2LNnj8l5NBoN2rdvj08++QRTp04FAOTm5qJTp044ePAgQkND7/m4Go0Gbm5uyMrKkjWW1Px0Oh0OHz6MkSNH8tu9FsaxsC4cD+vBsbAeRUVF6NSpE27fvg1XV1dLl2MRD8S3gDUaDQDUasKOHj0KT09PuLm5ISQkBG+99RY8PT0BAKmpqdDpdBg5cqQx38fHB3369EFycrLJBrCiogIVFRXG28XFxQAAJycnqNVqsz8vqj87Ozs4OztDrVZzw2phHAvrwvGwHhwL62E4L9+WT99q8Q2gEAKLFi3CE088gT59+hjjo0ePxrPPPgtfX19kZWVh5cqVGDp0KFJTU+Ho6Ij8/Hw4ODigbdu2suV5eXkhPz/f5GOtWbMGq1atqhVPSkqCs7OzeZ8YNUhiYqKlS6DfcSysC8fDenAsLI/X6H0AGsDIyEj88MMPOHHihCxuOKwLAH369EFAQAB8fX1x4MABPP3003UuTwhR5yeCZcuWYdGiRcbbhl3IQ4YMgbu7eyOfCTWGTqdDYmIiRowYwU/WFsaxsC4cD+vBsbAeRUVFli7B4lp0A7hgwQLEx8fj2LFj9zyJ09vbG76+vrh48SIAoEOHDtBqtbh165ZsL2BBQQGCg4NNLsPR0RGOjo614vb29nwzWwmOhfXgWFgXjof14FhYHl//FtoACiGwYMEC7N69G0ePHkXXrl3vOc+NGzdw9epVeHt7AwAGDhwIe3t7JCYmYsqUKQCAvLw8/Pjjj1i3bt39FaTTSRMAqFSAUll9G79fB1D6A9Drq+czkQsAsLevnatUSvmmcvV6Kd+cuXZ2gBCmcysrpfvulqtQSPHG5gJS/B65OiFgp1KhvKQEVYb57sxVKGBvZweVg4N0n6nlmhqjO3MNr2VjxvNBHnvD33e+Zk009veVa2o872fsW+J6Akh5NePWsJ408zbiruNp7vUEMD1Ghr9tfRthrvUEaPg24s7xskEt8uI3L730Ej799FNs374dbdq0QX5+PvLz81FWVgYAKCkpweLFi3Hq1ClkZ2fj6NGjGDduHDw8PDBp0iQAgKurK8LDwxEVFYUjR44gLS0NM2fORN++fTF8+PD7qsfe2xtwcJCmzz6Tgs7OEI6OyJs7Fxe+/hpZWVnIysxE1smT1dOPP0rx5OTq2KlTUuy//zWde/p0dSw52XRuZqYU//ZbeTwrC1lnz8pjP/wgxc+ckcd//hlZP/0kj6Wnm869fBlZ58+bzv3uO3n80iVkXbggj6WlSbmpqfL4xYvSVDOWmirlpqXJ4ld//hnenp7Iqfmcv/tOyk1Pl24fP44Lhw4h7/RpiF9/rR4zBwcgJEQat6goefyrr4DSUnns0Uel3Ndek8d37pTiNWO9ekmxtWvl8Y8/luJubtWxLl2k2IYN8twPPpDiPj7VsfbtpVhMjDz33XeleLdu1THD+amffSbPjY6W4v37y+OVlUB8vDy2dKmUGxQkj2s0wJEjspjy1VcBAKpRo+S5ubnAqVPyWESEtNwJE+TxS5eAjAx5bPp0KXf6dHk8I0PKrxmbMEHKjYiQx0+dkuqoGTO8319+WR4/ckR6fjVjQUFS7tKl8nh8vPS61Yz17y/lRkfL4zW2EcZYt25S7N135bkxMVK8ffvqmI+PFPvgA3nuhg1SvEuX6pibGwCgU1IS7Fu1qo6vXSvl9uolXwYgrcc1Y6+9JsUffVQeLy2V3h81Y1FRUm5IiDxeWAgcOyaPzZsn5Y4eLY//8guQmiqPzZ4t5U6eLI+fPStNNWOTJ0u5s2fL46mp0rJrxkaPlnLnzZPHjx2Taq4ZM8M2QrFrFwDIx8IGtxF4+WUpd/hwebw5txHTpsHWtcjLwNR1jl5sbCzCwsJQVlaGiRMnIi0tDbdv34a3tzeGDBmC6Oho2XX7ysvL8de//hXbt2+XXQi6vtf2KyoqgqurKwrz8qrPAazxqS3v+nXcLiqCZ/v2cG7dGgpA/qlEoZCmmp/kAGl+ISyba3iNTeWaWoaFc/UASoqL0bp1aygN892Ra/wt4MJCuLm6wtuwgTTktrQ9O1b66V5XVYWDhw5hzMiRsDd88q5rudwDaL7cOsZTB+Dg/v0YExpafdjLCtYTW9wDqNPrcTAhAWPuPAfQxrYR1rAHsKi4GK7u7tBoNHBxcYEtarGHgO9GrVbj0KFD91yOk5MTNmzYgA2GT84NZW8vTTVUKZW4XVwMTy8vfkGkGej1emi1Wjip1Xe9qru6VStAqURBQQE8vbxqX/BbpZKmO5k6X8QacpVKaTJ3rkJhOtfOxCbjbrl3Lvt+lltXbU2Vaw3j2RS5Op30mpvYTlnNetKY3Lpqs8b1xNBImRoLW91GNCa3rtrqk1tXjg1pkYeAWwLDNYZ4eRjrYxgT/j4zERHZKjaATcyWLzJprTgmRERk69gAEhEREdkYNoBERERENoYNIJk0Z84czJgxw9JlEBERURNgA0gmrVmzBlu3bm3w/GFhYVhquC7U75KTk6FSqTBq1KjGlkdERESNwAaQTGrXrh1atWrVoHn1ej0OHDiACYaL8f5u27ZtWLBgAU6cOIErV66Yo0wiIiJqADaAVEt2djYUCgV++eWXBs1/8uRJKJVKBAYGGmO//fYbduzYgXnz5mHs2LGIi4szU7VERER0v9gAUi3p6elwc3ODr69vg+aPj4/HuHHjZBdk/uKLL+Dn5wc/Pz/MnDkTsbGx97ygNxERETUNNoBUS0ZGBvz9/euVGxYWhv3798ti8fHxtQ7/xsTEYObMmQCAUaNGoaSkBEeOHDFPwURERHRf+FsoFvZd9k2sO/QTissr751cD22c7LEk1A8BXdo1eBnp6en1bgDvdO7cOeTk5GD48OHG2Pnz55GSkoJdv/8Qup2dHaZOnYpt27bJ8oiIiKh5sAG0sK3Hf0ZK1i2zL7MxDWBGRgbGjx+PkpISTJ48GdeuXQMArF+/HqGhoVi5ciW+/PJLPPzww7UO48bHx2PEiBFQq9XGWExMDCorK9GxY0djTAgBe3t73Lp1C23btm1wrURERHT/2ABa2AuDHsatUq1Z9wC+MOjhBs9fVFSE7Oxs+Pv749ChQ3B3d0dCQgKEECguLkZKSgoSEhKQkZGBGzduoGfPnpg/f75x/r179yIiIsJ4u7KyEh9//DHeeecdjBw5UvZYzzzzDP71r38hMjKywfUSERHR/WMDaGEBXdphx5xgS5dhlJGRAZVKhd69e6N169Z49dVXsWTJEkyaNAlBQUFITk7GpEmT4ODgAG9vbwwdOtQ4b0FBAc6cOYM9e/YYY/v378etW7cQHh4OV1dX2WNNnjwZMTExbACJiIiaGb8EQjIZGRno0aMHHB0d0b17d6SlpaFPnz5YuHAhNm7cCCEEFAqFyXn37duHwMBAeHp6GmMxMTEYPnx4reYPkPYApqen4/vvv2+y50NERES1sQEkmcjISGRmZgIAcnNz0apVK8yaNQsLFy5Eeno6Hn/8cezevRtarRb5+flISkoyzrt3716MHz9etrx9+/bhwIEDJh9rwIABEEJgwIABTfeEiIiIqBazHgJOT09H//79zblIsqDMzEwsXrwYKpUKarUaMTEx6NWrF0JDQ9GvXz/4+flh8ODBxvwnnngC06dPt2DFREREVB+NbgA1Gg3+9a9/4aOPPkJGRgaqqqrMURdZgdDQUISGhtaKR0dHIzo6ulZ8yZIlzVEWERERNVKDDwF//fXXmDlzJry9vbFhwwaMGTMG3333nTlrIyIiIqImcF97AHNychAXF4dt27bht99+w5QpU6DT6bBz50706tWrqWokIiIiIjOq9x7AMWPGoFevXjh79iw2bNiA3NxcbNiwoSlrIyIiIqImUO89gIcPH8bLL7+MefPmoVu3bk1ZExERERE1oXrvATx+/DiKi4sREBCAwMBAbNy4Eb/++mtT1kZERERETaDeDWBQUBC2bt2KvLw8zJkzB59//jk6duwIvV6PxMREFBcXN2WdRERERGQm9/0tYGdnZzz//PM4ceIEMjMzERUVhbVr18LT07PWRYCJiIiIyPo06pdA/Pz8sG7dOuTk5OCzzz4zV01ERERE1ITq3QAuX74cKSkpJu9TqVSYOHEi4uPjzVZYc9m0aRO6du0KJycnDBw4EMePH7d0SURERERNqt4NYF5eHsaOHQtvb2+8+OKLOHDgACoqKpqytib3xRdf4JVXXsGKFSuQlpaGQYMGYfTo0bhy5YqlSyMiIiJqMvVuAGNjY3H9+nXs2LEDbm5uiIqKgoeHB55++mnExcWhsLCwKetsEu+++y7Cw8MRERGBnj174u9//zs6deqEzZs3W7o0IiIioiZzX78EolAoMGjQIAwaNAjr1q3DuXPnsG/fPmzduhVz5sxBYGAgxo8fj+nTp6Njx45NVbNZaLVapKamYunSpbL4yJEjkZycbHKeiooK2V7PoqIiAIBOp4NOp5Pl6nQ6CCGg1+uh1+vNXL11E0JAoVBg1apVeP311423m/oxDf/e6/XW6/UQQkCn00GlUjVpXbbI8F648z1BlsHxsB4cC+vBMbjPBvBOPXv2RM+ePbFkyRL8+uuviI+PN54HuHjxYrMU2FQKCwtRVVUFLy8vWdzLywv5+fkm51mzZg1WrVpVK56UlARnZ2dZzM7ODh06dEBJSQm0Wq35Cm8BPvroI9jZ2eHmzZtYtGgRRowYgccff7xZHrs+lyPSarUoKyvDsWPHUFlZ2QxV2abExERLl0A1cDysB8fC8kpLSy1dgsUphGHXiY3Jzc1Fx44dkZycjKCgIGP8rbfewieffIKffvqp1jym9gB26tQJeXl5cHd3l+WWl5fj6tWr6NKlC5ycnJruiTSRuXPnori4GP/6178aNP/69euxcuVK/Oc//2mW5k8IgeLiYrRp0+aeexvLy8uRnZ2NTp06tcixsXY6nQ6JiYkYMWIE7O3tLV2OzeN4WA+OhfUoKiqCh4cHNBoNXFxcLF2ORTR4D+DBgwfvev+YMWMauuhm4eHhAZVKVWtvX0FBQa29ggaOjo5wdHSsFbe3t6/1Zq6qqoJCoYBSqYRS2air7VjE2rVr4ejo2KDaP/jgA7i5ueHll182rieDBg2qlRcWFoYOHTpg7dq1xlhycjIGDRqEESNGICEhod6PaTjsa3jN70apVEKhUJgcNzIfvr7WheNhPTgWlsfXvxEN4L///W8AUsOUnJyMYcOGQQiBpKQkhISEWH0D6ODggIEDByIxMRGTJk0yxhMTEzFhwgQLVmYd2rVr1+B558yZA4VCgTfeeANvvPEGTO1k1uv1OHDgQK1LB23btg0LFizARx99hCtXrqBz584NroOIiIhMa3ADGBsbCwAYN24czp07hw4dOgAA8vPzMW/ePPNU18QWLVqE5557DgEBAQgKCsKWLVtw5coVzJ0719KlWVR2dja6du2K7Oxs+Pr63vf8hkOwb7zxhux2TSdPnoRSqURgYKAx9ttvv2HHjh04c+YM8vPzERcXh9dee61hT4KIiIjq1Ohjk5cvX0b79u2Nt93d3XH+/PnGLrZZTJ06FX//+9+xevVq9O/fH8eOHcPBgwcb1PQ8SNLT0+Hm5takr0N8fDzGjRsnO1z7xRdfwM/PD35+fpg5cyZiY2NN7j0kIiKixmnUt4AB4JlnnkFwcLDxMOru3bsxefLkRhfWXObPn4/58+dbugyrkpGRAX9//yZ9jPj4eKxfv14Wi4mJwcyZMwEAo0aNQklJCY4cOYLhw4c3aS1ERES2ptENYHR0NMaPH4/k5GQIIfD+++8jICDAHLXZhiungf+sAiruffmSenFyAYa9BnR+rMGLSE9Pr3cDGBYWhsmTJ2Ps2LH1Xv65c+eQk5Mja+zOnz+PlJQU7Nq1C4B0GZ2pU6di27ZtbACJiIjMrNENYGJiIoKCgvDoo4/i/fffx5YtW9C6dWv06NHDHPU9+JI3AFdMX3i6UctsRAOYkZGB8ePHm7Egufj4eIwYMQJqtdoYi4mJQWVlpewC4kII2Nvb49atW2jbtm2T1UNERGRrGt0ALl68GBkZGTh9+jQ++eQTvPzyywgPD8fJkyfNUd+DL3gBUHrTvHsAgxc0ePaioiJkZ2fD398fJSUlmDx5Mq5duwZAurZfaGgoVq5ciS+//BIPP/xwg87R27t3LyIiIoy3Kysr8fHHH+Odd97ByJEjZbnPPPMM/vWvfyEyMrLBz4mIiIjkGt0AGuzZsweRkZGYMWMG3n77bXMt9sHX+THg+a8sXYVRRkYGVCoVevfujf3798Pd3R0JCQnGCy2npKQgISEBGRkZuHHjBnr27Hlf51AWFBTgzJkz2LNnjzG2f/9+3Lp1C+Hh4XB1dZXlT548GTExMWwAiYiIzKjR3wL28fHBc889h88//xxPPfUUKioqUFVVZY7ayAIyMjLQo0cPODo6om/fvjh+/DiWLFmC06dPw8XFBcnJyZg0aRIcHBzg7e2NoUOH3tfy9+3bh8DAQHh6ehpjMTExGD58eK3mD5D2AKanp+P7779v9HMjIiIiSb0awJkzZ6KsrAwAcPXqVdl9X375JSZNmoTExES0bdsWN2/erPXtTmo5IiMjkZmZCQDo3r070tLS0KdPHyxcuBAbN26EEOKeP7V2N3v37q11fuG+fftw4MABk/kDBgyAEAIDBgxo8GMSERGRXL0awNatWxt/A9fX1xfu7u4YOnQoXn31VeO5YF26dAEAeHt71zqPi1qm3NxctGrVCrNmzcLChQuRnp6Oxx9/HLt374ZWq0V+fj6SkpLua5lPPPEEpk+f3kQVExERUX3U6xzADz74wPh3VlYW0tPTkZGRgfT0dMTHxyM7Oxt2dnbo0aMHMjIymqxYal6ZmZlYvHgxVCoV1Go1YmJi0KtXL4SGhqJfv37w8/PD4MGD72uZS5YsaaJqiYiIqL7u+0sgvr6+8PX1lf1ebnFxMdLT0/HDDz+YtTiyrNDQUISGhtaKR0dHIzo62gIVERERkTmY5VvAbdq0waBBgzBo0CBzLI6IiIiImlCjvwVMRERERC0LG0AiIiIiG8MGkIiIiMjGsAEkIiIisjFsAImIiIhsDBtAIiIiIhvDBpCIiIjIxrABJCIiIrIxbACJiIiIbAwbQDJpzpw5mDFjhqXLICIioibABpBMWrNmDbZu3WrpMmoJCwvD0qVLjbeTk5OhUqkwevRoC1ZFRETUsrABJJPatWuHVq1aWboMGb1ejwMHDmDChAnG2LZt27BgwQKcPHkSV69etWB1RERELQcbQKolOzsbCoUCv/zyi6VLkTl58iSUSiUCAwMBAL/99ht27NiBefPm4amnnsJnn31m4QqJiIhaBjaAVEt6ejrc3Nzg6+tr6VJk4uPjMW7cOCiV0mr7xRdfwM/PD35+fvjzn/+Mf/3rXxBCWLhKIiIi68cGkGrJyMiAv79/vXLDwsKwf//+Jq5IEh8fLzv8GxMTg5kzZwIARo0ahd9++w1HjhxpllqIiIhaMjaA1qCqCtDpqie9XorXjOl09cutqmp0Oenp6fVuAJvLuXPnkJOTg+HDhwMAzp8/j5SUFEybNg0AYGdnh0mTJiE2NtaSZRIREbUILa4BzM7ORnh4OLp27Qq1Wo0//OEPeP3116HVamV5CoWi1vTBBx/IcjIzMxESEgK1Wo2OHTti9erVDTuEWFdDJoR02xCreVuvl24DwOrVgIND9fTZZ9J9zs7VsW7dpNx33pHnxsRIue3bS7dXr65ebs3HulcNNWIZGRno7++PkuJijAoNRd++fdG3b18cSkgAAKz829/Qs2dPPDVmDAquX5eWIQSyf/4Z/v7+CJs9G7169cK8efOwZ/duBAYGonfv3rh4/rwxd9zYsRg4cCD69OmDXTt3AgCST55EYGAgqnQ6XM/LQ/fu3aXl6/WI37sXI4YPh9rJCQAQ89FHqKysRMeOHWFnZwcHBwds27YNu3fvxq0bN+TPra7nXHPcKiulmKkGW4iGNePmyjU09aZy9Xrz5xpeL1O5lZXmzTW87qZy73zdzZFrajzvZ+y5njTP2DfFenK38TT3esKxb571xBzbCFsmWpivvvpKhIWFiUOHDonLly+LvXv3Ck9PTxEVFSXLAyBiY2NFXl6ecSotLTXer9FohJeXl5g2bZrIzMwUO3fuFG3atBHr16+vdy0ajUYAEJrq1UuITz8VQghR9oc/iLNffSXKzpwRIiNDmiEvT4gzZ6qnggIpfuaMEKdOSVNKihBVVUJcv14dO3VKiKtXpdzU1OrY6dNS7q+/ymO5uVLuDz/IH08IIW7ckMdycqT4jz8KceaM0CQlCYVCIVJTUsSX//ynmBEaKsSZM0KfkiI0P/4ovv32WxHQp4+oSE4WuV99JVxbtxb7du8WoqhIZO3dK+zt7MRP//63qLx0SfTo0UMsfv55Ic6cEZuXLhUvT50qRHm5ECUl4sZ//iPEmTPidlKS8Hv4YaHX64W4eFG8OmOGWPPSS2Lik0+KT7dtE6K0VIgzZ0RQ374iZuVKIS5eFDqdTnh5eIh3XnlFZH72mcj87DPxw7ffiuRvvhHdO3cWG/76V+m5nT8vPbesLNlzLissFGczM0WZr2/1uAUHS7kLF1bHACH27xeipEQe8/eXcpcvl8d37DCsfNXTI49IsTfflMdjY6W4s3N1zMdHir33njx340Yp7uFRHXN1lWJbtshz162T4jWfm52dFPv0U3nu669L8V695HGtVojdu+WxxYul3IAAefzWLSEOH5bFKufOFXv27BFVgwfLc3NyhDhxQh6bPVta7pgx8viFC0KkpcljkydLuZMny+NpaVJ+zdiYMVLu7Nny+IkTUh01YyEhUu78+fL44cPS86sZCwiQchcvlsd375Zet5qxXr2k3Ndfl8d/30YIO7vqmK+vFFu3Tp67ZYsUd3Wtjnl4SLGNG+W5770nxX18qmPOzkKr1YrUBQvkuW++KeU+8og8LoS0HteMLV8uxf395fGSEun9UTO2cKGUGxwsjxcUCJGUJI9FREi5w4fL41lZ0nawZmzGDCl3wgR5/McfpalmbMIEKXfGDHk8JUVads3Y8OFSbkSEPJ6UJNVcM2aGbYRu+3axZ88eea4NbiPE/PlSbkiIPN6M2wjNiBECgNBoNMJWwdIFmMO6detE165dZTEAYvfu3XXOs2nTJuHq6irKy8uNsTVr1ggfHx+pGakHQwNYmJcnvSG0WqkhE0KUFRWJs//9ryj77TdjTOj10t+GyfA4NWMWzj129Kiws7MT5WVl4vxPP4lOnTqJvy5eLJJPnBBCrxfvvfeeeOvNN435kyZOFPvi44XQ60XW5cuiT58+xuVOmjRJJHz1lRBVVeLk8eNi/Lhx0uPp9eJvK1aIfv36iX79+gm1Wi1yc3OF0OtFaUmJeOSRR8TYp54y5l7PyxN2dnbiel6eEHq92L17t3BwcBC3b9401lFVVSVu3bwpli1dKvr37y9/bnc857LSUnH27FlRVlRUPW46nZRbWVkdM4ynXi+PabV15wph/tzKyrpzq6rMn2t4vUzl6nT3zNWWlYk9e/YIbWnpvZdreN1NLffO190cuabG837GvgWuJ1qtVuzZtUtof/vNqtaTu45nU6wndxtPc68ndYyRtrxcem/UHIsmHHtr3UaYbT1pxDZCc+OGzTeAdpbc+2guGo0G7dq1qxWPjIxEREQEunbtivDwcLz44ovGb5CeOnUKISEhcHR0NOaHhoZi2bJlyM7ORteuXWstr6KiAhUVFcbbRUVFAADd7xMAadd2VRV0AAQA/e+TcXd+TYbPIneyYG76Dz+gR48esHdwwCPduiE1NRUHDhzAwldfxXPPPQe9Xg8oFDAsSQDQCyFNABwdHaX7hIBCoYC9g4N0W6lEZVUV9EIgKSkJJ5OTkZycDLVajV69eqGsrAx6IZBfUIDKykoU3rgBXWUlVCoV9u7bh8DAQHh4ekIvBD766CMMGzYMbVxdq+sQAlAoMOnpp7Fm7Vp89/33GDBggMnXQS998IEOgKrm66DT1cqt85zKunJN5Tc2t+Yh/ObINXVo5D5ydb/n6e587U0tt67XvalyTb3mTZULWMV6otPpAKUSunrkNud6Yotjr/s9t9Y9NraNsIb1pNb2yQa1+Abw8uXL2LBhA9555x1ZPDo6GsOGDYNarcaRI0cQFRWFwsJC/O1vfwMA5Ofno0uXLrJ5vLy8jPeZagDXrFmDVatW1YonJSXB2dlZFrOzs0OHDh1QUlJS6/xEa/bcc8/hueeeQ1FREfLy8tC2bVtMnDgRWq0WJ06cwOzZs7FkyRJERETg1q1bSEpKwtSpU1FUVISSkhJUVVUZG+PKykqUlpaiqKgIv/32GyorK1FUVITr16/DxcUFOp0Op0+fxoULF1BSUoKioiKEh4dj7dq1+M9//oO1a9diwYIF2LVrF0aMGGFc7qeffgqgugGvqVu3brh161ad9wOAVqtFWVkZjh07hkqeB9JkEhMTLV0C1cDxsB4cC8srLS21dAkWZzUN4BtvvGGyuarpzJkzCAgIMN7Ozc3FqFGj8OyzzyIiIkKWa2j0AKB///4AgNWrV8viCoVCNo/4/RPBnXGDZcuWYdGiRcbbRUVF6NSpE4YMGQJ3d3dZbnl5Oa5evYrWrVvD6fcvLrQ0p06dwpQpU6BSqaBWq7F161b06tULo0ePxuDBg9G9e3cMHjwYzs7OcHFxQevWraFSqeDi4gJAaoIN97Vq1Qp2dnZwcXHBxIkTERsbiyeffBL9+vVD37590bp1a+zYsQM+Pj549tlnMXr0aDz22GOYMmUKnnzySUybNs24XFOEECguLkabNm3qHD+D8vJyqNVqDB48uMWOjTXT6XRITEzEiBEjYG9vb+lybB7Hw3pwLKxHXTsIbIlCCOvYD1pYWIjCwsK75nTp0sX4H3Zubi6GDBmCwMBAxMXFGQ/t1uXkyZN44oknkJ+fDy8vL8yaNQsajQZ79+415qSlpWHAgAH4+eefTe4BvJNGo4GbmxuysrJqHYLWarW4fv26rGZqOkIIaDQauLq61qsBzM7OhpeXFxwcHJqpQtuh0+lw+PBhjBw5kv/JWQGOh/XgWFgPww6c27dvw9XV1dLlWITV7AH08PCAh4dHvXKvXbuGIUOGYODAgYiNjb1n8wdIzZ2TkxPc3NwAAEFBQVi+fDm0Wq2xCTh8+DB8fHxqHRquy40bNwDAZLPo6+uLDz74AGVlZfVaFjWvwsJCPPXUU1b3c3dERNR8iouLbbYBtJo9gPWVm5uLkJAQdO7cGR9//DFUKuNp/OjQoQMAYN++fcjPz0dQUBDUajWSkpIQFRWFsLAw/OMf/wAg7b3z8/PD0KFDsXz5cly8eBFhYWF47bXXEBUVVa9abt++jbZt2+LKlSu1ViDuAWxeVVVV+OGHH9CvXz/ZOmEK9wA2LcMn66tXr971sD01D46H9eBYWA/DaUM+Pj712on0ILKaPYD1dfjwYVy6dAmXLl3CQw89JLvP0Mva29tj06ZNWLRoEfR6PR5++GGsXr0aL730kjHX1dUViYmJeOmllxAQEIC2bdti0aJFsnP87sWw0ri6utZ6M5eXl+PXX3+FSqW6Z0NC5lOf11ulUkGpVLbo8zNbAhcXF/4nZ0U4HtaDY2EdbHXPn0GLawDDwsIQFhZ215xRo0Zh1KhR91xW3759cezYMTNVRkRERNQy2OZ+TyIiIiIbxgawERwdHfH666/LLiZ9pxZ2imWLpVAo4OPjc89vAAMck6ZWn/cFNR+Oh/XgWJA1aXFfAmkpqqqqcOHCBXh6eta6RiBZ1o0bN1BQUIDu3bvz/EwiIrJJLe4cwJZCpVLBzc0NBQUFAABnZ+d67Z2ipiOEQGlpKQoKCuDm5sbmj4iIbBb3ADYhIQTy8/Nx+/ZtS5dCNbi5uaFDhw5syImIyGaxAWwGVVVV0g+yk8XZ29tzzx8REdk8NoBERERENobfAiYiIiKyMWwAiYiIiGwMG0AiIiIiG8MGkIiIiMjGtMjrAK5Zswa7du3CTz/9BLVajeDgYPzv//4v/Pz8jDlhYWH45z//KZsvMDAQp0+fNt6uqKjA4sWL8dlnn6GsrAzDhg3Dpk2b8NBDD9WrDr1ej9zcXLRp04aXFCEiImohhBAoLi6Gj48PlErb3BfWIr8FPGrUKEybNg2PPvooKisrsWLFCmRmZuLs2bNo1aoVAKkBvH79OmJjY43zOTg4oF27dsbb8+bNw759+xAXFwd3d3dERUXh5s2bSE1NrdelQnJyctCpUyfzP0EiIiJqclevXq33Tp8HTYtsAO/066+/wtPTE9988w0GDx4MQGoAb9++jT179picR6PRoH379vjkk08wdepUAEBubi46deqEgwcPIjQ09J6Pq9Fo4ObmhqysLFljSc1Pp9Ph8OHDGDlyJOzt7S1djk3jWFgXjof14FhYj6KiInTq1Am3b9+Gq6urpcuxiBZ5CPhOGo0GAGo1YUePHoWnpyfc3NwQEhKCt956C56engCA1NRU6HQ6jBw50pjv4+ODPn36IDk52WQDWFFRgYqKCuPt4uJiAICTkxPUarXZnxfVn52dHZydnaFWq7lhtTCOhXXheFgPjoX1MPw4gy2fvtXiG0AhBBYtWoQnnngCffr0McZHjx6NZ599Fr6+vsjKysLKlSsxdOhQpKamwtHREfn5+XBwcEDbtm1ly/Py8kJ+fr7Jx1qzZg1WrVpVK56UlARnZ2fzPjFqkMTEREuXQL/jWFgXjof14FhYXmlpqaVLsLgW3wBGRkbihx9+wIkTJ2Rxw2FdAOjTpw8CAgLg6+uLAwcO4Omnn65zeUKIOj8RLFu2DIsWLTLeNuxCHjJkCNzd3Rv5TKgxdDodEhMTMWLECH6ytjCOhXXheFgPjoX1KCoqsnQJFteiG8AFCxYgPj4ex44du+dJnN7e3vD19cXFixcBAB06dIBWq8WtW7dkewELCgoQHBxschmOjo5wdHSsFbe3t+eb2UpwLKwHx8K6cDysB8fC8vj6t9AGUAiBBQsWYPfu3Th69Ci6du16z3lu3LiBq1evwtvbGwAwcOBA2NvbIzExEVOmTAEA5OXl4ccff8S6devuryCdTpoAQKUClMrq2wCqqqqgk/4A9Prq+UzkAgDs7WvnKpVSvqlcvV7KN2eunR0ghOncykrpvrvlKhRSvLG5gBS/R65OCNipVCgvKUGVYb66lqtSSfdVVgIKBezt7KCys5NyTY2RIffO17Ix4/kgj73h7ztfsyYa+/vKNTWehtz6jP395ALWsZ4AUl7NuDWsJ828jbjreJp7PQFMj5Hhb1vfRphrPQEavo24c7xsUIu8+M1LL72ETz/9FNu3b0ebNm2Qn5+P/Px8lJWVAQBKSkqwePFinDp1CtnZ2Th69CjGjRsHDw8PTJo0CQDg6uqK8PBwREVF4ciRI0hLS8PMmTPRt29fDB8+/L7qsff2BhwcpOmzz6SgszOEoyPy5s7Fha+/RlZWFrIyM5F18mT19OOPUjw5uTp26pQU++9/TeeePl0dS042nZuZKcW//VYez8pC1tmz8tgPP0jxM2fk8Z9/RtZPP8lj6emmcy9fRtb586Zzv/tOHr90CVkXLshjaWlSbmqqPH7xojTVjKWmSrlpabL41Z9/hrenJ3JqPufvvpNy09Plyzh/Xqr55ElkHT+OC4cOIe+ttyCEAKKiqsfSwQH46iugtFQee/RRaYxfe00e37lTiteM9eolxdaulcc//liKu7lVx7p0kWIbNshzP/hAivv4VMfat5diMTHy3HffleLdulXHDOenfvaZPDc6Wor37y+PV1YC8fHy2NKlUm5QkDyu0QBHjshiyldfBQCoRo2S5+bmAqdOyWMREdJyJ0yQxy9dAjIy5LHp06Xc6dPl8YwMKb9mbMIEKTciQh4/dUqqo2bM8H5/+WV5/MgR6fnVjAUFSblLl8rj8fHS61Yz1r+/lBsdLY/X2EYYY926SbF335XnxsRI8fbtq2M+PlLsgw/kuRs2SPEuXapjbm4AgE5JSbBv1ao6vnatlNurl3wZgLQe14y99poUf/RReby0VHp/1IxFRUm5ISHyeGEhcOyYPDZvnpQ7erQ8/ssvQGqqPDZ7tpQ7ebI8fvasNNWMTZ4s5c6eLY+npkrLrhkbPVrKnTdPHj92TKq5ZiwkRMptxDZCsWsXAMjHwga3EXj5ZSl3+HB5vDm3EdOmwda1yMvA1HWOXmxsLMLCwlBWVoaJEyciLS0Nt2/fhre3N4YMGYLo6GjZdfvKy8vx17/+Fdu3b5ddCLq+1/YrKiqCq6srCvPyqs8BrPGpLe/6ddwuKoJn+/Zwbt0aCkD+qUShkKaan+QAaX4hLJtreI1N5ZpahoVz9QBKiovRunVrKA3z1WO5QgiUlpai4Ndf4da2Lbw9PVvOnh0r/XSvq6rCwUOHMGbkSNgbPnnXtVzuATRfbh3jqQNwcP9+jAkNrT7sZQXriS3uAdTp9TiYkIAxd54DaGPbCGvYA1hUXAxXd3doNBq4uLjAFrXYQ8B3o1arcejQoXsux8nJCRs2bMAGwyfnhrK3l6YaqpRK3C4uhqeXF78g0gz0ej20Wi2c1Or7vqq7ulUrQKlEQUEBPD09oTJ1boipmEolTZbMVSqlydy5CoXpXDsTm4y75d657PtZbl21NVWuNYxnU+TqdNJrbmI7ZTXrSWNy66rNGtcTQyNlaixsdRvRmNy6aqtPbl05NqRFHgJuCQzXGOLlYVoGwzjpTJ0/RURE9IBhA9jEbPkiky0Jx4mIiGwJG0AiIiIiG8MGkIiIiMjG8CxIajZhYWHo0KED/vvf/6KsrAz/+c9/auWcOnUKwcHBSE1NxYABAyxQJRER0YOPewCpWej1ehw4cAATJkxAeHg4vv76a/zyyy+18rZt24b+/fuz+SMiImpCbACploSEBKjValTWuGbSuXPnoFAoUFhY2KBlnjx5EkqlEoGBgRg7diw8PT0RFxcnyyktLcUXX3yB8PDwxpRPRERE98AGkGpJT09H7969YVfjOknp6eno2LEjPDw8GrTM+Ph4jBs3DkqlEnZ2dpg1axbi4uJk13T897//Da1Wiz//+c+Nfg5ERERUNzaAVEtGRgb6G37G6ndpaWnw9/dv8DLj4+MxwfDzXACef/5548/0GWzbtg1PP/002rZt2+DHISIionvjl0As7Lvsm1h36CcUl5vnh6nbONljSagfArq0a/Ay0tPTMX/+/FqxgICABi3v3LlzyMnJkf3Gco8ePRAcHIxt27ZhyJAhuHz5Mo4fP47Dhw83uG4iIiKqHzaAFrb1+M9Iybpl9mU2tAEsKyvDxYsXZXsA9Xo9vv/+e4SHh6OkpASTJ0/GtWvXAADr169HaGgoVq5ciS+//BIPP/wwhBCYP38+xo4dC0Da+zdixAio1WrZY4WHhyMyMhLvv/8+YmNj4evri2HDhjXsSRMREVG9sQG0sBcGPYxbpVqz7gF8YdDDDZ7/8uXLqKqqgp+fnzF26NAh3LhxA/7+/jh06BDc3d2RkJAAIQSKi4uRkpKChIQEZGRk4MaNG+jZs6dsD+LevXsRERFR67GmTJmChQsXYvv27fjnP/+JF154gb/IQURE1AzYAFpYQJd22DEn2NJlGLm7u0OhUCAlJQVjx47F6dOnERkZCbVajW7dukGpVOLVV1/FkiVLMGnSJAQFBSE5ORmTJk2Cg4MDvL29MXToUOPyCgoKcObMGezZs6fWY7Vu3RpTp07F8uXLodFoEBYW1nxPlIiIyIbxSyAk4+3tjejoaMyaNQudO3fGpk2b8Oyzz6J3795QqVTo3r070tLS0KdPHyxcuBAbN26EEKLOPXf79u1DYGAgPD09Td4fHh6OW7duYfjw4ejcuXNTPjUiIiL63T33AKanp9f6Rig92FasWIEVK1aYvC83Nxft2rXDrFmzoFKpkJSUhBdffBGRkZGIiorCzZs3kZSUhOeffx6AdPh3/PjxdT5WUFCQ7FIwRERE1PTu2QAOGDAAf/zjHxEREYEZM2bA1dW1OeoiK5WZmYnFixdDpVJBrVYjJiYGvXr1QmhoKPr16wc/Pz8MHjzYmP/EE09g+vTpFqyYiIiI7nTPQ8AnT57EgAEDsHTpUnh7e2PmzJlISkpqjtrICoWGhiIzMxPp6ek4deoUevXqBQCIjo7GTz/9hL1798qu47dkyRJ06tTJUuUSERGRCfdsAIOCgrB161bk5+dj8+bNxuu5/eEPf8Bbb72FnJyc5qiTiIiIiMyk3l8CUavVmD17No4ePYoLFy5g+vTp+PDDD9G1a1eMGTOmKWukFiYuLs54DUAiIiKyPg36FvAf/vAHLF26FCtWrICLiwsOHTpk7rqIiIiIqInc93UAv/nmG2zbtg07d+6ESqXClClTEB4e3hS1EREREVETqFcDePXqVcTFxSEuLg5ZWVkIDg7Ghg0bMGXKFLRq1aqpayQiIiIiM7pnAzhixAgkJSWhffv2mDVrFp5//nnZz4QRERERUctyzwZQrVZj586dGDt2LFQqVXPURERERERN6J5fAunduze8vb3Z/BERERE9IO7ZAObn52Ps2LHw9vbGiy++iAMHDqCioqI5aiMiIiKiJnDPBjA2NhbXr1/Hjh074ObmhqioKHh4eODpp59GXFwcCgsLm6POJrNp0yZ07doVTk5OGDhwII4fP27pkoiIiIiaVL2uA6hQKDBo0CCsW7cOP/30E1JSUvDYY49h69at6NixIwYPHoz169fj2rVrTV2vWX3xxRd45ZVXsGLFCqSlpWHQoEEYPXo0rly5YunSiIiIiJpMgy4E3bNnTyxZsgQnT55ETk4OZs+ejePHj+Ozzz4zd31N6t1330V4eDgiIiLQs2dP/P3vf0enTp2wefNmS5dGRERE1GQa1ADW1L59e4SHh2Pv3r1YvHixOWpqFlqtFqmpqRg5cqQsPnLkSCQnJ1uoKmqIsLAwjBw5EhMnTjR5/6lTp6BQKPD99983b2FERERW6r5/CcTg4MGDd73f2n8fuLCwEFVVVfDy8pLFvby8kJ+fb3KeiooK2RdgioqKAAA6nQ46nU6Wq9PpIISAXq+HXq83c/VkoNfrceDAAbzyyitYuXIlsrOz0aVLF1lOTEwM+vfvj/79+9c5Fnq9HkII6HQ6fuO9kQzvhTvfE2QZHA/rwbGwHhyDRjSA77//PpKTkzFs2DAIIZCUlISQkBC4ublBoVBYfQNooFAoZLeFELViBmvWrMGqVatqxZOSkuDs7CyL2dnZoUOHDigpKYFWqzVfwc3gP//5D5577jlcvXoVdnbSKnL+/Hk89thjuHTpEtzd3S1cYbXk5GQoFAq88MIL+L//+z9s3boV/+///T/j/aWlpdixYwf+9re/GRt2U7RaLcrKynDs2DFUVlY2R+kPvMTEREuXQDVwPKwHx8LySktLLV2CxTW4AVQqlTh37hw6dOgAQLpczLx58xAbG2u24pqSh4cHVCpVrb19BQUFtfYKGixbtgyLFi0y3i4qKkKnTp0wZMiQWk1ReXk5rl69itatW8PJycn8T6AJXbx4Eb1790a7du2MsUuXLqFjx47o2rWrBSur7euvv8a4cePg7u6OqVOn4vPPP8ebb75pbOJ3794NrVaL8PBwuLi41Lmc8vJyqNVqDB48uMWNl7XR6XRITEzEiBEjYG9vb+lybB7Hw3pwLKzH3XYI2IoGN4CXL19G+/btjbfd3d1x/vx5sxTVHBwcHDBw4EAkJiZi0qRJxnhiYiImTJhgch5HR0c4OjrWitvb29d6M1dVVUGhUECpVEKpbPSpls3qhx9+QP/+/WV1Z2RkwN/f3+qey759+7B+/XooFArMnDkTGzZswLFjxzBkyBAAQFxcHJ5++ul77rVUKpVQKBQmx5Iahq+ldeF4WA+OheXx9W9EA/jMM88gODjY2Dzt3r0bkydPNlthzWHRokV47rnnEBAQgKCgIGzZsgVXrlzB3Llzm7eQqiqg5rlpKhWgVAJ3nqNgb3/vXKVSijVCeno65s+fXysWEBDQqOWa27lz55CTk4Phw4cDALp3747g4GBs27YNQ4YMweXLl3H8+HEcPnzYwpUSERFZlwbvzomOjsbGjRuhVqvh5OSE999/H6tXrzZnbU1u6tSp+Pvf/47Vq1ejf//+OHbsGA4ePAhfX9/mLSQ6GnBwqJ4Ml9Nxdq6Odesmxd59V54bEyPF27eXbkdHN6qUsrIyXLx4Ef379zfG9Ho9vv/+e/j7+6OkpASjRo1C37590bdvXxw6dAgAsHLlSvTs2RNPPfUUxowZg/379wMAsrOz4e/vj7CwMPTq1Qvz5s3Dnj17EBgYiN69e+PixYvGxxk3bhwGDhyIPn36YNeuXQCkc/wCAwNRVVWF69evo3v37igoKAAAxMfHY8SIEVCr1cZl/OUvf8HOnTtRVFSE2NhY+Pr6YtiwYY16TYiIiB44ooEOHz4siouLhRBCbNy4Ubzwwgvi3LlzDV1ci6TRaAQAUVhYWOu+srIycfbsWVFWVnbvBVVWCqHVVk9VVVK8ZkyrrV9uZWWjnlNmZqYAIK5fv26MHTx4UAAQ586dE19++aWYMWOGEEIIvV4vNBqN+Pbbb0VAQICoqKgQubm5wtXVVezbt08IIURWVpawt7cXP/30k6isrBQ9evQQixcvFkIIsXnzZvHyyy8bH+fGjRtCCCFu374t/Pz8hF6vF0II8eqrr4o1a9aIiRMnik8//dSYHxQUJGJiYoQQQlRVVYlbt24JjUYjWrduLTZv3iweeughsWrVqno97/saL7orrVYr9uzZI7SGdZYsiuNhPTgW1sPw/7dGo7F0KRbT4D2AixcvRuvWrXH69Gl88sknePLJJxEeHm6uvtS2qFTS4V3DZDjPrmbMcL7CvXIbefjX3d0dCoUCKSkpAIDTp08jMjISarUa3bp1Q9++fXH8+HEsWbIEp0+fhouLC5KTkzFp0iQ4ODjA29sbQ4cOlS3Tz88Pfn5+UKlU6Nmzp/GQbb9+/ZCdnW3Me++99+Dv74/BgwfjypUrxi/ovPXWW4iJiUFlZSX+/Oc/A5C+rHPmzBmMHTtW9litW7fG1KlTsXz5cuTm5iIsLKxRrwcREdGDqNFn9O/ZsweRkZGYMWMGv1b9APD29kZ0dDRmzZqFzp07Y9OmTXj22WfRu3dvqFQqdO/eHWlpaejTpw8WLlyIjRs33vXSOQBkX5xRKpXG20qlElVVVQCkS+mcPHkSp0+fRkZGBjp37my85mJBQQEqKyuN124EpC9/BAYGwtPTs9bjhYeH49atWxg+fDg6d+5stteGiIjoQdHgBtDHxwfPPfccPv/8czz11FOoqKgw/udMLduKFStw8+ZNXLlyBR9//DHWrl2LM2fOAAByc3PRqlUrzJo1CwsXLkR6ejoef/xx4+VW8vPzkZSUdN+PWVRUBHd3d6jVaqSkpODChQvG+1544QVs3LgRAwcOxHvvvQcA2Lt3L8aPH29yWUFBQRBCGM9PJCIiIrl6fwv45s2bsuvCffnllzh06BBee+01tG3bFnl5eVi/fn2TFEnWIzMzE4sXL4ZKpYJarUZMTAx69eqF0NBQ9OvXD35+fhg8ePB9Lzc0NBTvv/8++vfvD39/f/Tt2xcA8NFHH8HLywtPPfUUQkJC8Kc//QkTJkzAE088genTp5v76REREdkEhRBC1CdRqVTioYcegr+/v2zq1q3bXQ//PciKiorg6uqKwsJCkxeCzsrKQteuXW3uwsJhYWGYPHlyrfPzmpJer0dRURFcXFwadK1CWx4vc9PpdDh48CDGjBnDa21ZAY6H9eBYWA/D/98ajeauPxLwIKv3HsCzZ88iPT0daWlpOHPmDD788EPcvHkTarUavXv3xrffftuUdRIRERGRmdS7AezRowd69OiBadOmAZB+MzchIQELFizgddZIJi4uztIlEBER0V00+EsgCoUCo0ePxqefforc3Fxz1kRERERETajeDaC+5s+P1fDYY4/h6NGj5qqHiIiIiJpYvQ8Bt27dGn369DF+S7N///7w8/NDSkoKSkpKmrJGIiIiIjKjejeAu3btQkZGBjIyMvD+++/j4sWL0Ov1UCgUiG7k788SERERUfOpdwM4atQojBo1yni7vLwcly9fhru7Ozp06NAkxRERERGR+dW7AbyTk5MTevfubc5aHkj1vMwiWRjHiYiIbEmjfwuYTDNc5JO/j9wyGMaJF2clIiJb0OA9gHR3KpUKbm5uKCgoAAA4Ozvb7C+mNAe9Xg+tVovy8vL7+iUQIQRKS0tRUFAANzc3qFSqJqySiIjIOrABbEKGcyMNTSA1HSEEysrKoFarG9Rou7m58VxWIiKyGWwAm5BCoYC3tzc8PT2h0+ksXc4DTafT4dixYxg8ePB9H8a1t7fnnj8iIrIpbACbgUqlYoPRxFQqFSorK+Hk5MTz+IiIiO6BXwIhIiIisjFsAImIiIhsDBtAIiIiIhvDBpCIiIjIxrABJCIiIrIxbACJiIiIbAwbQCIiIiIbwwaQiIiIyMa0uAtBZ2dnIzo6Gl9//TXy8/Ph4+ODmTNnYsWKFXBwcDDmmfo5sM2bN2Pu3LnG25mZmYiMjERKSgratWuHOXPmYOXKlff/U2I6nTQBgEoFKJXVtw3s7YGqKkCvr47dT65SKeWbytXrpXxz5trZAUKYzq2slO67W65CIcUbmwtI8Xvl6vXS7ZrPo65clUq6z9RyTY3RnblA48fzQR57w993vmZNNfb3k9vYsW+J6wkg5dWMW8N60tzbiLuNZ3NtIwx/2/o2wlzrCdDwbcSd42WLRAvz1VdfibCwMHHo0CFx+fJlsXfvXuHp6SmioqJkeQBEbGysyMvLM06lpaXG+zUajfDy8hLTpk0TmZmZYufOnaJNmzZi/fr19a5Fo9EIAEIjrWrS9Omn0p12dtUxX18ptm5ddQwQYssWKe7qWh3z8JBiGzfKc997T4r7+FTHnJ2lWGysPPfNN6X4I4/I40IIsWOHPLZ8uRT395fHS0qE2L9fHlu4UMoNDpbHCwqESEqSxyIipNzhw+XxrCwhUlLksRkzpNwJE+TxH3+UppqxCROk3BkzZHFdcrI49OGH8tzhw6XciAh5PClJqrlmLDhYyl24UB7fv196LWrG/P2l3OXL5fEdOwwrX/X0yCNS7M035fHYWCnu7Fwd8/GRYu+9J8/duFGKe3hUx1xdpdiWLfLcdeukuK9vdczOTop9+qk89/XXpXivXvK4VivE7t3y2OLFUm5AgDx+65YQhw/LYpVz54o9e/aIqsGD5bk5OUKcOCGPzZ4tLXfMGHn8wgUh0tLkscmTpdzJk+XxtDQpv2ZszBgpd/ZsefzECamOmrGQECl3/nx5/PBh6fnVjAUESLmLF8vju3dLr1vNWK9eUu7rr8vjzbyN0Gq1InXBAnmuDW4jREqKtOyasWbeRui2bxd79uyR59rgNkLMny/lhoTI4824jdCMGCEACI1GI2yVQgghLNyDNtrbb7+NzZs34+effzbGFAoFdu/ejYkTJ5qcZ/PmzVi2bBmuX78OR0dHAMDatWuxYcMG5OTk1GsvYFFREVxdXVGYlwd3d3cpyL1Akmb+dK/T63Hwq68wZuTI6p+C4x7AhuU2cux1VVU4eOiQNBaGMaxrudwDaL7cOsZTB+Dg/v0YExpa/d6wgvXEFvcA6vR6HExIwJgRI+Q/WWlj2whr2ANYVFwMV3d3aDQauLi4wBa1uEPApmg0GrRr165WPDIyEhEREejatSvCw8Px4osvQqmUTns8deoUQkJCjM0fAISGhmLZsmXIzs5G165d61+Avb003Rm7k0olTabmb0yuUilN5s5VKEzn2plYbZoqt67a7szV6aTlmhqL+1luU42RrY79nctuirE3V641jGdT5Op00mte3+3Ug7qNMFduY8bI0EiZGgtb3UY0Jreu2uqTW1eODWnxr8Dly5exYcMGvPPOO7J4dHQ0hg0bBrVajSNHjiAqKgqFhYX429/+BgDIz89Hly5dZPN4eXkZ7zPVAFZUVKCiosJ4u6ioCACg0+mgM3XeDTUbw+vPcbA8joV14XhYD46F9eAYWFED+MYbb2DVqlV3zTlz5gwCAgKMt3NzczFq1Cg8++yziIiIkOUaGj0A6N+/PwBg9erVsvidh3kNR8PrOvy7Zs0akzUmJSXB2dn5rrVT80hMTLR0CfQ7joV14XhYD46F5ZWWllq6BIuzmnMACwsLUVhYeNecLl26wMnJCYDU/A0ZMgSBgYGIi4szHtqty8mTJ/HEE08gPz8fXl5emDVrFjQaDfbu3WvMSUtLw4ABA/Dzzz/Xew9gp06dkFfzHECyCJ1Oh8TERIy489waanYcC+vC8bAeHAvrUVRUBA8PD54DaA08PDzg4eFRr9xr165hyJAhGDhwIGJjY+/Z/AFSc+fk5AQ3NzcAQFBQEJYvXw6tVmu8fMzhw4fh4+NT69CwgaOjo+ycQUPvXF5ejrKysnrVTk1Dp9OhtLQUZWVlqOTX+y2KY2FdOB7Wg2NhPQz/Z1vJPjDLsOA3kBvk2rVr4pFHHhFDhw4VOTk5ssu8GMTHx4stW7aIzMxMcenSJbF161bh4uIiXn75ZWPO7du3hZeXl5g+fbrIzMwUu3btEi4uLvd1GZjLly8LAJw4ceLEiROnFjhdvXrVrD1KS2I1h4DrKy4uDn/5y19M3md4KgkJCVi2bBkuXboEvV6Phx9+GBEREXjppZdgV+ObP5mZmXjppZeQkpKCtm3bYu7cuXjttdfqfSHo27dvo23btrhy5QpcXV0b/+SowQyH469evWqzu/OtBcfCunA8rAfHwnoIIVBcXAwfH596HUV8ELW4BtCaGK4DaMvnEFgLjoX14FhYF46H9eBYkDWxzbaXiIiIyIaxASQiIiKyMWwAG8HR0RGvv/667JvBZBkcC+vBsbAuHA/rwbEga8JzAImIiIhsDPcAEhEREdkYNoBERERENoYNIBEREZGNYQNIREREZGPYABIRERHZGDaARERERDaGDSARERGRjWmRDeCaNWvw6KOPok2bNvD09MTEiRNx/vx5WU5YWBgUCoVseuyxx2Q5FRUVWLBgATw8PNCqVSuMHz8eOTk5zflUiIiIiJpdi7wQ9KhRozBt2jQ8+uijqKysxIoVK5CZmYmzZ8+iVatWAKQG8Pr164iNjTXO5+DggHbt2hlvz5s3D/v27UNcXBzc3d0RFRWFmzdvIjU1FSqV6p516PV65Obmok2bNlAoFOZ/okRERGR2QggUFxfDx8cHSmWL3BfWeOIBUFBQIACIb775xhibPXu2mDBhQp3z3L59W9jb24vPP//cGLt27ZpQKpUiISGhXo979epVAYATJ06cOHHi1AKnq1evNrj3aOns8ADQaDQAINu7BwBHjx6Fp6cn3NzcEBISgrfeeguenp4AgNTUVOh0OowcOdKY7+Pjgz59+iA5ORmhoaH3fNw2bdoAALKysmo9NjUvnU6Hw4cPY+TIkbC3t7d0OTaNY2FdOB7Wg2NhPYqKitCpUyfj/+O2qMU3gEIILFq0CE888QT69OljjI8ePRrPPvssfH19kZWVhZUrV2Lo0KFITU2Fo6Mj8vPz4eDggLZt28qW5+Xlhfz8fJOPVVFRgYqKCuPt4uJiAICTkxPUanUTPDuqLzs7Ozg7O0OtVnPDamEcC+vC8bAeHAvrodPpAMCmT99q8Q1gZGQkfvjhB5w4cUIWnzp1qvHvPn36ICAgAL6+vjhw4ACefvrpOpcnhKhzhVizZg1WrVpVK56UlARnZ+cGPgMyp8TEREuXQL/jWFgXjof14FhYXmlpqaVLsLgW3QAuWLAA8fHxOHbsGB566KG75np7e8PX1xcXL14EAHTo0AFarRa3bt2S7QUsKChAcHCwyWUsW7YMixYtMt427EIeMmQI3N3dzfCMqKF0Oh0SExMxYsQIfrK2MI6FdeF4WA+OhfUoKiqydAkW1yIbQCEEFixYgN27d+Po0aPo2rXrPee5ceMGrl69Cm9vbwDAwIEDYW9vj8TEREyZMgUAkJeXhx9//BHr1q0zuQxHR0c4OjrWitvb2/PNbCU4FtaDY2FdOB7Wg2NheXz9W2gD+NJLL2H79u3Yu3cv2rRpYzxnz9XVFWq1GiUlJXjjjTfwzDPPwNvbG9nZ2Vi+fDk8PDwwadIkY254eDiioqLg7u6Odu3aYfHixejbty+GDx9+fwXpdNIEACoVoFRW3wZQVVUFnfQHoNdXz2ciFwBgb187V6mU8k3l6vVSvjlz7ewAIUznVlZK990tV6GQ4o3NBaT4PXJ1QsBOpUJ5SQmqDPPVtVyVSrqvshJQKGBvZweVnZ2Ua2qMDLl3vpaNGc8HeewNf9/5mjXR2N9XrqnxNOTWZ+zvJxewjvUEkPJqxq1hPWnmbcRdx9Pc6wlgeowMf9v6NsJc6wnQ8G3EneNlg1rkxW82b94MjUaDJ598Et7e3sbpiy++AACoVCpkZmZiwoQJ6N69O2bPno3u3bvj1KlTsm/8vPfee5g4cSKmTJmCxx9/HM7Ozti3b1+9rgFYk723N+DgIE2ffSYFnZ0hHB2RN3cuLnz9NbKyspCVmYmskyerpx9/lOLJydWxU6ek2H//azr39OnqWHKy6dzMTCn+7bfyeFYWss6elcd++EGKnzkjj//8M7J++kkeS083nXv5MrLOnzed+9138vilS8i6cEEeS0uTclNT5fGLF6WpZiw1VcpNS5PFr/78M7w9PZFT8zl/952Um54uX8b581LNJ08i6/hxXDh0CHlvvQUhBBAVVT2WDg7AV18BpaXy2KOPSmP82mvy+M6dUrxmrFcvKbZ2rTz+8cdS3M2tOtalixTbsEGe+8EHUtzHpzrWvr0Ui4mR5777rhTv1q06Zjg/9bPP5LnR0VK8f395vLISiI+Xx5YulXKDguRxjQY4ckQWU776qvQ+HDVKnpubC5w6JY9FREjLnTBBHr90CcjIkMemT5dyp0+XxzMypPyasQkTpNyICHn81Cmpjpoxwwe+l1+Wx48ckZ5fzVhQkJS7dKk8Hh8vvW41Y/37S7nR0fJ4jW2EMdatmxR79115bkyMFG/fvjrm4yPFPvhAnrthgxTv0qU65uYGAOiUlAT7Vq2q42vXSrm9esmXAUjrcc3Ya69J8UcflcdLS6X3R81YVJSUGxIijxcWAseOyWPz5km5o0fL47/8AqSmymOzZ0u5kyfL42fPSlPN2OTJUu7s2fJ4aqq07Jqx0aOl3Hnz5PFjx6Saa8ZCQqTcRmwjFLt2AYB8LGxwG4GXX5Zyhw+Xx5tzGzFtGmxdi7wQtLUoKiqCq6srCvPyqs8BrPGpLe/6ddwuKoJn+/Zwbt0aCkD+qUShkKaan+QAaX4hLJtr+CKMqVxTy7Bwrh5ASXExWrduDaVhvnosVwiB0tJSFPz6K9zatoW3p2fL2bNjpZ/udVVVOHjoEMaMHAl7wyfvupbLPYDmy61jPHUADu7fjzGhodWHvaxgPbHFPYA6vR4HExIw5s5zAG1sG2ENewCLiovh6u4OjUYDFxcX2KIWeQjY6tjbS1MNVUolbhcXw9PLi18QaQZ6vR5arRZOavV9X9Vd3aoVoFSioKAAnp6eUJk6N8RUTKWSJkvmKpXSZO5chcJ0rp2JTcbdcu9c9v0st67amirXGsazKXJ1Ouk1N7Gdspr1pDG5ddVmjeuJoZEyNRa2uo1oTG5dtdUnt64cG9IiDwG3BIZrDPHyMC2DYZx0ps6fIiIiesCwAWxitnyRyZaE40RERLaEDSARERGRjWEDSERERGRjeBYkNZuwsDB06NAB//3vf1FWVob//Oc/tXJOnTqF4OBgpKamYsCAARaokoiI6MHHPYDULPR6PQ4cOIAJEyYgPDwcX3/9NX755Zdaedu2bUP//v3Z/BERETUhNoBUS0JCAtRqNSprXDPp3LlzUCgUKCwsbNAyT548CaVSicDAQIwdOxaenp6Ii4uT5ZSWluKLL75AeHh4Y8onIiKie2ADSLWkp6ejd+/esKtxnaT09HR07NgRHh4eDVpmfHw8xo0bB6VSCTs7O8yaNQtxcXGoeR3yf//739Bqtfjzn//c6OdAREREdeM5gBb2XfZNrDv0E4rLzfO7hG2c7LEk1A8BXdo1eBkZGRnob/gZq9+lpaXB39+/XvOHhYVh8uTJGDt2rDEWHx+P9evXG28///zzePvtt3H06FEMGTIEgHT49+mnn0bbtm0bXDsRERHdGxtAC9t6/GekZN0y+zIb0wCmp6dj/vz5tWIBAQENWt65c+eQk5OD4YbfXAXQo0cPBAcHY9u2bRgyZAguX76M48eP4/Dhww2um4iIiOqHDaCFvTDoYdwq1Zp1D+ALgx5u8PxlZWW4ePGibA+gXq/H999/j/DwcJSUlGDy5Mm4du0aAGD9+vUIDQ3FypUr8eWXX+Lhhx/GnT8vHR8fjxEjRkCtVsvi4eHhiIyMxPvvv4/Y2Fj4+vpi2LBhDa6diIiI6ocNoIUFdGmHHXOCLV2G0eXLl1FVVQU/Pz9j7NChQ7hx4wb8/f1x6NAhuLu7IyEhAUIIFBcXIyUlBQkJCcjIyMCNGzfQs2dP2R7EvXv3IiIiotZjTZkyBQsXLsT27dvxz3/+Ey+88AJ/kYOIiKgZ8EsgJOPu7g6FQoGUlBQAwOnTpxEZGQm1Wo1u3bqhb9++OH78OJYsWYLTp0/DxcUFycnJmDRpEhwcHODt7Y2hQ4cal1dQUIAzZ87Izgc0aN26NaZOnYrly5cjNzcXYWFhzfU0iYiIbFq9G8D09PQmLIOshbe3N6KjozFr1ix07twZmzZtwrPPPovevXtDpVKhe/fuSEtLQ58+fbBw4UJs3LgRQog699zt27cPgYGB8PT0NHl/eHg4bt26heHDh6Nz585N+dSIiIjod/VuAAcMGICBAwdi8+bN0Gg0TVkTWdiKFStw8+ZNXLlyBR9//DHWrl2LM2fOAAByc3PRqlUrzJo1CwsXLkR6ejoef/xx7N69G1qtFvn5+UhKSjIua+/evRg/fnydjxUUFAQhBA4dOtTkz4uIiIgk9W4AT548iQEDBmDp0qXw9vbGzJkzZf/Rk23IzMzEo48+iv79+2Pjxo1YtGgR/vSnPyE0NBT9+vXDnDlzMHjwYGP+E088genTp1uwYiIiIrpTvb8EEhQUhKCgIPzf//0fduzYgdjYWAwfPhxdunTB888/j9mzZ+Ohhx5qylrJCoSGhiI0NLRWPDo6GtHR0bXiS5YsaY6yiIiI6D7c95dA1Go1Zs+ejaNHj+LChQuYPn06PvzwQ3Tt2hVjxoxpihqJiIiIyIwa9S3gP/zhD1i6dClWrFgBFxcXnsdFRERE1AI0+DqA33zzDbZt24adO3dCpVJhypQpCA8PN2dtRERERNQE7qsBvHr1KuLi4hAXF4esrCwEBwdjw4YNmDJlClq1atVUNRIRERGRGdW7ARwxYgSSkpLQvn17zJo1C88//7zs1yKIiIiIqGWodwOoVquxc+dOjB07FiqVqilrIiIiIqImVO8vgfTu3Rve3t5s/oiIiIhauHo3gPn5+Rg7diy8vb3x4osv4sCBA6ioqGjK2oiIiIioCdS7AYyNjcX169exY8cOuLm5ISoqCh4eHnj66acRFxeHwsLCpqyTiIiIiMzkvq4DqFAoMGjQIKxbtw4//fQTUlJS8Nhjj2Hr1q3o2LEjBg8ejPXr1+PatWtNVa/Zbdq0CV27doWTkxMGDhyI48ePW7okIiIioibVqAtB9+zZE0uWLMHJkyeRk5OD2bNn4/jx4/jss8/MVV+T+uKLL/DKK69gxYoVSEtLw6BBgzB69GhcuXLF0qXRfQgLC8PIkSMxceJEk/efOnUKCoUC33//ffMWRkREZKUa1QDW1L59e4SHh2Pv3r1YvHixuRbbpN59912Eh4cjIiICPXv2xN///nd06tQJmzdvtnRpVE96vR4HDhzA0KFDcezYMfzyyy+1crZt24b+/ftjwIABFqiQiIjI+jT4l0AOHjx41/ut/XeBtVotUlNTsXTpUll85MiRSE5ONjlPRUWF7IsvRUVFAACdTgedTifL1el0EEJAr9dDr9ebufqmlZCQgGeeeQYajQZ2dtIqcu7cOfTp0wfXr1+Hh4eHhSusdvz4cSiVSrz66qv4xz/+gbi4OLz++uvG+0tLS/HFF1/grbfeuus46PV6CCGg0+n4TfdGMrwX7nxPkGVwPKwHx8J6cAwa0QD++9//BgAUFBQgOTkZw4YNgxACSUlJCAkJsfoGsLCwEFVVVfDy8pLFvby8kJ+fb3KeNWvWYNWqVbXiSUlJcHZ2lsXs7OzQoUMHlJSUQKvVmq/wZvDtt9+iR48eKC0tNcZOnToFHx8fODg4GBtfa/Dll18iNDQUFRUVmDp1KuLi4vDKK69AoVAAAD777DNotVqMGzfurnVrtVqUlZXh2LFjqKysbK7yH2iJiYmWLoFq4HhYD46F5dX8/81WNbgBjI2NBQCMGzcO586dQ4cOHQBIl4uZN2+eeaprBoZGwUAIUStmsGzZMixatMh4u6ioCJ06dcKQIUPg7u4uyy0vL8fVq1fRunVrODk5mb/wJnT+/HkMGDAALi4uspi/v78sVpe//OUveOaZZzB27NimLBMAcPjwYaxbtw5t2rTBzJkzsWHDBnz//fcYMmQIAODzzz/HpEmT0Llz57sup7y8HGq1GoMHD25x42VtdDodEhMTMWLECNjb21u6HJvH8bAeHAvrYU07MiylwQ2gweXLl9G+fXvjbXd3d5w/f76xi21yHh4eUKlUtfb2FRQU1NoraODo6AhHR8dacXt7+1pv5qqqKigUCiiVSiiV9zjVsqoKqHl4UqUClErgzl3U9vb3zlUqpVgjZGRkYP78+bK6MzIyEBAQcO/nAtT/eTfSuXPnkJOTg5EjR0KhUKB79+4IDg5GXFwchg0bhsuXL+P48eM4fPjwPWtRKpVQKBQmx5Iahq+ldeF4WA+OheXx9TfDl0CeeeYZBAcHY+3atVi7di2eeOIJTJ482Ry1NSkHBwcMHDiw1q74xMREBAcHN28x0dGAg0P1ZPgWtbNzdaxbNyn27rvy3JgYKd6+vXQ7OrpRpZSVleHixYvo37+/MabX6/H999/D398fJSUlGDVqFPr27Yu+ffvi0KFDAICVK1eiZ8+eeOqpp1BQUGCcNzs7G/7+/ggLC0OvXr0wb9487NmzB4GBgejduzcuXrxozB03bhwGDhyIPn36YNeuXQCA5ORkBAYGoqqqCtevX0f37t2Ny4+Pj8eIESOgVquNy/jLX/6CnTt3oqioCLGxsfD19cWwYcMa9ZoQERE9aBq9BzA6Ohrjx49HcnIyhBB4//33ERAQYI7amtyiRYvw3HPPISAgAEFBQdiyZQuuXLmCuXPnNm8hK1cCK1ZU3zbswTN1jsKiRcArr9TO/fVX6d9G7nW7fPkyqqqq4OfnZ4wdOnQIN27cgL+/Pw4dOgR3d3ckJCRACIHi4mKkpKQgISEBGRkZuHHjBnr27In58+cb5z937hx27NiBRx55BH369EHr1q3x7bff4oMPPsDGjRvxj3/8AwDwz3/+E+3atYNGo0FgYCAmTZqE4OBgPP7443j77bfx7bff4vXXX4enpycAYO/evYiIiJDVP2XKFLz66qvYvn07/vnPf+KFF16o85A+ERGRrWr0HsDExET07NkTCxcuhL29PbZs2YKffvrJHLU1ualTp+Lvf/87Vq9ejf79++PYsWM4ePAgfH19m7cQlUo6vGuYDE1czZhhd/W9cht5+Nfd3R0KhQIpKSkAgNOnTyMyMhJqtRrdunVD3759cfz4cSxZsgSnT5+Gi4sLkpOTMWnSJDg4OMDb2xtDhw6VLdPPzw9+fn5QqVTo2bMnhg8fDgDo168fsrOzjXnvvfce/P39MXjwYFy5csV4eP6tt95CTEwMKisr8ec//xmAdKj+zJkztc4zbN26NaZOnYrly5cjNzcXYWFhjXo9iIiIHkSNbgAXL16M1q1b4/Tp0/jkk0/w5JNPIjw83By1NYv58+cjOzsbFRUVSE1NxeDBgy1dkkV5e3sjOjoas2bNQufOnbFp0yY8++yz6N27N1QqFbp37460tDT06dMHCxcuxMaNG+/6xRkAsvMmlUql8bZSqURVVRUA6ZvUJ0+exOnTp5GRkYHOnTsbL7lTUFCAyspK4ze3AWDfvn0IDAw07g2sKTw8HLdu3cLw4cPv+eUPIiIiW2S2s/T37NmDyMhIzJgxg1+vbuFWrFiBmzdv4sqVK/j444+xdu1anDlzBgCQm5uLVq1aYdasWVi4cCHS09Px+OOPY/fu3dBqtcjPz0dSUtJ9P2ZRURHc3d2hVquRkpKCCxcuGO974YUXsHHjRgwcOBDvvfceAOnw7/jx400uKygoCEII4/mJREREJNfocwB9fHzw3HPP4fjx40hLS0NFRYVxLw09eDIzM7F48WKoVCqo1WrExMSgV69eCA0NRb9+/eDn59egvaihoaF4//330b9/f/j7+6Nv374AgI8++gheXl546qmnEBISgj/96U+YMGECnnjiCUyfPt3cT4+IiMgmKIQQ4n5muHnzJtq1a2e8/dtvv+HQoUPo27cvunXrhry8PGRmZmLkyJFmL9baFBUVwdXVFYWFhSavA5iVlYWuXbvyunLNQK/Xo6ioCC4uLg26/AzHy3x0Oh0OHjyIMWPG8FILVoDjYT04FtbD8P+3RqOp1/VtH0T3vQfQw8MDDz30EPz9/WXTI488AkA6h8zb29vshRIRERGRedx3A3j27Fmkp6cjLS0NZ86cwYcffoibN29CrVajd+/e+Pbbb5uiTiIiIiIyk/tuAHv06IEePXpg2rRpAKSfTktISMCCBQt4wV0iIiKiFqDR3wJWKBQYPXo0Pv30U+Tm5pqjJiIiIiJqQvfdAOpr/g5tDY899hiOHj3a2HqIiIiIqInd9yHg1q1bo0+fPsbLdfTv3x9+fn5ISUlBSUlJU9TYot3nl6zJQjhORERkS+67Ady1axcyMjKQkZGB999/HxcvXoRer4dCoUB0dHRT1NgiGb7iX1paCrVabeFq6F4MFy/npRmIiMgW3HcDOGrUKIwaNcp4u7y8HJcvX4a7uzs6dOhg1uJaMpVKBTc3NxQUFAAAnJ2d7/pzadQ4er0eWq0W5eXl93UdQCEESktLUVBQADc3N6ga+VvKRERELUGjfwnEyckJvXv3NkctDxxDQ2xoAqnpCCFQVlYGtVrdoEbbzc2NH2CIiMhmNLoBpLopFAp4e3vD09MTOp3O0uU80HQ6HY4dO4bBgwff92Fce3t77vkjIiKbwgawGahUKjYYTUylUqGyshJOTk48j4+IiOgeGn0dQCIiIiJqWdgAEhEREdkYNoBERERENoYNIBEREZGNYQNIREREZGPYABIRERHZGDaARERERDaGDSARERGRjWEDSERERGRj2AASERER2Rg2gEREREQ2hg0gERERkY2xs3QB9ys7OxvR0dH4+uuvkZ+fDx8fH8ycORMrVqyAg4ODMU+hUNSad/PmzZg7d67xdmZmJiIjI5GSkoJ27dphzpw5WLlypcl570qnkyYAUKkApbL6toG9PVBVBej11bH7yVUqpXxTuXq9lG/OXDs7QAjTuZWV0n13y1UopHhjcwEpfq9cvV66XfN51JWrUkn3mVquqTG6Mxdo/Hg+yGNv+PvO16ypxv5+chs79i1xPQGkvJpxa1hPmnsbcbfxbK5thOFvW99GmGs9ARq+jbhzvGyRaGG++uorERYWJg4dOiQuX74s9u7dKzw9PUVUVJQsD4CIjY0VeXl5xqm0tNR4v0ajEV5eXmLatGkiMzNT7Ny5U7Rp00asX7++3rVoNBoBQGikVU2aPv1UutPOrjrm6yvF1q2rjgFCbNkixV1dq2MeHlJs40Z57nvvSXEfn+qYs7MUi42V5775phR/5BF5XAghduyQx5Yvl+L+/vJ4SYkQ+/fLYwsXSrnBwfJ4QYEQSUnyWESElDt8uDyelSVESoo8NmOGlDthgjz+44/SVDM2YYKUO2OGLK5LThaHPvxQnjt8uJQbESGPJyVJNdeMBQdLuQsXyuP790uvRc2Yv7+Uu3y5PL5jh2Hlq54eeUSKvfmmPB4bK8WdnatjPj5S7L335LkbN0pxD4/qmKurFNuyRZ67bp0U9/WtjtnZSbFPP5Xnvv66FO/VSx7XaoXYvVseW7xYyg0IkMdv3RLi8GFZrHLuXLFnzx5RNXiwPDcnR4gTJ+Sx2bOl5Y4ZI49fuCBEWpo8NnmylDt5sjyelibl14yNGSPlzp4tj584IdVRMxYSIuXOny+PHz4sPb+asYAAKXfxYnl8927pdasZ69VLyn39dXm8mbcRWq1WpC5YIM+1wW2ESEmRll0z1szbCN327WLPnj3yXBvcRoj586XckBB5vBm3EZoRIwQAodFohK1SCCGEhXvQRnv77bexefNm/Pzzz8aYQqHA7t27MXHiRJPzbN68GcuWLcP169fh6OgIAFi7di02bNiAnJyceu0FLCoqgqurKwrz8uDu7i4FuRdI0syf7nV6PQ5+9RXGjBwJe3v7uy/3QdmzY6Vjr6uqwsFDh6SxMIxhXcvlHkDz5dYxnjoAB/fvx5jQ0Or3hhWsJ7a4B1Cn1+NgQgLGjBhRPRZ15D7I2whr2ANYVFwMV3d3aDQauLi4wBa1uEPApmg0GrRr165WPDIyEhEREejatSvCw8Px4osvQqmUTns8deoUQkJCjM0fAISGhmLZsmXIzs5G165day2voqICFRUVxttFRUUApA2s8e1RVSVfeQ1MHZa5n1y9Xv6Gb+pcU7vH7ydXCNOP10S5uqoqQKGArh65Jl/z+8kFGj+eD/DY637P09352dIK1pNGj30LXE90Oh2gVMrfG1awntji2Ot+z611j41tI6xhPam1fbJBLb4BvHz5MjZs2IB33nlHFo+OjsawYcOgVqtx5MgRREVFobCwEH/7298AAPn5+ejSpYtsHi8vL+N9phrANWvWYNWqVbXiSUlJcHZ2NtMzosZITEy0dAn0O46FdeF4WA+OheWVlpZaugSLs5pDwG+88YbJ5qqmM2fOICAgwHg7NzcXISEhCAkJwUcffXTXed955x2sXr0aGo0GADBy5Eh07doVH374oTHn2rVreOihh3Dq1Ck89thjtZZhag9gp06dkFfzEDBZhE6nQ2JiIkbceWiFmh3HwrpwPKwHx8J6FBUVwcPDg4eArUFkZCSmTZt215yae+xyc3MxZMgQBAUFYcuWLfdc/mOPPYaioiJcv34dXl5e6NChA/Lz82U5BQUFAKr3BN7J0dFRdsjY0DuXl5ejrKzsnjVQ09HpdCgtLUVZWRkq+e0ui+JYWBeOh/XgWFgPw//ZVrIPzCKspgH08PCAh4dHvXKvXbuGIUOGYODAgYiNjTWe13c3aWlpcHJygpubGwAgKCgIy5cvh1arNV4+5vDhw/Dx8al1aLguN27cAACTh4uJiIjIuhUXF8PV1dXSZViE1RwCri/DYd/OnTvj448/hkqlMt7XoUMHAMC+ffuQn5+PoKAgqNVqJCUlISoqCmFhYfjHP/4BQPriiJ+fH4YOHYrly5fj4sWLCAsLw2uvvYaoqKh61XL79m20bdsWV65csdkVyFoYDsdfvXrVZnfnWwuOhXXheFgPjoX1EEKguLgYPj4+9dqJ9CCymj2A9XX48GFcunQJly5dwkMPPSS7z9DL2tvbY9OmTVi0aBH0ej0efvhhrF69Gi+99JIx19XVFYmJiXjppZcQEBCAtm3/fzv3GhJl+oYB/BrHGcfcCrN0hxA3/eCBLEppap2IyuxEWgtFRypa2spA80MNRNlROi0bRLU0+SEoCjIsKbeyXSzTzjgUq7SFWYRKBUVSlE7e+6G/s01a2397H+fwXj+YD/PMMy/3zeXA7TvvO5EoKChAQUHBF9fS+UfTt29ffpj9RJ8+fZiFn2AW/oV5+A9m4R/0fuIm4M4A+pPO3wHU80Wk/oJZ+A9m4V+Yh/9gFuRP9Hnek4iIiEjHOAB+hbCwMBQWFnrdGUy+wSz8B7PwL8zDfzAL8if8CpiIiIhIZ3gGkIiIiEhnOAASERER6QwHQCIiIiKd4QBIREREpDMcAD+wb98+DBo0CBaLBWlpaaiqqvrs/osXLyItLQ0WiwXx8fH49ddfu+w5ceIEUlJSEBYWhpSUFJSWlqoqP+honYfT6cTo0aMRGRmJyMhIZGZm4vr16ypbCBoqPhudjh07BoPBgOnTp2tcdXBSkcWLFy+Qm5sLq9UKi8WC5ORklJeXq2ohaKjIYvfu3UhMTER4eDhiY2OxatUqvHnzRlULpGdCIiJy7NgxMZlM4nQ6pa6uTvLy8iQiIkIePnzY7f6Ghgbp1auX5OXlSV1dnTidTjGZTFJSUuLZU1NTI0ajUYqKiqS+vl6KiookNDRUrl692lNtBSwVecydO1f27t0rtbW1Ul9fL4sXL5a+ffvK48ePe6qtgKQii06NjY0ycOBAGT16tOTk5CjuJPCpyOLt27eSnp4uU6ZMkcuXL0tjY6NUVVWJy+XqqbYCkoosDh8+LGFhYXLkyBF58OCBnDt3TqxWq+Tn5/dUW6QjHAD/Z8SIEbJs2TKvtaSkJHE4HN3uX716tSQlJXmt/fTTTzJy5EjP81mzZsmkSZO89kycOFFmz56tUdXBS0UeH3O73dK7d285dOjQ1xccxFRl4Xa7JSMjQw4ePCgLFy7kAPgFVGSxf/9+iY+Pl7a2Nu0LDmIqssjNzZVx48Z57SkoKBC73a5R1UT/4FfAANra2nDr1i1kZWV5rWdlZaGmpqbb91y5cqXL/okTJ+LmzZtob2//7J5PHZPeU5XHx16/fo329nb069dPm8KDkMosNm3ahAEDBmDJkiXaFx6EVGVRVlaGUaNGITc3FzExMRg8eDCKiorw7t07NY0EAVVZ2O123Lp1y3NpSkNDA8rLyzF16lQFXZDehfq6AH/w7NkzvHv3DjExMV7rMTExaGlp6fY9LS0t3e53u9149uwZrFbrJ/d86pj0nqo8PuZwODBw4EBkZmZqV3yQUZVFdXU1iouL4XK5VJUedFRl0dDQgD/++APz5s1DeXk57t27h9zcXLjdbqxfv15ZP4FMVRazZ8/G06dPYbfbISJwu91Yvnw5HA6Hsl5IvzgAfsBgMHg9F5Eua/+2/+P1//eY9A8VeXTasWMHjh49isrKSlgsFg2qDW5aZtHa2or58+fD6XSif//+2hcb5LT+XHR0dCA6OhoHDhyA0WhEWloampqasHPnTg6A/0LrLCorK7F161bs27cPNpsN9+/fR15eHqxWK9atW6dx9aR3HAAB9O/fH0ajsct/bk+ePOnyH1unb7/9ttv9oaGhiIqK+uyeTx2T3lOVR6ddu3ahqKgIFy5cwJAhQ7QtPsioyOLPP/9EY2Mjpk2b5nm9o6MDABAaGoq7d+8iISFB404Cn6rPhdVqhclkgtFo9OxJTk5GS0sL2traYDabNe4k8KnKYt26dViwYAF+/PFHAEBqaipevXqFpUuXYu3atQgJ4VVbpB3+NQEwm81IS0tDRUWF13pFRQW+//77bt8zatSoLvvPnz+P9PR0mEymz+751DHpPVV5AMDOnTuxefNmnD17Funp6doXH2RUZJGUlIQ7d+7A5XJ5HtnZ2Rg7dixcLhdiY2OV9RPIVH0uMjIycP/+fc8QDgB//fUXrFYrh79PUJXF69evuwx5RqMR8v6GTQ07IAJ/BqZT5y39xcXFUldXJ/n5+RIRESGNjY0iIuJwOGTBggWe/Z239K9atUrq6uqkuLi4yy391dXVYjQaZdu2bVJfXy/btm3jz8B8IRV5bN++Xcxms5SUlEhzc7Pn0dra2uP9BRIVWXyMdwF/GRVZPHr0SL755htZuXKl3L17V06fPi3R0dGyZcuWHu8vkKjIorCwUHr37i1Hjx6VhoYGOX/+vCQkJMisWbN6vD8KfhwAP7B3716Ji4sTs9ksw4cPl4sXL3peW7hwoYwZM8Zrf2VlpQwbNkzMZrN89913sn///i7HPH78uCQmJorJZJKkpCQ5ceKE6jaChtZ5xMXFCYAuj8LCwh7oJrCp+Gx8iAPgl1ORRU1NjdhsNgkLC5P4+HjZunWruN1u1a0EPK2zaG9vlw0bNkhCQoJYLBaJjY2VFStWyPPnz3ugG9IbgwjPKxMRERHpCa8BJCIiItIZDoBEREREOsMBkIiIiEhnOAASERER6QwHQCIiIiKd4QBIREREpDMcAImIiIh0hgMgERERkc5wACQiIiLSGQ6ARKR7+fn5mD59epf1RYsWweFw9HxBRESKcQAkIt27ceMGRowY4bXW0dGBM2fOICcnx0dVERGpwwGQiHSrvb0dZrMZNTU1WLt2LQwGA2w2GwCguroaISEhnuclJSVITU1FeHg4oqKikJmZiVevXvmyfCKi/yzU1wUQEfmK0WjE5cuXYbPZ4HK5EBMTA4vFAgAoKyvDtGnTEBISgubmZsyZMwc7duzAjBkz0NraiqqqKoiIjzsgIvpvOAASkW6FhISgqakJUVFRGDp0qNdrZWVl2LVrFwCgubkZbrcbP/zwA+Li4gAAqampPV4vEZFW+BUwEelabW1tl+Gvvr4ejx8/RmZmJgBg6NChGD9+PFJTUzFz5kw4nU48f/7cF+USEWmCAyAR6ZrL5er27N+ECRMQHh4O4P1XxRUVFfjtt9+QkpKCPXv2IDExEQ8ePPBFyUREX40DIBHp2p07dzBkyBCvtVOnTiE7O9trzWAwICMjAxs3bkRtbS3MZjNKS0t7slQiIs3wGkAi0rWOjg7cvn0bTU1NiIiIwNu3b3Hjxg2cPHnSs+fatWv4/fffkZWVhejoaFy7dg1Pnz5FcnKy7wonIvoKHACJSNe2bNmCNWvW4JdffkFBQQFSUlJgs9kQHR3t2dOnTx9cunQJu3fvxsuXLxEXF4eff/4ZkydP9mHlRET/nUH4OwZERB7Z2dmw2+1YvXq1r0shIlKG1wASEX3Abrdjzpw5vi6DiEgpngEkIiIi0hmeASQiIiLSGQ6ARERERDrDAZCIiIhIZzgAEhEREekMB0AiIiIineEASERERKQzHACJiIiIdIYDIBEREZHOcAAkIiIi0pm/AQAAy3jr1LEmAAAAAElFTkSuQmCC", "text/html": [ "\n", "
\n", "
\n", " Time Plots\n", "
\n", - " \n", + " \n", "
\n", " " ], @@ -346,7 +290,7 @@ "visu = MotorDashboard(state_plots=['i_sq', 'i_sd', 'u_sq', 'u_sd'], update_interval=10) # visualization\n", "motor = Motor(MotorType.PermanentMagnetSynchronousMotor,\n", " ControlType.CurrentControl,\n", - " ActionType.Continuous)\n", + " ActionType.Finite)\n", "env = gem.make(motor.env_id(),\n", " visualization=visu,\n", " load=ConstantSpeedLoad(omega_fixed=1000 * np.pi / 30), # Fixed preset speed\n", @@ -373,19 +317,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -2059.299467864888\n" + "Reward = -1430.5877421997113\n" ] } ], "source": [ - "controller = Controller.make('fcs_mpc', env, ph=1) # initializing the MPC Controller\n", + "controller = Controller.make('fcs_mpc', env, prediction_horizon=1) # initializing the MPC Controller\n", "(state, reference), _ = env.reset()\n", "cum_rew = 0\n", "\n", From 39e418a03b8cc587b4a214b7f36995f95107b3d5 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 15 Jul 2025 22:39:06 +0200 Subject: [PATCH 20/37] added (self.u_abc_k1 = self.u_lim * self.subactions) for calculation of u_abc from subaction state --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index 6738c935..cd7d86e0 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -42,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -94,7 +94,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -124,15 +124,19 @@ " self.p = environment.get_wrapper_attr('physical_system').electrical_motor.motor_parameter['p']\n", " self.abc_to_dq = environment.get_wrapper_attr('physical_system').abc_to_dq_space\n", " self.prediction_horizon = prediction_horizon # Prediction horizon (typically 1 for FCS-MPC)\n", + "\n", + " # limit values for the normalization\n", + " self.u_lim = environment.get_wrapper_attr('physical_system').limits[env.get_wrapper_attr('state_names').index('u_sd')]\n", " \n", " # Get the finite set of voltage vectors from the environment\n", " self.subactions = -np.power(-1, environment.get_wrapper_attr('physical_system')._converter._subactions)\n", + " self.u_abc_k1 = self.u_lim * self.subactions\n", " \n", " def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth):\n", " min_cost = float('inf')\n", " best_sequence = []\n", "\n", - " for idx, (v_a, v_b, v_c) in enumerate(self.subactions):\n", + " for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1):\n", " # Convert to dq \n", " v_dq = np.transpose(np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5*omega*self.tau)]))\n", " v_d, v_q = v_dq[0], v_dq[1]\n", @@ -199,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -217,7 +221,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -254,13 +258,13 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "47799e64eeb84ba88aaee9f1d299e191", + "model_id": "793190cb48db401cb57950c71587cbe5", "version_major": 2, "version_minor": 0 }, @@ -317,14 +321,14 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -1430.5877421997113\n" + "Reward = -1041.746469033069\n" ] } ], From 34ab8f447b0f392c38125e7305f7f80a2868c1b2 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 23 Jul 2025 18:45:27 +0200 Subject: [PATCH 21/37] Revised Description for FCS-MPC --- .../pmsm_fcs_mpc_dq_current_control.ipynb | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb index cd7d86e0..2336ea7a 100644 --- a/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_fcs_mpc_dq_current_control.ipynb @@ -18,10 +18,9 @@ "\n", "![MPC1](img/mpc_scheme.png)\n", "\n", - "With the help of the system model the output variables are calculated. By minimizing a cost function, the optimizer determines the optimal control variables for the following N time steps, the so-called prediction horizon. The first manipulated variable is switched on and then the system outputs are measured. When calculating the manipulated variables, the manipulated variable limits are explicitly taken into account.\n", + "With the help of the system model, the output variables are predicted for each possible switching state in the finite control set. The optimizer evaluates a cost function (typically the quadratic control error) for all possible switching combinations over the prediction horizon. The switching state that minimizes the cost function is selected and applied to the system in the next time step.\n", "\n", - "![Limits](img/voltage_limits.png)\n", - "The quadratic control error area is used as the cost function. Since the control is performed in the rotating dq-coordinates, the voltage limits, which have the shape of a hexagon, are time-variant. The optimization problem is solved iteratively using an Interior-Point Solver, which approximates the limits using barrier functions." + "Unlike CCS-MPC, FCS-MPC does not require an iterative numerical solver or barrier functions to handle constraints, as the voltage limits are inherently respected by evaluating only the physically realizable switching states of the converter. The computational efficiency of FCS-MPC comes from the direct enumeration and evaluation of the finite number of possible switching states, rather than solving an optimization problem with constraints.\n" ] }, { @@ -33,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -42,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -94,7 +93,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -203,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -221,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -258,13 +257,13 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "793190cb48db401cb57950c71587cbe5", + "model_id": "22faa601faf14fbd99256e8bb6eca063", "version_major": 2, "version_minor": 0 }, @@ -321,14 +320,22 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 8, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/labdoo/anaconda3/envs/PE/lib/python3.12/site-packages/gymnasium/utils/passive_env_checker.py:158: UserWarning: \u001b[33mWARN: The obs returned by the `step()` method is not within the observation space.\u001b[0m\n", + " logger.warn(f\"{pre} is not within the observation space.\")\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "Reward = -1041.746469033069\n" + "Reward = -1148.3054379267162\n" ] } ], From 8ad4a5a9c286f3429a66b096c1ba1ae41adf228c Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 30 Jul 2025 01:25:39 +0200 Subject: [PATCH 22/37] added mpc controller in gem_controllers,though testing is pending --- src/gem_controllers/__init__.py | 2 + src/gem_controllers/gem_controller.py | 25 +++-- src/gem_controllers/mpc_current_controller.py | 97 +++++++++++++++++++ 3 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 src/gem_controllers/mpc_current_controller.py diff --git a/src/gem_controllers/__init__.py b/src/gem_controllers/__init__.py index 456c91cb..128d5dbc 100644 --- a/src/gem_controllers/__init__.py +++ b/src/gem_controllers/__init__.py @@ -9,3 +9,5 @@ from .pi_speed_controller import PISpeedController from .reference_plotter import ReferencePlotter from .torque_controller import TorqueController +from .mpc_current_controller import MPCCurrentController + diff --git a/src/gem_controllers/gem_controller.py b/src/gem_controllers/gem_controller.py index 8083c5e8..04f1c3b3 100644 --- a/src/gem_controllers/gem_controller.py +++ b/src/gem_controllers/gem_controller.py @@ -4,6 +4,7 @@ import gym_electric_motor.core + class GemController: """The GemController is the base for all motor controllers in the gem-control package. @@ -66,13 +67,23 @@ def make( control_task = gc.utils.get_control_task(env_id) tuner_kwargs = dict() - # Initialize the current control stage - controller = gc.PICurrentController( - env, - env_id, - base_current_controller=base_current_controller, - decoupling=decoupling, - ) + # Initialize the current control stage + if base_current_controller == "PI": + controller = gc.PICurrentController( + env, + env_id, + base_current_controller=base_current_controller, + decoupling=decoupling, + ) + tuner_kwargs["a"] = a + tuner_kwargs["plot_references"] = plot_references + + elif base_current_controller == "MPC": + controller = gc.MPCCurrentController(env, env_id) + + else: + raise NotImplementedError(f"Unsupported base_current_controller: {base_current_controller}") + tuner_kwargs["a"] = a tuner_kwargs["plot_references"] = plot_references if control_task in ["TC", "SC"]: diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py new file mode 100644 index 00000000..09030c4a --- /dev/null +++ b/src/gem_controllers/mpc_current_controller.py @@ -0,0 +1,97 @@ +import numpy as np + +class MPCCurrentController: + def __init__(self, env, env_id, prediction_horizon=1): + self.env_id = env_id + + # Get attributes from environment + self.state_names = env.get_wrapper_attr('state_names') + self.physical_system = env.get_wrapper_attr('physical_system') + self.tau = self.physical_system.tau + self.limits = self.physical_system.limits + + motor = self.physical_system.electrical_motor.motor_parameter + self.l_q = motor['l_q'] + self.l_d = motor['l_d'] + self.psi_ = motor['psi_p'] + self.r_s = motor['r_s'] + self.p = motor['p'] + + # Indices + self.i_sd_idx = self.state_names.index('i_sd') + self.i_sq_idx = self.state_names.index('i_sq') + self.omega_idx = self.state_names.index('omega') + self.epsilon_idx = self.state_names.index('epsilon') + + self.u_sd_idx = self.state_names.index('u_sd') + self.u_lim = self.limits[self.u_sd_idx] + + self.abc_to_dq = self.physical_system.abc_to_dq_space + + # Get finite set of voltage vectors (actions) + self.subactions = -np.power(-1, self.physical_system._converter._subactions) + self.u_abc_k1 = self.u_lim * self.subactions + + # Prediction horizon + self.prediction_horizon = prediction_horizon + print("MPCCurrentController initialized") + + + def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth): + min_cost = float('inf') + best_sequence = [] + + for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): + v_dq = np.transpose( + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5 * omega * self.tau)]) + ) + v_d, v_q = v_dq[0], v_dq[1] + + epsilon_next = epsilon_el + omega * self.tau + i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d) + i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q) + + cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2 + + if depth == self.prediction_horizon - 1: + total_cost = cost + sequence = [idx] + else: + future_cost, future_sequence = self._simulate_sequence( + i_d_next, i_q_next, epsilon_next, omega, ref_i_d, ref_i_q, depth + 1 + ) + total_cost = cost + future_cost + sequence = [idx] + future_sequence + + if total_cost < min_cost: + min_cost = total_cost + best_sequence = sequence + + return min_cost, best_sequence + + def control(self, state, reference): + print("MPC control called with state:", state) + i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx] + i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx] + epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx] + omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx] + + ref_i_q = reference[0] * self.limits[self.i_sq_idx] + ref_i_d = reference[1] * self.limits[self.i_sd_idx] + + _, best_sequence = self._simulate_sequence( + i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0 + ) + + best_idx = best_sequence[0] if best_sequence else 0 + return best_idx + + def reset(self): + pass + + def tune(self, env, env_id, **kwargs): + pass + + @property + def stages(self): + return [] From 72ebcce938be660beafc2457bb9492f8500b40d4 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 26 Aug 2025 23:56:36 +0200 Subject: [PATCH 23/37] 1)In gym_controller file edited such that MPC controller should not wrap around GymElectricMotorAdapter class. 2)updated the MPC controller file to process directly from environment,PMSM works. --- src/gem_controllers/gem_controller.py | 5 +- src/gem_controllers/mpc_current_controller.py | 152 +++++++++++++----- .../stages/disc_output_stage.py | 1 + 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/gem_controllers/gem_controller.py b/src/gem_controllers/gem_controller.py index 04f1c3b3..2445e5c7 100644 --- a/src/gem_controllers/gem_controller.py +++ b/src/gem_controllers/gem_controller.py @@ -99,7 +99,10 @@ def make( base_speed_controller=base_speed_controller, ) # Wrap the controller with the adapter to map the inputs and outputs to the environment - controller = gc.GymElectricMotorAdapter(env, env_id, controller) + + if base_current_controller != "MPC": + + controller = gc.GymElectricMotorAdapter(env, env_id, controller) # Fit the controllers parameters to the environment controller.tune(env, env_id, **tuner_kwargs) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 09030c4a..a74f5827 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,64 +1,129 @@ import numpy as np +from .gem_controller import GemController -class MPCCurrentController: + +class MPCCurrentController(GemController): + """MPC current controller for finite action space control""" + + @property + def signal_names(self): + """Signal names of the calculated values""" + return ["u_MPC"] + + @property + def stages(self): + """List of stages (empty for MPC as it handles everything internally)""" + return [] + + @property + def references(self): + """Reference values of the current control stage""" + return dict() + + @property + def referenced_states(self): + """Referenced states of the current control stage""" + return ['i_sd', 'i_sq'] + + @property + def maximum_reference(self): + """Maximum reference values""" + return {'i_sd': 1.0, 'i_sq': 1.0} + def __init__(self, env, env_id, prediction_horizon=1): + """ + Initialize MPC current controller + + Args: + env: GEM environment + env_id: Environment ID + prediction_horizon: MPC prediction steps + """ + super().__init__() self.env_id = env_id - + # Get attributes from environment self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau self.limits = self.physical_system.limits - motor = self.physical_system.electrical_motor.motor_parameter - self.l_q = motor['l_q'] - self.l_d = motor['l_d'] - self.psi_ = motor['psi_p'] - self.r_s = motor['r_s'] - self.p = motor['p'] + #motor_type = self.physical_system.electrical_motor.motor_type + self.motor_params = self.physical_system.electrical_motor.motor_parameter + + # Store each parameter as an attribute dynamically + for key, value in self.motor_params.items(): + setattr(self, key, value) + - # Indices + # State indices self.i_sd_idx = self.state_names.index('i_sd') - self.i_sq_idx = self.state_names.index('i_sq') + self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.epsilon_idx = self.state_names.index('epsilon') - self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] + # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space - - # Get finite set of voltage vectors (actions) + + # Finite action set self.subactions = -np.power(-1, self.physical_system._converter._subactions) - self.u_abc_k1 = self.u_lim * self.subactions - - # Prediction horizon + # All possible voltage vectors in abc coordinates + self.u_abc_k1 = self.u_lim * self.subactions + self.prediction_horizon = prediction_horizon + print("MPCCurrentController initialized") - - - def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth): + # Model constants for MPC + self._model_constants = self.physical_system.electrical_motor._model_constants + motor_type = type(self.physical_system.electrical_motor).__name__ + + if motor_type == "PermanentMagnetSynchronousMotor": + self.motor_state_names = ["i_sd", "i_sq", "epsilon"] + elif motor_type == "SynchronousReluctanceMotor": + self.motor_state_names = ["i_sd", "i_sq", "epsilon"] + elif motor_type == "ExternallyExcitedSynchronousMotor": + self.motor_state_names = ["i_sd", "i_sq", "i_e", "epsilon"] + elif motor_type == "InductionMotor": + self.motor_state_names = ["i_salpha", "i_sbeta", "psi_ralpha", "psi_rbeta", "epsilon"] + else: + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + + + + def _simulate_sequence(self, x, A, g, omega, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): + # Predict next state v_dq = np.transpose( - np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), epsilon_el + 0.5 * omega * self.tau)]) + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - epsilon_next = epsilon_el + omega * self.tau - i_d_next = i_d + self.tau * ((v_d - self.r_s * i_d + omega * self.l_q * i_q) / self.l_d) - i_q_next = i_q + self.tau * ((v_q - self.r_s * i_q - omega * self.l_d * i_d - omega * self.psi_) / self.l_q) + # Voltage input contribution (from motor parameters) + u_contrib = np.array([ + float(v_d) / self.l_d, + float(v_q) / self.l_q, + 0.0 + ]) + + # Compute derivative + dx = A @ x + g + u_contrib + + # Euler integration for next step + x_next = x + self.tau * dx - cost = (i_d_next - ref_i_d)**2 + (i_q_next - ref_i_q)**2 + cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - i_d_next, i_q_next, epsilon_next, omega, ref_i_d, ref_i_q, depth + 1 + x_next, A, g, omega, ref_i_d, ref_i_q, depth + 1 ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -70,28 +135,39 @@ def _simulate_sequence(self, i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, dept return min_cost, best_sequence def control(self, state, reference): - print("MPC control called with state:", state) - i_d = state[self.i_sd_idx] * self.limits[self.i_sd_idx] - i_q = state[self.i_sq_idx] * self.limits[self.i_sq_idx] - epsilon_el = state[self.epsilon_idx] * self.limits[self.epsilon_idx] + """Main control method matching PI controller interface""" + + # Denormalize state and references omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx] ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sd_idx] + ref_i_d = reference[1] * self.limits[self.i_sq_idx] + + # Build denormalized state vector for the motor (only relevant states) + state_vector = [] + for name in self.motor_state_names: + s_idx = self.state_names.index(name) + state_vector.append(state[s_idx] * self.limits[s_idx]) + x = np.array(state_vector) + + # Unpack electrical jacobian + A, g, dTdx = self.physical_system.electrical_motor.electrical_jacobian( + x, # full state vector + u_in=0, # inputs (or adapt for your motor) + omega=omega + ) + # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( - i_d, i_q, epsilon_el, omega, ref_i_d, ref_i_q, depth=0 + x, A, g, omega, ref_i_d, ref_i_q, depth=0 ) - best_idx = best_sequence[0] if best_sequence else 0 return best_idx - def reset(self): - pass - def tune(self, env, env_id, **kwargs): + """Required tuning method (no tuning needed for MPC)""" pass - @property - def stages(self): - return [] + def reset(self): + """Reset controller state""" + pass \ No newline at end of file diff --git a/src/gem_controllers/stages/disc_output_stage.py b/src/gem_controllers/stages/disc_output_stage.py index 9fba43d1..e8cb0bb5 100644 --- a/src/gem_controllers/stages/disc_output_stage.py +++ b/src/gem_controllers/stages/disc_output_stage.py @@ -63,6 +63,7 @@ def to_discrete(multi_discrete_action): def to_b6_discrete(multi_discrete_action): """Returns the multi discrete action for a B6 brigde converter.""" raise NotImplementedError + @staticmethod def to_multi_discrete(multi_discrete_action): From baa022da7d3c67f430a46b79f8d8942c85f6325f Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Sat, 6 Sep 2025 13:29:21 +0200 Subject: [PATCH 24/37] =?UTF-8?q?=1B[200~fix:=20avoid=20double-counting=20?= =?UTF-8?q?of=20cross-coupling=20terms=20in=20MPC=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gem_controllers/mpc_current_controller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index a74f5827..412f68f5 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -156,6 +156,12 @@ def control(self, state, reference): u_in=0, # inputs (or adapt for your motor) omega=omega ) + # Local indices for motor states + self.local_idx = {name: i for i, name in enumerate(self.motor_state_names)} + + # remove duplicated cross-coupling terms from g + g[0] -= self.p * self.l_q / self.l_d * x[self.local_idx['i_sq']] + g[1] += self.p * self.l_d / self.l_q * x[self.local_idx['i_sd']] # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( From 31f5c8810a98bb539deae4c7d81f6b9f75941a8b Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Fri, 12 Sep 2025 15:18:45 +0200 Subject: [PATCH 25/37] I have updated my MPC controller that takes motor dynamics from model_constants --- src/gem_controllers/mpc_current_controller.py | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 412f68f5..de7f0e75 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -59,22 +59,22 @@ def __init__(self, env, env_id, prediction_horizon=1): # State indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') - self.omega_idx = self.state_names.index('omega') - self.epsilon_idx = self.state_names.index('epsilon') + self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space - - # Finite action set + self.subactions = -np.power(-1, self.physical_system._converter._subactions) + # All possible voltage vectors in abc coordinates self.u_abc_k1 = self.u_lim * self.subactions self.prediction_horizon = prediction_horizon print("MPCCurrentController initialized") + # Model constants for MPC self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ @@ -82,49 +82,57 @@ def __init__(self, env, env_id, prediction_horizon=1): if motor_type == "PermanentMagnetSynchronousMotor": self.motor_state_names = ["i_sd", "i_sq", "epsilon"] elif motor_type == "SynchronousReluctanceMotor": - self.motor_state_names = ["i_sd", "i_sq", "epsilon"] - elif motor_type == "ExternallyExcitedSynchronousMotor": - self.motor_state_names = ["i_sd", "i_sq", "i_e", "epsilon"] - elif motor_type == "InductionMotor": - self.motor_state_names = ["i_salpha", "i_sbeta", "psi_ralpha", "psi_rbeta", "epsilon"] + self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - def _simulate_sequence(self, x, A, g, omega, ref_i_d, ref_i_q, depth): + def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): - # Predict next state + # Transform voltages to dq frame v_dq = np.transpose( np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - # Voltage input contribution (from motor parameters) - u_contrib = np.array([ - float(v_d) / self.l_d, - float(v_q) / self.l_q, - 0.0 + # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] + exterded_vector = np.array([ + omega, + x[0], # i_d + x[1], # i_q + float(v_d), # u_d + float(v_q), # u_q + omega_Isd, + omega_Isq ]) # Compute derivative - dx = A @ x + g + u_contrib - + dx = model_constants @ exterded_vector + # Euler integration for next step x_next = x + self.tau * dx - cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 + # Extract next states + i_d_next, i_q_next, epsilon_next = x_next + omega_Isd_next = omega * i_d_next + omega_Isq_next = omega * i_q_next + + # Calculate cost + cost = (i_d_next - ref_i_d) ** 2 + (i_q_next - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - x_next, A, g, omega, ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, + omega_Isd_next, omega_Isq_next, + ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -135,37 +143,28 @@ def _simulate_sequence(self, x, A, g, omega, ref_i_d, ref_i_q, depth): return min_cost, best_sequence def control(self, state, reference): - """Main control method matching PI controller interface""" - - # Denormalize state and references - omega = self.p * state[self.omega_idx] * self.limits[self.omega_idx] - - ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sq_idx] - + """Main control method matching PI controller interface""" + # Build denormalized state vector for the motor (only relevant states) state_vector = [] for name in self.motor_state_names: s_idx = self.state_names.index(name) state_vector.append(state[s_idx] * self.limits[s_idx]) - x = np.array(state_vector) + x = np.array(state_vector) - # Unpack electrical jacobian - A, g, dTdx = self.physical_system.electrical_motor.electrical_jacobian( - x, # full state vector - u_in=0, # inputs (or adapt for your motor) - omega=omega - ) - # Local indices for motor states - self.local_idx = {name: i for i, name in enumerate(self.motor_state_names)} - - # remove duplicated cross-coupling terms from g - g[0] -= self.p * self.l_q / self.l_d * x[self.local_idx['i_sq']] - g[1] += self.p * self.l_d / self.l_q * x[self.local_idx['i_sd']] + # Denormalize state and references + omega = state[self.omega_idx] * self.limits[self.omega_idx] + ref_i_q = reference[0] * self.limits[self.i_sq_idx] + ref_i_d = reference[1] * self.limits[self.i_sq_idx] + omega_Isd = omega * x[0] # omega * i_sd + omega_Isq = omega * x[1] # omega * i_sq + # Unpack model constants + model_constants= self.physical_system.electrical_motor._model_constants + # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( - x, A, g, omega, ref_i_d, ref_i_q, depth=0 + model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 ) best_idx = best_sequence[0] if best_sequence else 0 return best_idx From ea18b08e44fdeaf27082ac525ad6234919657d2b Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 24 Sep 2025 19:35:30 +0200 Subject: [PATCH 26/37] Added MPC current controller with delay compensation --- src/gem_controllers/mpc_current_controller.py | 168 ++++++++++++------ 1 file changed, 112 insertions(+), 56 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index de7f0e75..2e39a81f 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,44 +1,30 @@ import numpy as np from .gem_controller import GemController - class MPCCurrentController(GemController): - """MPC current controller for finite action space control""" + """MPC current controller with delay compensation using extrapolation""" @property def signal_names(self): - """Signal names of the calculated values""" return ["u_MPC"] @property def stages(self): - """List of stages (empty for MPC as it handles everything internally)""" return [] @property def references(self): - """Reference values of the current control stage""" return dict() @property def referenced_states(self): - """Referenced states of the current control stage""" return ['i_sd', 'i_sq'] @property def maximum_reference(self): - """Maximum reference values""" return {'i_sd': 1.0, 'i_sq': 1.0} def __init__(self, env, env_id, prediction_horizon=1): - """ - Initialize MPC current controller - - Args: - env: GEM environment - env_id: Environment ID - prediction_horizon: MPC prediction steps - """ super().__init__() self.env_id = env_id @@ -48,14 +34,12 @@ def __init__(self, env, env_id, prediction_horizon=1): self.tau = self.physical_system.tau self.limits = self.physical_system.limits - #motor_type = self.physical_system.electrical_motor.motor_type self.motor_params = self.physical_system.electrical_motor.motor_parameter # Store each parameter as an attribute dynamically for key, value in self.motor_params.items(): setattr(self, key, value) - # State indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') @@ -65,7 +49,6 @@ def __init__(self, env, env_id, prediction_horizon=1): # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space - self.subactions = -np.power(-1, self.physical_system._converter._subactions) # All possible voltage vectors in abc coordinates @@ -73,8 +56,6 @@ def __init__(self, env, env_id, prediction_horizon=1): self.prediction_horizon = prediction_horizon - print("MPCCurrentController initialized") - # Model constants for MPC self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ @@ -84,26 +65,93 @@ def __init__(self, env, env_id, prediction_horizon=1): elif motor_type == "SynchronousReluctanceMotor": self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: - raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + # === DELAY COMPENSATION VARIABLES === + self.previous_voltage_idx = 0 # Store the previously calculated voltage + self.past_references = [] # Store past references for extrapolation + self.extrapolation_order = 2 # n=2 as recommended in the text + + print("MPCCurrentController with delay compensation initialized") + + def _extrapolate_reference(self, current_ref, n=2): + """Extrapolate future reference using Lagrange extrapolation (Eq. 12.8-12.10)""" + # Store current reference + self.past_references.append(current_ref.copy()) + + # Keep only the needed history (n+1 samples) + if len(self.past_references) > n + 1: + self.past_references.pop(0) + + # If we don't have enough history, return current reference + if len(self.past_references) < n + 1: + return current_ref + + # Extract references: i*(k), i*(k-1), i*(k-2), etc. + ref_k = self.past_references[-1] # i*(k) + ref_k_minus_1 = self.past_references[-2] # i*(k-1) + ref_k_minus_2 = self.past_references[-3] # i*(k-2) + + # Extrapolate i*(k+2) + ref_k_plus_2 = 6 * ref_k - 8 * ref_k_minus_1 + 3 * ref_k_minus_2 + + return ref_k_plus_2 + + def _estimate_currents(self, model_constants, x, omega, voltage_idx): + """Estimate currents at next sampling instant (Step 3 in flowchart)""" + # Get the voltage vector that was applied in the previous cycle + v_abc = self.u_abc_k1[voltage_idx] + + # Transform voltages to dq frame at the appropriate angle + v_dq = np.transpose( + np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) + ) + v_d, v_q = v_dq[0], v_dq[1] + + # Calculate omega * i terms + omega_Isd = omega * x[0] + omega_Isq = omega * x[1] + + # Build input vector for derivative calculation + exterded_vector = np.array([ + omega, + x[0], # i_d + x[1], # i_q + float(v_d), # u_d + float(v_q), # u_q + omega_Isd, + omega_Isq + ]) + # Compute derivative using the applied voltage + dx = model_constants @ exterded_vector + + # Euler integration to estimate next state + x_estimated = x + self.tau * dx + + return x_estimated - def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): + def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i_q, depth): + """Predict future states from estimated current (Step 4 in flowchart)""" min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): # Transform voltages to dq frame v_dq = np.transpose( - np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x_estimated[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] + # Calculate omega * i terms for estimated state + omega_Isd = omega * x_estimated[0] + omega_Isq = omega * x_estimated[1] + # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] exterded_vector = np.array([ omega, - x[0], # i_d - x[1], # i_q + x_estimated[0], # i_d (estimated) + x_estimated[1], # i_q (estimated) float(v_d), # u_d float(v_q), # u_q omega_Isd, @@ -113,26 +161,19 @@ def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d # Compute derivative dx = model_constants @ exterded_vector - # Euler integration for next step - x_next = x + self.tau * dx + # Euler integration for next step (predicting k+2 from estimated k+1) + x_next = x_estimated + self.tau * dx - # Extract next states - i_d_next, i_q_next, epsilon_next = x_next - omega_Isd_next = omega * i_d_next - omega_Isq_next = omega * i_q_next - - # Calculate cost - cost = (i_d_next - ref_i_d) ** 2 + (i_q_next - ref_i_q) ** 2 + # Calculate cost using future reference (k+2) + cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - model_constants, x_next, omega, - omega_Isd_next, omega_Isq_next, - ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -143,36 +184,51 @@ def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d return min_cost, best_sequence def control(self, state, reference): - """Main control method matching PI controller interface""" - - # Build denormalized state vector for the motor (only relevant states) + """Main control method with delay compensation""" + # Step 1: Measurement (k) state_vector = [] for name in self.motor_state_names: s_idx = self.state_names.index(name) state_vector.append(state[s_idx] * self.limits[s_idx]) - x = np.array(state_vector) + x_measured = np.array(state_vector) - # Denormalize state and references + # Denormalize state omega = state[self.omega_idx] * self.limits[self.omega_idx] - ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sq_idx] - omega_Isd = omega * x[0] # omega * i_sd - omega_Isq = omega * x[1] # omega * i_sq - - # Unpack model constants - model_constants= self.physical_system.electrical_motor._model_constants - - # Pass everything needed to _simulate_sequence + + # Step 2: Apply previously calculated voltage (k) + # (This happens automatically in the environment - we just track what was applied) + + # Step 3: Estimate currents at k+1 + x_estimated = self._estimate_currents( + self._model_constants, x_measured, omega, self.previous_voltage_idx + ) + + # Step 4: Extrapolate future references + ref_i_q_current = reference[0] * self.limits[self.i_sq_idx] + ref_i_d_current = reference[1] * self.limits[self.i_sq_idx] + current_ref = np.array([ref_i_d_current, ref_i_q_current]) + + # Get references for k+2 + ref_k_plus_2 = self._extrapolate_reference(current_ref) + ref_i_d_future, ref_i_q_future = ref_k_plus_2 # Use k+2 reference for prediction + + # Step 5: Predict for k+2 and evaluate cost function _, best_sequence = self._simulate_sequence( - model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 + self._model_constants, x_estimated, omega, + ref_i_d_future, ref_i_q_future, depth=0 ) + + # Step 6: Select optimal switching state best_idx = best_sequence[0] if best_sequence else 0 + self.previous_voltage_idx = best_idx # Store for next cycle + return best_idx def tune(self, env, env_id, **kwargs): - """Required tuning method (no tuning needed for MPC)""" + """Required tuning method""" pass def reset(self): - """Reset controller state""" - pass \ No newline at end of file + """Reset controller state including delay compensation variables""" + self.previous_voltage_idx = 0 + self.past_references = [] \ No newline at end of file From 06ff656ff57358bd24ac369f4c30581da9699b65 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Sun, 28 Sep 2025 22:48:54 +0200 Subject: [PATCH 27/37] support weighted cost function (w_d, w_q) and forward args via GemController.make - Added w_d and w_q parameters to MPCCurrentController - Updated cost function to use weighted errors - Modified GemController.make to forward extra kwargs into MPC --- src/gem_controllers/gem_controller.py | 3 +- src/gem_controllers/mpc_current_controller.py | 179 +++++++----------- 2 files changed, 67 insertions(+), 115 deletions(-) diff --git a/src/gem_controllers/gem_controller.py b/src/gem_controllers/gem_controller.py index 2445e5c7..22114bd8 100644 --- a/src/gem_controllers/gem_controller.py +++ b/src/gem_controllers/gem_controller.py @@ -40,6 +40,7 @@ def make( plot_references: bool = True, block_diagram: bool = True, save_block_diagram_as: (str, tuple) = None, + **kwargs ): """A factory function that generates (and parameterizes) a matching GemController for a given gym-electric-motor environment `env`. @@ -79,7 +80,7 @@ def make( tuner_kwargs["plot_references"] = plot_references elif base_current_controller == "MPC": - controller = gc.MPCCurrentController(env, env_id) + controller = gc.MPCCurrentController(env, env_id, **kwargs) else: raise NotImplementedError(f"Unsupported base_current_controller: {base_current_controller}") diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 2e39a81f..9c822b3e 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,30 +1,46 @@ import numpy as np from .gem_controller import GemController + class MPCCurrentController(GemController): - """MPC current controller with delay compensation using extrapolation""" + """MPC current controller for finite action space control""" @property def signal_names(self): + """Signal names of the calculated values""" return ["u_MPC"] @property def stages(self): + """List of stages (empty for MPC as it handles everything internally)""" return [] @property def references(self): + """Reference values of the current control stage""" return dict() @property def referenced_states(self): + """Referenced states of the current control stage""" return ['i_sd', 'i_sq'] @property def maximum_reference(self): + """Maximum reference values""" return {'i_sd': 1.0, 'i_sq': 1.0} - def __init__(self, env, env_id, prediction_horizon=1): + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): + """ + Initialize MPC current controller + + Args: + env: GEM environment + env_id: Environment ID + prediction_horizon: MPC prediction steps + w_d: weight for d-axis current error + w_q: weight for q-axis current error + """ super().__init__() self.env_id = env_id @@ -34,12 +50,14 @@ def __init__(self, env, env_id, prediction_horizon=1): self.tau = self.physical_system.tau self.limits = self.physical_system.limits + #motor_type = self.physical_system.electrical_motor.motor_type self.motor_params = self.physical_system.electrical_motor.motor_parameter # Store each parameter as an attribute dynamically for key, value in self.motor_params.items(): setattr(self, key, value) + # State indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') @@ -49,13 +67,20 @@ def __init__(self, env, env_id, prediction_horizon=1): # Coordinate transformation self.abc_to_dq = self.physical_system.abc_to_dq_space + self.subactions = -np.power(-1, self.physical_system._converter._subactions) # All possible voltage vectors in abc coordinates self.u_abc_k1 = self.u_lim * self.subactions self.prediction_horizon = prediction_horizon - + + # Store weights + self.w_d = w_d + self.w_q = w_q + + print(f"MPCCurrentController initialized with w_d: {self.w_d}, w_q: {self.w_q}") + # Model constants for MPC self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ @@ -65,93 +90,26 @@ def __init__(self, env, env_id, prediction_horizon=1): elif motor_type == "SynchronousReluctanceMotor": self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: - raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - # === DELAY COMPENSATION VARIABLES === - self.previous_voltage_idx = 0 # Store the previously calculated voltage - self.past_references = [] # Store past references for extrapolation - self.extrapolation_order = 2 # n=2 as recommended in the text - - print("MPCCurrentController with delay compensation initialized") - - def _extrapolate_reference(self, current_ref, n=2): - """Extrapolate future reference using Lagrange extrapolation (Eq. 12.8-12.10)""" - # Store current reference - self.past_references.append(current_ref.copy()) - - # Keep only the needed history (n+1 samples) - if len(self.past_references) > n + 1: - self.past_references.pop(0) - - # If we don't have enough history, return current reference - if len(self.past_references) < n + 1: - return current_ref - - # Extract references: i*(k), i*(k-1), i*(k-2), etc. - ref_k = self.past_references[-1] # i*(k) - ref_k_minus_1 = self.past_references[-2] # i*(k-1) - ref_k_minus_2 = self.past_references[-3] # i*(k-2) - - # Extrapolate i*(k+2) - ref_k_plus_2 = 6 * ref_k - 8 * ref_k_minus_1 + 3 * ref_k_minus_2 - - return ref_k_plus_2 - - def _estimate_currents(self, model_constants, x, omega, voltage_idx): - """Estimate currents at next sampling instant (Step 3 in flowchart)""" - # Get the voltage vector that was applied in the previous cycle - v_abc = self.u_abc_k1[voltage_idx] - - # Transform voltages to dq frame at the appropriate angle - v_dq = np.transpose( - np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) - ) - v_d, v_q = v_dq[0], v_dq[1] - - # Calculate omega * i terms - omega_Isd = omega * x[0] - omega_Isq = omega * x[1] - # Build input vector for derivative calculation - exterded_vector = np.array([ - omega, - x[0], # i_d - x[1], # i_q - float(v_d), # u_d - float(v_q), # u_q - omega_Isd, - omega_Isq - ]) - - # Compute derivative using the applied voltage - dx = model_constants @ exterded_vector - - # Euler integration to estimate next state - x_estimated = x + self.tau * dx - - return x_estimated - def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i_q, depth): - """Predict future states from estimated current (Step 4 in flowchart)""" + def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): # Transform voltages to dq frame v_dq = np.transpose( - np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x_estimated[-1] + 0.5 * omega * self.tau)]) + np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - # Calculate omega * i terms for estimated state - omega_Isd = omega * x_estimated[0] - omega_Isq = omega * x_estimated[1] - # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] exterded_vector = np.array([ omega, - x_estimated[0], # i_d (estimated) - x_estimated[1], # i_q (estimated) + x[0], # i_d + x[1], # i_q float(v_d), # u_d float(v_q), # u_q omega_Isd, @@ -161,19 +119,27 @@ def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i # Compute derivative dx = model_constants @ exterded_vector - # Euler integration for next step (predicting k+2 from estimated k+1) - x_next = x_estimated + self.tau * dx + # Euler integration for next step + x_next = x + self.tau * dx + + # Extract next states + i_d_next, i_q_next, epsilon_next = x_next + omega_Isd_next = omega * i_d_next + omega_Isq_next = omega * i_q_next + + # Calculate Weighted cost + cost = self.w_d * (i_d_next - ref_i_d) ** 2 + self.w_q * (i_q_next - ref_i_q) ** 2 - # Calculate cost using future reference (k+2) - cost = (x_next[0] - ref_i_d) ** 2 + (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: total_cost = cost sequence = [idx] else: future_cost, future_sequence = self._simulate_sequence( - model_constants, x_next, omega, ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, + omega_Isd_next, omega_Isq_next, + ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -184,51 +150,36 @@ def _simulate_sequence(self, model_constants, x_estimated, omega, ref_i_d, ref_i return min_cost, best_sequence def control(self, state, reference): - """Main control method with delay compensation""" - # Step 1: Measurement (k) + """Main control method matching PI controller interface""" + + # Build denormalized state vector for the motor (only relevant states) state_vector = [] for name in self.motor_state_names: s_idx = self.state_names.index(name) state_vector.append(state[s_idx] * self.limits[s_idx]) - x_measured = np.array(state_vector) + x = np.array(state_vector) - # Denormalize state + # Denormalize state and references omega = state[self.omega_idx] * self.limits[self.omega_idx] - - # Step 2: Apply previously calculated voltage (k) - # (This happens automatically in the environment - we just track what was applied) - - # Step 3: Estimate currents at k+1 - x_estimated = self._estimate_currents( - self._model_constants, x_measured, omega, self.previous_voltage_idx - ) - - # Step 4: Extrapolate future references - ref_i_q_current = reference[0] * self.limits[self.i_sq_idx] - ref_i_d_current = reference[1] * self.limits[self.i_sq_idx] - current_ref = np.array([ref_i_d_current, ref_i_q_current]) - - # Get references for k+2 - ref_k_plus_2 = self._extrapolate_reference(current_ref) - ref_i_d_future, ref_i_q_future = ref_k_plus_2 # Use k+2 reference for prediction - - # Step 5: Predict for k+2 and evaluate cost function + ref_i_q = reference[0] * self.limits[self.i_sq_idx] + ref_i_d = reference[1] * self.limits[self.i_sq_idx] + omega_Isd = omega * x[0] # omega * i_sd + omega_Isq = omega * x[1] # omega * i_sq + + # Unpack model constants + model_constants= self.physical_system.electrical_motor._model_constants + + # Pass everything needed to _simulate_sequence _, best_sequence = self._simulate_sequence( - self._model_constants, x_estimated, omega, - ref_i_d_future, ref_i_q_future, depth=0 + model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 ) - - # Step 6: Select optimal switching state best_idx = best_sequence[0] if best_sequence else 0 - self.previous_voltage_idx = best_idx # Store for next cycle - return best_idx def tune(self, env, env_id, **kwargs): - """Required tuning method""" + """Required tuning method (no tuning needed for MPC)""" pass def reset(self): - """Reset controller state including delay compensation variables""" - self.previous_voltage_idx = 0 - self.past_references = [] \ No newline at end of file + """Reset controller state""" + pass \ No newline at end of file From 76816ebe0864d1d28ccfb7c4b690e39bdab65eed Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 30 Sep 2025 15:32:42 +0200 Subject: [PATCH 28/37] added configurable delay compensation feature --- src/gem_controllers/mpc_current_controller.py | 214 +++++++----------- 1 file changed, 87 insertions(+), 127 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 9c822b3e..8d43d69a 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -1,145 +1,109 @@ import numpy as np from .gem_controller import GemController - class MPCCurrentController(GemController): - """MPC current controller for finite action space control""" - - @property - def signal_names(self): - """Signal names of the calculated values""" - return ["u_MPC"] - - @property - def stages(self): - """List of stages (empty for MPC as it handles everything internally)""" - return [] - - @property - def references(self): - """Reference values of the current control stage""" - return dict() - - @property - def referenced_states(self): - """Referenced states of the current control stage""" - return ['i_sd', 'i_sq'] - - @property - def maximum_reference(self): - """Maximum reference values""" - return {'i_sd': 1.0, 'i_sq': 1.0} - - def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0, Deadtimpstep=0): """ - Initialize MPC current controller - Args: - env: GEM environment - env_id: Environment ID - prediction_horizon: MPC prediction steps - w_d: weight for d-axis current error - w_q: weight for q-axis current error + env: Gym environment instance + env_id: Environment ID string + prediction_horizon: Prediction horizon (N) + w_d: Weight for d-axis current error + w_q: Weight for q-axis current errory + Deadtimpstep = 0 -> without delay compensation + Deadtimpstep = 1 -> with delay compensation """ super().__init__() self.env_id = env_id - - # Get attributes from environment + self.prediction_horizon = prediction_horizon + self.w_d = w_d + self.w_q = w_q + self.step = Deadtimpstep + + # environment info self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau self.limits = self.physical_system.limits - - #motor_type = self.physical_system.electrical_motor.motor_type self.motor_params = self.physical_system.electrical_motor.motor_parameter - - # Store each parameter as an attribute dynamically for key, value in self.motor_params.items(): - setattr(self, key, value) - + setattr(self, key, value) - # State indices + # state indices self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] - # Coordinate transformation + # coordinate transforms self.abc_to_dq = self.physical_system.abc_to_dq_space - self.subactions = -np.power(-1, self.physical_system._converter._subactions) - - # All possible voltage vectors in abc coordinates self.u_abc_k1 = self.u_lim * self.subactions - - self.prediction_horizon = prediction_horizon - # Store weights - self.w_d = w_d - self.w_q = w_q - - print(f"MPCCurrentController initialized with w_d: {self.w_d}, w_q: {self.w_q}") - - # Model constants for MPC - self._model_constants = self.physical_system.electrical_motor._model_constants + # model constants + self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ - - if motor_type == "PermanentMagnetSynchronousMotor": + if motor_type in ["PermanentMagnetSynchronousMotor", "SynchronousReluctanceMotor"]: self.motor_state_names = ["i_sd", "i_sq", "epsilon"] - elif motor_type == "SynchronousReluctanceMotor": - self.motor_state_names = ["i_sd", "i_sq", "epsilon"] else: - raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - - - - def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth): + raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") + + # === delay compensation variables === + self.previous_voltage_idx = 0 + self.past_references = [] + self.extrapolation_order = 2 + + # -------- Delay Compensation Helpers ---------- + def _extrapolate_reference(self, current_ref, n=2): + """Extrapolate future reference.""" + self.past_references.append(current_ref.copy()) + if len(self.past_references) > n + 1: + self.past_references.pop(0) + if len(self.past_references) < n + 1: + return current_ref + ref_k = self.past_references[-1] + ref_km1 = self.past_references[-2] + ref_km2 = self.past_references[-3] + return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 + + def _estimate_currents(self, model_constants, x, omega, voltage_idx): + """Estimate currents at k+1 using the previous optimal voltage.""" + v_abc = self.u_abc_k1[voltage_idx] + v_dq = np.transpose( + np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) + ) + v_d, v_q = v_dq[0], v_dq[1] + omega_Isd = omega * x[0] + omega_Isq = omega * x[1] + ext_vec = np.array([omega, x[0], x[1], float(v_d), float(v_q), omega_Isd, omega_Isq]) + dx = model_constants @ ext_vec + return x + self.tau * dx + + # -------- Prediction / Cost Evaluation ---------- + def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] for idx, (v_a, v_b, v_c) in enumerate(self.u_abc_k1): - # Transform voltages to dq frame v_dq = np.transpose( np.array([self.abc_to_dq(np.array([v_a, v_b, v_c]), x[-1] + 0.5 * omega * self.tau)]) ) v_d, v_q = v_dq[0], v_dq[1] - - # Build input vector: [omega, i_d, i_q, u_d, u_q, omega*i_d, omega*i_q] - exterded_vector = np.array([ - omega, - x[0], # i_d - x[1], # i_q - float(v_d), # u_d - float(v_q), # u_q - omega_Isd, - omega_Isq - ]) - - # Compute derivative - dx = model_constants @ exterded_vector - - # Euler integration for next step + omega_Isd = omega * x[0] + omega_Isq = omega * x[1] + ext_vec = np.array([omega, x[0], x[1], float(v_d), float(v_q), omega_Isd, omega_Isq]) + dx = model_constants @ ext_vec x_next = x + self.tau * dx - # Extract next states - i_d_next, i_q_next, epsilon_next = x_next - omega_Isd_next = omega * i_d_next - omega_Isq_next = omega * i_q_next - - # Calculate Weighted cost - cost = self.w_d * (i_d_next - ref_i_d) ** 2 + self.w_q * (i_q_next - ref_i_q) ** 2 - + cost = self.w_d * (x_next[0] - ref_i_d) ** 2 + self.w_q * (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: - total_cost = cost - sequence = [idx] + total_cost, sequence = cost, [idx] else: future_cost, future_sequence = self._simulate_sequence( - model_constants, x_next, omega, - omega_Isd_next, omega_Isq_next, - ref_i_d, ref_i_q, depth + 1 - ) + model_constants, x_next, omega, ref_i_d, ref_i_q, depth + 1 + ) total_cost = cost + future_cost sequence = [idx] + future_sequence @@ -149,37 +113,33 @@ def _simulate_sequence(self,model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d return min_cost, best_sequence + # -------- Control Interface ---------- def control(self, state, reference): - """Main control method matching PI controller interface""" - - # Build denormalized state vector for the motor (only relevant states) - state_vector = [] - for name in self.motor_state_names: - s_idx = self.state_names.index(name) - state_vector.append(state[s_idx] * self.limits[s_idx]) - x = np.array(state_vector) - - # Denormalize state and references - omega = state[self.omega_idx] * self.limits[self.omega_idx] + # build measured state + x_measured = np.array([state[self.state_names.index(n)] * self.limits[self.state_names.index(n)] + for n in self.motor_state_names]) + omega = state[self.omega_idx] * self.limits[self.omega_idx] ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sq_idx] - omega_Isd = omega * x[0] # omega * i_sd - omega_Isq = omega * x[1] # omega * i_sq - - # Unpack model constants - model_constants= self.physical_system.electrical_motor._model_constants - - # Pass everything needed to _simulate_sequence - _, best_sequence = self._simulate_sequence( - model_constants,x,omega,omega_Isd,omega_Isq, ref_i_d, ref_i_q, depth=0 - ) + ref_i_d = reference[1] * self.limits[self.i_sd_idx] + + if self.step == 0: + # === without delay compensation === + _, best_sequence = self._simulate_sequence( + self._model_constants, x_measured, omega, ref_i_d, ref_i_q, depth=0 + ) + else: + # === with delay compensation === + x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) + ref_k_plus_2 = self._extrapolate_reference(np.array([ref_i_d, ref_i_q])) + ref_i_d_future, ref_i_q_future = ref_k_plus_2 + _, best_sequence = self._simulate_sequence( + self._model_constants, x_est, omega, ref_i_d_future, ref_i_q_future, depth=0 + ) + best_idx = best_sequence[0] if best_sequence else 0 + self.previous_voltage_idx = best_idx return best_idx - def tune(self, env, env_id, **kwargs): - """Required tuning method (no tuning needed for MPC)""" - pass - def reset(self): - """Reset controller state""" - pass \ No newline at end of file + self.previous_voltage_idx = 0 + self.past_references = [] From fa5d30c1711ee1e4004865ef2801fcb42a90b9a8 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Tue, 14 Oct 2025 21:47:06 +0200 Subject: [PATCH 29/37] added DeadTimeProcessor from environment and removed Extrapolation of reference --- src/gem_controllers/mpc_current_controller.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index 8d43d69a..d3935919 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -2,23 +2,27 @@ from .gem_controller import GemController class MPCCurrentController(GemController): - def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0, Deadtimpstep=0): + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): """ Args: env: Gym environment instance env_id: Environment ID string prediction_horizon: Prediction horizon (N) w_d: Weight for d-axis current error - w_q: Weight for q-axis current errory - Deadtimpstep = 0 -> without delay compensation - Deadtimpstep = 1 -> with delay compensation + w_q: Weight for q-axis current error """ super().__init__() self.env_id = env_id self.prediction_horizon = prediction_horizon self.w_d = w_d - self.w_q = w_q - self.step = Deadtimpstep + self.w_q = w_q + + + # Assign self.step from the wrapper + ps_wrapper = env.unwrapped.physical_system + self.step = getattr(ps_wrapper, 'dead_time', 0) # default to 0 if no DeadTimeProcessor + print(f"DeadTimeProcessor steps: {self.step}") + # environment info self.state_names = env.get_wrapper_attr('state_names') @@ -55,8 +59,10 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0, Deadtimp self.extrapolation_order = 2 # -------- Delay Compensation Helpers ---------- + """ + #Extrapolation of reference for delay compensation. This can be used when working the sine-wave reference for e.g. alfa-beta current control. def _extrapolate_reference(self, current_ref, n=2): - """Extrapolate future reference.""" + self.past_references.append(current_ref.copy()) if len(self.past_references) > n + 1: self.past_references.pop(0) @@ -67,6 +73,9 @@ def _extrapolate_reference(self, current_ref, n=2): ref_km2 = self.past_references[-3] return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 + """ + + #current estimation for delay compensation. def _estimate_currents(self, model_constants, x, omega, voltage_idx): """Estimate currents at k+1 using the previous optimal voltage.""" v_abc = self.u_abc_k1[voltage_idx] @@ -129,11 +138,9 @@ def control(self, state, reference): ) else: # === with delay compensation === - x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) - ref_k_plus_2 = self._extrapolate_reference(np.array([ref_i_d, ref_i_q])) - ref_i_d_future, ref_i_q_future = ref_k_plus_2 + x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) _, best_sequence = self._simulate_sequence( - self._model_constants, x_est, omega, ref_i_d_future, ref_i_q_future, depth=0 + self._model_constants, x_est, omega, ref_i_d, ref_i_q, depth=0 ) best_idx = best_sequence[0] if best_sequence else 0 From 17373c675c589f9b63efcc5603b2a156e3efca4d Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Mon, 27 Oct 2025 20:33:10 +0100 Subject: [PATCH 30/37] added comments more clearly and corrected the sequence of Id and Iq --- src/gem_controllers/mpc_current_controller.py | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index d3935919..a34d9982 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -17,14 +17,12 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): self.w_d = w_d self.w_q = w_q - - # Assign self.step from the wrapper + """Assign self.step from the environment wrapper, default to 0 if no DeadTimeProcessor""" ps_wrapper = env.unwrapped.physical_system - self.step = getattr(ps_wrapper, 'dead_time', 0) # default to 0 if no DeadTimeProcessor + self.step = getattr(ps_wrapper, 'dead_time', 0) print(f"DeadTimeProcessor steps: {self.step}") - - # environment info + """Retrieve environment info and motor parameters""" self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau @@ -33,19 +31,19 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): for key, value in self.motor_params.items(): setattr(self, key, value) - # state indices + """Identify indices of key states and inputs""" self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] - # coordinate transforms + """Setup coordinate transforms and precompute voltage combinations""" self.abc_to_dq = self.physical_system.abc_to_dq_space self.subactions = -np.power(-1, self.physical_system._converter._subactions) self.u_abc_k1 = self.u_lim * self.subactions - # model constants + """Load motor model constants and motor-specific state names""" self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ if motor_type in ["PermanentMagnetSynchronousMotor", "SynchronousReluctanceMotor"]: @@ -53,16 +51,15 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): else: raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - # === delay compensation variables === + """Initialize delay compensation variables""" self.previous_voltage_idx = 0 self.past_references = [] self.extrapolation_order = 2 # -------- Delay Compensation Helpers ---------- """ - #Extrapolation of reference for delay compensation. This can be used when working the sine-wave reference for e.g. alfa-beta current control. + # Extrapolate reference for delay compensation using past references. (works for sinusodia only for e.g. alfa-beta frames) def _extrapolate_reference(self, current_ref, n=2): - self.past_references.append(current_ref.copy()) if len(self.past_references) > n + 1: self.past_references.pop(0) @@ -72,12 +69,10 @@ def _extrapolate_reference(self, current_ref, n=2): ref_km1 = self.past_references[-2] ref_km2 = self.past_references[-3] return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 - """ - #current estimation for delay compensation. + """Estimate currents at next timestep using previous voltage for delay compensation""" def _estimate_currents(self, model_constants, x, omega, voltage_idx): - """Estimate currents at k+1 using the previous optimal voltage.""" v_abc = self.u_abc_k1[voltage_idx] v_dq = np.transpose( np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) @@ -90,6 +85,7 @@ def _estimate_currents(self, model_constants, x, omega, voltage_idx): return x + self.tau * dx # -------- Prediction / Cost Evaluation ---------- + """Simulate all possible voltage sequences to find the one minimizing the cost""" def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth): min_cost = float('inf') best_sequence = [] @@ -105,6 +101,7 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth) dx = model_constants @ ext_vec x_next = x + self.tau * dx + """Compute cost based on tracking error""" cost = self.w_d * (x_next[0] - ref_i_d) ** 2 + self.w_q * (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: @@ -123,21 +120,21 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth) return min_cost, best_sequence # -------- Control Interface ---------- + """Compute the best voltage index based on current state and reference""" def control(self, state, reference): - # build measured state x_measured = np.array([state[self.state_names.index(n)] * self.limits[self.state_names.index(n)] for n in self.motor_state_names]) - omega = state[self.omega_idx] * self.limits[self.omega_idx] - ref_i_q = reference[0] * self.limits[self.i_sq_idx] - ref_i_d = reference[1] * self.limits[self.i_sd_idx] + omega = state[self.omega_idx] * self.limits[self.omega_idx] + ref_i_d = reference[0] * self.limits[self.i_sd_idx] + ref_i_q = reference[1] * self.limits[self.i_sq_idx] if self.step == 0: - # === without delay compensation === + """Without delay compensation""" _, best_sequence = self._simulate_sequence( self._model_constants, x_measured, omega, ref_i_d, ref_i_q, depth=0 ) else: - # === with delay compensation === + """With delay compensation""" x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) _, best_sequence = self._simulate_sequence( self._model_constants, x_est, omega, ref_i_d, ref_i_q, depth=0 @@ -147,6 +144,7 @@ def control(self, state, reference): self.previous_voltage_idx = best_idx return best_idx + """Reset delay compensation state""" def reset(self): self.previous_voltage_idx = 0 self.past_references = [] From b5a9001ffb6394260893d0ddd6bc92a5cb13df12 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 29 Oct 2025 13:21:29 +0100 Subject: [PATCH 31/37] added mpc in gem_control.rst --- docs/parts_gc/api_documentation/gem_control.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/parts_gc/api_documentation/gem_control.rst b/docs/parts_gc/api_documentation/gem_control.rst index 5ddd1bae..bf7cd259 100644 --- a/docs/parts_gc/api_documentation/gem_control.rst +++ b/docs/parts_gc/api_documentation/gem_control.rst @@ -9,6 +9,7 @@ GEM Control API Documentation pi_current_controller torque_controller pi_speed_controller + mpc_current_controller gem_adapter reference_plotter utils From 37ebd9a33bb7404f3a8570fa8a77f8e4e9321435 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 29 Oct 2025 15:13:40 +0100 Subject: [PATCH 32/37] changed top level syntax --- .../mpc_current_controller.rst | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/parts_gc/api_documentation/mpc_current_controller.rst b/docs/parts_gc/api_documentation/mpc_current_controller.rst index e5d5ea11..6124b21a 100644 --- a/docs/parts_gc/api_documentation/mpc_current_controller.rst +++ b/docs/parts_gc/api_documentation/mpc_current_controller.rst @@ -8,12 +8,10 @@ FCS-MPC directly evaluates a finite set of switching states to optimize the cont based on a cost function over a prediction horizon. .. figure:: ../../../plots/mpc_structure.png - :align: center - :alt: MPC structure diagram + .. figure:: ../../../plots/mpc_scheme.png - :align: center - :alt: MPC control scheme + With the help of the system model, the output variables are predicted for each possible switching state in the finite control set. The optimizer evaluates a cost function @@ -30,7 +28,7 @@ problem with constraints. MPC Current Controller -********************** +====================== .. autoclass:: gem_controllers.mpc_current_controller.MPCCurrentController :members: @@ -41,7 +39,7 @@ MPC Current Controller Example Usage -************* +============= The following example demonstrates how to apply the :class:`MPCCurrentController` to control a permanent magnet synchronous motor (PMSM) using a finite-control-set MPC approach @@ -140,15 +138,12 @@ within the ``gym-electric-motor`` simulation environment. env.close() Simulation Results -****************** +================== The following figures illustrate the performance of the FCS-MPC controller in current control of the PMSM under varying reference trajectories. The controller accurately tracks both the d- and q-axis current references while ensuring smooth control actions. -.. figure:: ../../../plots/MPC_Time_Plots.png - :align: center - :alt: FCS-MPC tracking performance - :width: 80% +.. figure:: ../../../plots/MPC_Time_Plots.png FCS-MPC current tracking of *id* and *iq*. From 38664efc5fabc1bb92b8b7290de1700437d5132f Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 5 Nov 2025 18:00:16 +0100 Subject: [PATCH 33/37] Force add image file --- docs/plots/mpc_scheme.png | Bin 0 -> 53974 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/plots/mpc_scheme.png diff --git a/docs/plots/mpc_scheme.png b/docs/plots/mpc_scheme.png new file mode 100644 index 0000000000000000000000000000000000000000..6f90ee760daf26a9b03aa410af9aac3b791858e0 GIT binary patch literal 53974 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

n4?OyX7KDB)E1q&GZE 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 TvCIIC00000NkvXXu0mjfEeSAZ literal 0 HcmV?d00001 From 0f296068aea45a22bf124c08830525458c41b066 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 5 Nov 2025 18:08:06 +0100 Subject: [PATCH 34/37] Force add image file --- docs/plots/MPC_Time_Plots.png | Bin 0 -> 118198 bytes docs/plots/mpc_structure.png | Bin 0 -> 26369 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/plots/MPC_Time_Plots.png create mode 100644 docs/plots/mpc_structure.png diff --git a/docs/plots/MPC_Time_Plots.png b/docs/plots/MPC_Time_Plots.png new file mode 100644 index 0000000000000000000000000000000000000000..8d5db4d8dbb623ab312c5f4b7cabb15b1865d697 GIT binary patch literal 118198 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 z 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

LNSNH&W3a%A%1W`h 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

XZ*+_KVyAr ze!*Te04lakb4u^!q0ddyMGb>)F)reY>;Ptw$sM-t)|$=8t5qDha$~mvYC1GDQ~`up z+Ghm2T7z036eK|iX2gGLN~vpbL$d~X7AG-ymh83q_H55v6jNvU(;t8bCt_0KaIy>2 z??(#P0)DC#OymyeaVl)%mJ=M6*i8}RRvx;#gtrC^+%4}2zzk4YI!B2h6PrxLk9lK} z5@$7iJuPOWt!-)0HFh=^7nhpRwVOI_m;r9Bj7l}4#R93Z7f}d(K(xvK&?_HH&T2{H zg@d*Z#sjnz$4ww$LwV*z;PvXga|vQ-u!*gYnl1^N^r6!0mhCBC1`aN23^v1Qy|@tV z$fe zbhf}5=|Gb?-KgvHbLwg|n+6r3Ocd&q(OB4n54&2z@B!?5c~xSISK8v})+byDj}0qS zuyL}3ajxUC3_o9uPdh_~32x8n7_Zi+cx<9Le;drXaZ^Q^j0~IN)D(tn%{Z{HnA5 zzP`;6?z{KHtc|B8NtC-^_P!Nh%0v)((>jY)1=T&(nygrJP~CF6eR-{e=z@snjpvM6 zQMbF7DVZWIQe;1P8-mOf7=r+TyotaX&|d%&#~Bt-x9;p4`^hY z#A6>XX++q5Dse8;2XwS%AIaqI>3H~X9_=}25fZkw8C&yrY-tiZ>`Vk4OdmeODgEGk5xn=qVE*$Ihpny z!^%yb*`;k@2?T0vYrHZL;z}v@h;Y+kyN}(5F5}in|FUjt+Sz+oRy#X8tGNEnNjN$D z>LOfIiHV6K3uARx{yxdZI&fs|o)VQ=($1_YnJw+tzZ$G0DHEEX#xWUnI8#1I>rJLH z6(^P#pcWMeqbX8F{+gwgmAR5|X`=VAvDM7Vxj}D!9z;%vLP#Zx*tPkE04GP;e8V3R z1pUIi&&KSeM;ibdSPoIhgcQ#MLuJt->gR0&4es7ZXCm(}>Fb0$E8gi#tgjj?ldW~i x57IANTUU+w-+nx37dIv+i9>M6v}%4lXPnzJ=M6C)t-RAly*_uLZ=O$R{GUMdlsy0d literal 0 HcmV?d00001 From 7e7c4e27061c7b054fc1904189743b5714f1f969 Mon Sep 17 00:00:00 2001 From: Manjeet Jha Date: Wed, 5 Nov 2025 20:35:03 +0100 Subject: [PATCH 35/37] Fix figure paths in mpc_current_controller.rst --- docs/parts_gc/api_documentation/mpc_current_controller.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/parts_gc/api_documentation/mpc_current_controller.rst b/docs/parts_gc/api_documentation/mpc_current_controller.rst index 6124b21a..71146a82 100644 --- a/docs/parts_gc/api_documentation/mpc_current_controller.rst +++ b/docs/parts_gc/api_documentation/mpc_current_controller.rst @@ -7,10 +7,10 @@ Motor (SRM) in rotor field-oriented coordinates. Unlike continuous-control set ( FCS-MPC directly evaluates a finite set of switching states to optimize the control input based on a cost function over a prediction horizon. -.. figure:: ../../../plots/mpc_structure.png +.. figure:: ../../plots/mpc_structure.png -.. figure:: ../../../plots/mpc_scheme.png +.. figure:: ../../plots/mpc_scheme.png With the help of the system model, the output variables are predicted for each possible @@ -143,7 +143,7 @@ Simulation Results The following figures illustrate the performance of the FCS-MPC controller in current control of the PMSM under varying reference trajectories. The controller accurately tracks both the d- and q-axis current references while ensuring smooth control actions. -.. figure:: ../../../plots/MPC_Time_Plots.png +.. figure:: ../../plots/MPC_Time_Plots.png FCS-MPC current tracking of *id* and *iq*. From 71a73802392fea498fcf33739b8510abb85c71a2 Mon Sep 17 00:00:00 2001 From: manjeetjha070 Date: Wed, 5 Nov 2025 20:59:27 +0100 Subject: [PATCH 36/37] docs: Add docstrings to MPC controller --- .../mpc_current_controller.rst | 4 + src/gem_controllers/mpc_current_controller.py | 114 +++++++++++++----- 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/docs/parts_gc/api_documentation/mpc_current_controller.rst b/docs/parts_gc/api_documentation/mpc_current_controller.rst index 71146a82..9af16e1a 100644 --- a/docs/parts_gc/api_documentation/mpc_current_controller.rst +++ b/docs/parts_gc/api_documentation/mpc_current_controller.rst @@ -93,6 +93,8 @@ within the ``gym-electric-motor`` simulation environment. ActionType.Finite ) + #physical_system_wrapper = [DeadTimeProcessor(steps=1)] # Dead time processor with 1 step delay + #uncomment the above line to activate the DeadTimeProcessor env = gem.make( motor.env_id(), visualization=visu, @@ -108,6 +110,8 @@ within the ``gym-electric-motor`` simulation environment. limit_values=limit_values, nominal_values=nominal_values ), + #physical_system_wrappers=physical_system_wrapper, + #uncomment the above line to activate the DeadTimeProcessor ) visu.initialize() diff --git a/src/gem_controllers/mpc_current_controller.py b/src/gem_controllers/mpc_current_controller.py index a34d9982..29e2b174 100644 --- a/src/gem_controllers/mpc_current_controller.py +++ b/src/gem_controllers/mpc_current_controller.py @@ -2,27 +2,33 @@ from .gem_controller import GemController class MPCCurrentController(GemController): + """ + Finite-Control Set Model Predictive Current Controller for PMSM and SynRM motors. + + This controller implements FCS-MPC to directly evaluate switching states + and optimize current tracking in field-oriented coordinates. + + Args: + env: Gym environment instance + env_id: Environment ID string + prediction_horizon: Prediction horizon length (default: 1) + w_d: Weight for d-axis current error (default: 1.0) + w_q: Weight for q-axis current error (default: 1.0) + """ + def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): - """ - Args: - env: Gym environment instance - env_id: Environment ID string - prediction_horizon: Prediction horizon (N) - w_d: Weight for d-axis current error - w_q: Weight for q-axis current error - """ super().__init__() self.env_id = env_id self.prediction_horizon = prediction_horizon self.w_d = w_d self.w_q = w_q - """Assign self.step from the environment wrapper, default to 0 if no DeadTimeProcessor""" + # Assign self.step from the environment wrapper, default to 0 if no DeadTimeProcessor ps_wrapper = env.unwrapped.physical_system self.step = getattr(ps_wrapper, 'dead_time', 0) print(f"DeadTimeProcessor steps: {self.step}") - """Retrieve environment info and motor parameters""" + # Retrieve environment info and motor parameters self.state_names = env.get_wrapper_attr('state_names') self.physical_system = env.get_wrapper_attr('physical_system') self.tau = self.physical_system.tau @@ -31,19 +37,19 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): for key, value in self.motor_params.items(): setattr(self, key, value) - """Identify indices of key states and inputs""" + # Identify indices of key states and inputs self.i_sd_idx = self.state_names.index('i_sd') self.i_sq_idx = self.state_names.index('i_sq') self.omega_idx = self.state_names.index('omega') self.u_sd_idx = self.state_names.index('u_sd') self.u_lim = self.limits[self.u_sd_idx] - """Setup coordinate transforms and precompute voltage combinations""" + # Setup coordinate transforms and precompute voltage combinations self.abc_to_dq = self.physical_system.abc_to_dq_space self.subactions = -np.power(-1, self.physical_system._converter._subactions) self.u_abc_k1 = self.u_lim * self.subactions - """Load motor model constants and motor-specific state names""" + # Load motor model constants and motor-specific state names self._model_constants = self.physical_system.electrical_motor._model_constants motor_type = type(self.physical_system.electrical_motor).__name__ if motor_type in ["PermanentMagnetSynchronousMotor", "SynchronousReluctanceMotor"]: @@ -51,15 +57,31 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0): else: raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}") - """Initialize delay compensation variables""" + # Initialize delay compensation variables self.previous_voltage_idx = 0 self.past_references = [] self.extrapolation_order = 2 - # -------- Delay Compensation Helpers ---------- - """ - # Extrapolate reference for delay compensation using past references. (works for sinusodia only for e.g. alfa-beta frames) def _extrapolate_reference(self, current_ref, n=2): + """ + [REFERENCE IMPLEMENTATION - Not currently active] + + Extrapolate reference for delay compensation using past references. + Works for sinusoidal signals in alpha-beta frames. + + This method uses polynomial extrapolation to predict future reference + values based on past samples. Useful for systems with computational delays. + + Args: + current_ref: Current reference value + n: Extrapolation order (default: 2 for quadratic extrapolation) + + Returns: + extrapolated_ref: Extrapolated reference value + """ + # Implementation kept as reference for future use + # Uncomment and modify as needed for specific applications + """ self.past_references.append(current_ref.copy()) if len(self.past_references) > n + 1: self.past_references.pop(0) @@ -69,10 +91,23 @@ def _extrapolate_reference(self, current_ref, n=2): ref_km1 = self.past_references[-2] ref_km2 = self.past_references[-3] return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2 - """ + """ + # Currently using state estimation instead of reference extrapolation + return current_ref - """Estimate currents at next timestep using previous voltage for delay compensation""" def _estimate_currents(self, model_constants, x, omega, voltage_idx): + """ + Estimate currents at next timestep using previous voltage for delay compensation. + + Args: + model_constants: Motor model constants matrix + x: Current state vector [i_sd, i_sq, epsilon] + omega: Electrical rotor speed + voltage_idx: Index of previously applied voltage vector + + Returns: + Estimated state vector at next timestep + """ v_abc = self.u_abc_k1[voltage_idx] v_dq = np.transpose( np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)]) @@ -84,9 +119,22 @@ def _estimate_currents(self, model_constants, x, omega, voltage_idx): dx = model_constants @ ext_vec return x + self.tau * dx - # -------- Prediction / Cost Evaluation ---------- - """Simulate all possible voltage sequences to find the one minimizing the cost""" def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth): + """ + Simulate all possible voltage sequences to find the one minimizing the cost. + + Args: + model_constants: Motor model constants matrix + x: Current state vector + omega: Electrical rotor speed + ref_i_d: D-axis current reference + ref_i_q: Q-axis current reference + depth: Current depth in prediction horizon + + Returns: + min_cost: Minimum cost found + best_sequence: Best voltage sequence + """ min_cost = float('inf') best_sequence = [] @@ -101,7 +149,7 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth) dx = model_constants @ ext_vec x_next = x + self.tau * dx - """Compute cost based on tracking error""" + # Compute cost based on tracking error cost = self.w_d * (x_next[0] - ref_i_d) ** 2 + self.w_q * (x_next[1] - ref_i_q) ** 2 if depth == self.prediction_horizon - 1: @@ -119,9 +167,17 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth) return min_cost, best_sequence - # -------- Control Interface ---------- - """Compute the best voltage index based on current state and reference""" def control(self, state, reference): + """ + Compute the optimal voltage vector based on current state and reference. + + Args: + state: Current motor state vector + reference: Current reference vector [i_sd_ref, i_sq_ref] + + Returns: + best_idx: Index of optimal voltage vector + """ x_measured = np.array([state[self.state_names.index(n)] * self.limits[self.state_names.index(n)] for n in self.motor_state_names]) omega = state[self.omega_idx] * self.limits[self.omega_idx] @@ -129,12 +185,12 @@ def control(self, state, reference): ref_i_q = reference[1] * self.limits[self.i_sq_idx] if self.step == 0: - """Without delay compensation""" + # Without delay compensation _, best_sequence = self._simulate_sequence( self._model_constants, x_measured, omega, ref_i_d, ref_i_q, depth=0 ) else: - """With delay compensation""" + # With delay compensation x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx) _, best_sequence = self._simulate_sequence( self._model_constants, x_est, omega, ref_i_d, ref_i_q, depth=0 @@ -144,7 +200,9 @@ def control(self, state, reference): self.previous_voltage_idx = best_idx return best_idx - """Reset delay compensation state""" def reset(self): + """ + Reset the controller state including delay compensation variables. + """ self.previous_voltage_idx = 0 - self.past_references = [] + self.past_references = [] \ No newline at end of file From 54094ee49b50dad7ac78d24d2dc3019fc67fe009 Mon Sep 17 00:00:00 2001 From: HauckeBa Date: Fri, 19 Dec 2025 16:29:58 +0100 Subject: [PATCH 37/37] modified changelog and pyptoject toml to make a new realease after merge of the changes in this branch. --- CHANGELOG.md | 10 +++++++--- pyproject.toml | 5 ++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b275656..593a5860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Changed ## Fixed -## [3.0.3] - unreleased +## [3.0.3] - 2025-12-19 ## Added - Automated testing of the existing examples - Automated testing of the electric motors - 2x3 phase PMSM environment with tests and documentation +- finite-control-set MPC example +- finite-control-set model predicitve current control as base current control in gem-control for PMSM and SynRM (Currently working not with superimposed speed and/or torque control) +- merged gem-control documentation into the gem docs ## Changed - Changed minimal required gymnasium version to 0.29.1. -- updated the code of gem-control to be compatible with gymnasium v1.0.0 +- Updated the code of gem-control to be compatible with gymnasium v1.0.0 ## Fixed - Updated syntax in the classic_controllers to run with gymnasium v1.0.0 -- #263 updated the sb3, mpc and gem-control examples to run with gymnasium v1.0.0 +- #263 updated the sb3, mpc and gem-control examples to run with gymnasium v1.0.0 +- exchanged widget to ipympl in the examples to run the visualization in visual studio code ## [3.0.2] - 2024-11-19 ## Added diff --git a/pyproject.toml b/pyproject.toml index 9680cebc..35874539 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gym_electric_motor" -version = "3.0.2" +version = "3.0.3" authors = [ { name = "Arne Traue" }, { name = "Gerrit Book" }, @@ -16,6 +16,9 @@ authors = [ { name = "Stefan Arndt" }, { name = "Marius Köhler" }, { name = "Ranil Thomas" }, + { name = "Manjeet Jha" }, + { name = "Valliammai Annamalai" }, + { name = "Mounica Vadivelu" }, ] description = "A Farama Gymnasium environment for electric motor control." readme = "README.md"