From b411f9b1ef34f7fe1f558a42e88cd68117979704 Mon Sep 17 00:00:00 2001 From: Christopher Teubert Date: Wed, 23 Aug 2023 16:24:38 -0700 Subject: [PATCH 1/2] Add dt example --- examples/01_Simulation.ipynb | 211 ++++++++++++++++++++++++++++++++++ examples/dynamic_step_size.py | 55 --------- 2 files changed, 211 insertions(+), 55 deletions(-) delete mode 100644 examples/dynamic_step_size.py diff --git a/examples/01_Simulation.ipynb b/examples/01_Simulation.ipynb index 88951f9..913c93a 100644 --- a/examples/01_Simulation.ipynb +++ b/examples/01_Simulation.ipynb @@ -926,6 +926,217 @@ "## Step Size" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next configurable parameter in simulation is the step size, or `dt`. This is the size of the step taken from one step to the next when simulating. Smaller step sizes will usually more accurately simulate state evolution, but at a computational cost. Conversely, some models can become unstable at large step sizes. Chosing the correct step size is important to the success of a simulation or prediction.\n", + "\n", + "In this section we will introduce the concept of setting simulation step size (`dt`) and discuss some considerations when selecting step sizes.\n", + "\n", + "For this section we will use the `progpy.models.ThrownObject model` (see 3. Included models), imported and created below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import ThrownObject\n", + "m = ThrownObject()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic Step Size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To set step size, set the dt parameter in the `simulate_to` or `simulate_to_threshold` methods. In this example we will use a large and small step size and compare the results.\n", + "\n", + "First, let's simulate with a large step size, saving the result at every step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results = m.simulate_to_threshold(\n", + " threshold_keys='impact',\n", + " dt=2.5,\n", + " save_freq=2.5)\n", + "fig = results.outputs.plot(ylabel='Position (m)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the parabola above is jagged. Also note that the estimated time of impact is around 10 seconds and the maximum height is a little over 120 meters. \n", + "\n", + "Now lets run the simulation again with a smaller step size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results = m.simulate_to_threshold(\n", + " threshold_keys='impact',\n", + " dt=0.25,\n", + " save_freq=0.25)\n", + "fig = results.outputs.plot(ylabel='Position (m)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Not only is the curve much smoother with a smaller step size, but the results are significantly different. Now the time of impact is closer to 8 seconds and maximum height closer to 80 meters.\n", + "\n", + "All simulations are approximations. The example with the larger step size accumulates more error in integration. The second example (with a smaller step size) is more accurate to the actual model behavior.\n", + "\n", + "Now let's decrease the step size even more" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results = m.simulate_to_threshold(\n", + " threshold_keys='impact',\n", + " dt=0.05,\n", + " save_freq=0.05)\n", + "fig = results.outputs.plot(ylabel='Position (m)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The resulting output is different than the 0.25 second step size run, but not by much. What you see here is the diminishing returns in decreasing step size.\n", + "\n", + "The smaller the step size, the more computational resources required to simulate it. This doesn't matter as much for simulating this simple model over a short horizon, but becomes very important when performing prediction (see 9. Prediction), using a complex model with a long horizon, or when operating in a computationally constrained environment (e.g., embedded)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dynamic Step Size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The last section introduced step size and showed how changing the step size effects the simulation results. In the last example step size (`dt`) and `save_freq` were the same, meaning each point was captured exactly. This is not always the case, for example in the case below. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results = m.simulate_to_threshold(\n", + " threshold_keys='impact',\n", + " dt=1,\n", + " save_freq=1.5)\n", + "print('Times saved: ', results.times)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With a `save_freq` of 1.5 seconds you might expect the times saved to be 0, 1.5, 3, 4.5, ..., but that's not the case. This is because the timestep is 1 second, so the simulation never stops near 1.5 seconds to record it. 'auto' stepsize can help with this.\n", + "\n", + "To use 'auto' stepsize set `dt` to a tuple of ('auto', MAX) where MAX is replaced with the maximum allowable stepsize." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results = m.simulate_to_threshold(\n", + " threshold_keys='impact',\n", + " dt=('auto', 1),\n", + " save_freq=1.5)\n", + "print('Times saved: ', results.times)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We repeated the simulation using automatic step size with a maximum step size of 1. The result was that the times where state was saved matched what was requested exactly. This is important for simulations with large step sizes where there are specific times that must be captured.\n", + "\n", + "Also note that automatic step size doesn't just adjust for `save_freq`. It will also adjust to meet `save_pts` and any transition points in a Piecewise loading profile." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom Step Size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are times when an advanced user would like more flexibility in selecting step sizes. This can be used to adjust step size dynamically close to events or times of interest. In some models, there are complex behaviors during certain parts of the life of the system that require more precise simulation. For example, the knee point in the voltage profile for a discharged batter. This can be done by providing a function (t, x)->dt instead of a scalar `dt`. \n", + "\n", + "For example, if a user wanted to reduce the step size closer to impact, they could do so like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def next_time(t, x):\n", + " # In this example dt is a function of state. Uses a dt of 1 until impact event state 0.25, then 0.25\n", + " event_state = m.event_state(x)\n", + " if event_state['impact'] < 0.25:\n", + " return 0.25\n", + " return 1\n", + "\n", + "results=m.simulate_to_threshold(dt=next_time, save_freq= 0.25, threshold_keys='impact')\n", + "\n", + "print(results.times)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that after 8 seconds the step size decreased to 0.25 seconds, as expected." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parameters" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/dynamic_step_size.py b/examples/dynamic_step_size.py deleted file mode 100644 index 74f8069..0000000 --- a/examples/dynamic_step_size.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. - -""" -Example demonstrating ways to use the dynamic step size feature. This feature allows users to define a time-step that changes with time or state. -""" - -from progpy.models.thrown_object import ThrownObject - -def run_example(): - print("EXAMPLE 1: dt of 1 until 8 sec, then 0.5\n\nSetting up...\n") - # Step 1: Create instance of model - m = ThrownObject() - - # Step 2: Setup for simulation - def future_load(t, x=None): - return {} - - # Step 3: Define dynamic step size function - # This `next_time` function will specify what the next step of the simulation should be at any state and time. - # f(x, t) -> (t, dt) - def next_time(t, x): - # In this example dt is a function of time. We will use a dt of 1 for the first 8 seconds, then 0.5 - if t < 8: - return 1 - return 0.5 - - # Step 4: Simulate to impact - # Here we're printing every time step so we can see the step size change - print('\n\n------------------------------------------------') - print('Simulating to threshold\n\n') - (times, inputs, states, outputs, event_states) = m.simulate_to_threshold(future_load, save_freq=1e-99, print=True, dt=next_time, threshold_keys=['impact']) - - # Example 2 - print("EXAMPLE 2: dt of 1 until impact event state 0.5, then 0.25 \n\nSetting up...\n") - - # Step 3: Define dynamic step size function - # This `next_time` function will specify what the next step of the simulation should be at any state and time. - # f(x, t) -> (t, dt) - def next_time(t, x): - # In this example dt is a function of state. Uses a dt of 1 until impact event state 0.5, then 0.25 - event_state = m.event_state(x) - if event_state['impact'] < 0.5: - return 0.25 - return 1 - - # Step 4: Simulate to impact - # Here we're printing every time step so we can see the step size change - print('\n\n------------------------------------------------') - print('Simulating to threshold\n\n') - (times, inputs, states, outputs, event_states) = m.simulate_to_threshold(future_load, save_freq=1e-99, print=True, dt=next_time, threshold_keys=['impact']) - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() From 0ac2d5427b83cf63ea4bd858970ce8a3bc2a10fb Mon Sep 17 00:00:00 2001 From: Christopher Teubert Date: Fri, 15 Sep 2023 08:44:33 -0700 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Katy Jarvis Griffith <55932920+kjjarvis@users.noreply.github.com> --- examples/01_Simulation.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/01_Simulation.ipynb b/examples/01_Simulation.ipynb index 913c93a..6d1a84a 100644 --- a/examples/01_Simulation.ipynb +++ b/examples/01_Simulation.ipynb @@ -930,7 +930,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The next configurable parameter in simulation is the step size, or `dt`. This is the size of the step taken from one step to the next when simulating. Smaller step sizes will usually more accurately simulate state evolution, but at a computational cost. Conversely, some models can become unstable at large step sizes. Chosing the correct step size is important to the success of a simulation or prediction.\n", + "The next configurable parameter in simulation is the step size, or `dt`. This is the size of the step taken from one step to the next when simulating. Smaller step sizes will usually more accurately simulate state evolution, but at a computational cost. Conversely, some models can become unstable at large step sizes. Choosing the correct step size is important to the success of a simulation or prediction.\n", "\n", "In this section we will introduce the concept of setting simulation step size (`dt`) and discuss some considerations when selecting step sizes.\n", "\n", @@ -982,7 +982,7 @@ "source": [ "Note that the parabola above is jagged. Also note that the estimated time of impact is around 10 seconds and the maximum height is a little over 120 meters. \n", "\n", - "Now lets run the simulation again with a smaller step size." + "Now let's run the simulation again with a smaller step size." ] }, { @@ -1100,7 +1100,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There are times when an advanced user would like more flexibility in selecting step sizes. This can be used to adjust step size dynamically close to events or times of interest. In some models, there are complex behaviors during certain parts of the life of the system that require more precise simulation. For example, the knee point in the voltage profile for a discharged batter. This can be done by providing a function (t, x)->dt instead of a scalar `dt`. \n", + "There are times when an advanced user would like more flexibility in selecting step sizes. This can be used to adjust step size dynamically close to events or times of interest. In some models, there are complex behaviors during certain parts of the life of the system that require more precise simulation. For example, the knee point in the voltage profile for a discharged battery. This can be done by providing a function (t, x)->dt instead of a scalar `dt`. \n", "\n", "For example, if a user wanted to reduce the step size closer to impact, they could do so like this:" ]