diff --git a/examples/06_Combining_Models.ipynb b/examples/06_Combining_Models.ipynb index 8c858ac..c75699f 100644 --- a/examples/06_Combining_Models.ipynb +++ b/examples/06_Combining_Models.ipynb @@ -6,6 +6,250 @@ "source": [ "# Combining Prognostic Models" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This section demonstrates how prognostic models can be combined. There are two times in which this is useful: \n", + "\n", + "1. When combining multiple models of different inter-related systems into one system-of-system model (i.e., [Composite Models](https://nasa.github.io/progpy/api_ref/prog_models/CompositeModel.html)), or\n", + "2. Combining multiple models of the same system to be simulated together and aggregated (i.e., [Ensemble Models](https://nasa.github.io/progpy/api_ref/prog_models/EnsembleModel.html)). This is generally done to improve the accuracy of prediction when you have multiple models that each represent part of the behavior or represent a distribution of different behaviors. \n", + "\n", + "These two methods for combining models are described in the following sections." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Composite Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A CompositeModel is a PrognosticsModel that is composed of multiple PrognosticsModels. This is a tool for modeling system-of-systems. i.e., interconnected systems, where the behavior and state of one system affects the state of another system. The composite prognostics models are connected using defined connections between the output or state of one model, and the input of another model. The resulting CompositeModel behaves as a single model.\n", + "\n", + "To illustrate this, we will create a composite model of an aircraft's electric powertrain, combining the DCMotor, ESC, and PropellerLoad models. The Electronic Speed Controller (ESC) converts a commanded duty (i.e., throttle) to signals to the motor. The motor then acts on the signals from the ESC to spin the load, which enacts a torque on the motor (in this case from air resistence).\n", + "\n", + "First we will import the used models, and the CompositeModel class" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import DCMotor, ESC, PropellerLoad\n", + "from progpy import CompositeModel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we will initiate objects of the individual models that will later create the composite powertrain model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m_motor = DCMotor()\n", + "m_esc = ESC()\n", + "m_load = PropellerLoad()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we have to define the connections between the systems. Let's first define the connections from the DCMotor to the propeller load. For this, we'll need to look at the DCMotor states and understand how they influence the PropellerLoad inputs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('motor states: ', m_motor.states)\n", + "print('load inputs: ', m_load.inputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each of the states and inputs are described in the model documentation at [DC Motor Docs](https://nasa.github.io/progpy/api_ref/prog_models/IncludedModels.html#dc-motor) and [Propeller Docs](https://nasa.github.io/progpy/api_ref/prog_models/IncludedModels.html#propellerload)\n", + "\n", + "From reading the documentation we understand that the propeller's velocity is from the motor, so we can define the first connection:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "connections = [\n", + " ('DCMotor.v_rot', 'PropellerLoad.v_rot')\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Connections are defined as couples where the first value is the input for the second value. The connection above tells the composite model to feed the DCMotor's v_rot into the PropellerLoad's input v_rot.\n", + "\n", + "Next, let's look at the connections the other direction, from the load to the motor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('load states: ', m_load.states)\n", + "print('motor inputs: ', m_motor.inputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We know here that the load on the motor is from the propeller load, so we can add that connection. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "connections.append(('PropellerLoad.t_l', 'DCMotor.t_l'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we will repeat the exercise with the DCMotor and ESC." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('ESC states: ', m_esc.states)\n", + "print('motor inputs: ', m_motor.inputs)\n", + "connections.append(('ESC.v_a', 'DCMotor.v_a'))\n", + "connections.append(('ESC.v_b', 'DCMotor.v_b'))\n", + "connections.append(('ESC.v_c', 'DCMotor.v_c'))\n", + "\n", + "print('motor states: ', m_motor.states)\n", + "print('ESC inputs: ', m_esc.inputs)\n", + "connections.append(('DCMotor.theta', 'ESC.theta'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we are ready to combine the models. We create a composite model with the inidividual models and the defined connections." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m_powertrain = CompositeModel(\n", + " (m_esc, m_load, m_motor), \n", + " connections=connections)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The resulting model includes two inputs, ESC voltage (from the battery) and duty (i.e., commanded throttle). These are the only two inputs not connected internally from the original three models. The states are a combination of all the states of every system. Finally, the outputs are a combination of all the outputs from each of the individual systems. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('inputs: ', m_powertrain.inputs)\n", + "print('states: ', m_powertrain.states)\n", + "print('outputs: ', m_powertrain.outputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Frequently users only want a subset of the outputs from the original model. For example, in this case you're unlikely to be measuring the individual voltages from the ESC. Outputs can be specified when creating the composite model. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m_powertrain = CompositeModel(\n", + " (m_esc, m_load, m_motor), \n", + " connections=connections,\n", + " outputs={'DCMotor.v_rot', 'DCMotor.theta'})\n", + "print('outputs: ', m_powertrain.outputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the outputs are only DCMotor angle and velocity.\n", + "\n", + "The resulting model can be used in simulation, state estimation, and prediction the same way any other model would be, as demonstrated below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "load = m_powertrain.InputContainer({\n", + " 'ESC.duty': 1, # 100% Throttle\n", + " 'ESC.v': 23\n", + " })\n", + "def future_loading(t, x=None):\n", + " return load\n", + "\n", + "simulated_results = m_powertrain.simulate_to(2, future_loading, dt=2.5e-5, save_freq=1e-2)\n", + "fig = simulated_results.outputs.plot(compact=False, keys=['DCMotor.v_rot'], ylabel='Velocity')\n", + "fig = simulated_results.states.plot(keys=['DCMotor.i_b', 'DCMotor.i_c', 'DCMotor.i_a'], ylabel='ESC Currents')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ensemble Model" + ] } ], "metadata": { @@ -15,7 +259,15 @@ "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.11.0" }, "orig_nbformat": 4, diff --git a/examples/composite_model.py b/examples/composite_model.py deleted file mode 100644 index 10fa6ca..0000000 --- a/examples/composite_model.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. - -""" -Example illustrating how to use the CompositeModel class to create a composite model from multiple models. - -This example creates a composite model of a DC motor with an Electronic Speed Controller and a propeller load. The three composite models are interrelated. The created composite model describes the nature of these interconnections. The resulting powertrain model is then simulated forward with time and the results are plotted. -""" - -from progpy.models import DCMotor, ESC, PropellerLoad -from progpy import CompositeModel - -def run_example(): - # First, lets define the composite models - m_motor = DCMotor() - m_esc = ESC() - m_load = PropellerLoad() - - # Now let's combine them into a single composite model describing the behavior of a powertrain - # This model will then behave as a single model - m_powertrain = CompositeModel( - (m_esc, m_load, m_motor), - connections = [ - ('DCMotor.theta', 'ESC.theta'), - ('ESC.v_a', 'DCMotor.v_a'), - ('ESC.v_b', 'DCMotor.v_b'), - ('ESC.v_c', 'DCMotor.v_c'), - ('PropellerLoad.t_l', 'DCMotor.t_l'), - ('DCMotor.v_rot', 'PropellerLoad.v_rot')], - outputs = {'DCMotor.v_rot', 'DCMotor.theta'}) - - # Print out the inputs, states, and outputs of the composite model - print('Composite model of DCMotor, ESC, and Propeller load') - print('inputs: ', m_powertrain.inputs) - print('states: ', m_powertrain.states) - print('outputs: ', m_powertrain.outputs) - - # Define future loading function - 100% duty all the time - def future_loading(t, x=None): - return m_powertrain.InputContainer({ - 'ESC.duty': 1, - 'ESC.v': 23 - }) - - # Simulate to threshold - print('\n\n------------------------------------------------') - print('Simulating to threshold\n\n') - simulated_results = m_powertrain.simulate_to(2, future_loading, dt=2e-5, save_freq=0.1, print=True) - - simulated_results.outputs.plot() - -if __name__ == '__main__': - run_example() diff --git a/src/progpy/models/propeller_load.py b/src/progpy/models/propeller_load.py index 28eb6b8..488bc59 100644 --- a/src/progpy/models/propeller_load.py +++ b/src/progpy/models/propeller_load.py @@ -19,6 +19,17 @@ class PropellerLoad(PrognosticsModel): This is a simple model of a propeller load. This model estimates load torque as a function of the rotational velocity. When the propeller is spinning faster, drag increases, and the propeller load on the torque increases. This model is typically used with the esc and dcmotor models to simulate a motor and propeller system. + + :term:`Events`: (0) + + :term:`Inputs/Loading`: (1): + v_rot: Rotational Velocity (rad/sec) + + :term:`States`: (1) + t_l: Load Torque + + :term:`Outputs`: (1): + t_l: Load Torque """ inputs = ['v_rot'] states = ['t_l']