{ "cells": [ { "cell_type": "markdown", "id": "5d5178dd", "metadata": {}, "source": [ "# Model simulation and fitting\n", "This Notebook demonstrate the use and basic functionality of the model simulation and parameter fitting capabilities of BalancePy." ] }, { "cell_type": "code", "execution_count": 1, "id": "af92f4ef", "metadata": {}, "outputs": [], "source": [ "# Import balancepy and necessary libraries\n", "\n", "import balancepy as bp\n", "import numpy as np\n", "from balancepy.model_sim.peterka18 import Peterka18 as P18\n", "\n", "# render plots as static image - only needed for documentation\n", "import plotly.io as pio\n", "pio.renderers.default = \"svg\"\n", "\n", "# Set the path containing example data\n", "data_path = 'data/'" ] }, { "cell_type": "markdown", "id": "6400e828", "metadata": {}, "source": [ "To initialize a model instance height and weight are required." ] }, { "cell_type": "code", "execution_count": 2, "id": "17ede7bc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "balancepyModel(ModelName=Peterka 2018,\n", " ParameterSet( \n", " mgh: value=8.05, bounds=(10, 20), fixed=True,\n", " J: value=0.9, bounds=(0, 0), fixed=True,\n", " Kp: value=11.28, bounds=(8.457096571348341, 20.13594421749605), fixed=False,\n", " Kd: value=3.22, bounds=(0.805437768699842, 8.05437768699842), fixed=False,\n", " W: value=0.45, bounds=(0.01, 1), fixed=False,\n", " dt: value=0.16, bounds=(0.1, 0.3), fixed=False,\n", " Kt: value=0.01, bounds=(0, 0.05), fixed=False\n", " ),\n", " Frequencies=250 frequencies from 0.01 to 2.5,\n", " Fit Reference=Not defined,\n", " Sampling Rate=Not defined)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Define subject antrhopometry\n", "weight = 53\n", "height = 1.66\n", "\n", "# create a model instance for the subject\n", "model_surf = P18(weight, height)\n", "\n", "# show default model parameters (not tuned to experimental data)\n", "model_surf" ] }, { "cell_type": "markdown", "id": "78a15fc8", "metadata": {}, "source": [ "As no further information was given, the model contains only the frequency response function for the default parameters, which depend on the subjects height and weight. Calling a plot of the model displays the frequency response function." ] }, { "cell_type": "code", "execution_count": 3, "id": "e20a717a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "No experimental data available for plotting\n" ] }, { "data": { "image/svg+xml": [ "0.01250.1251200.511.520.01250.12512−200−1000SimulatedBode PlotStimulus Time SeriesResponse Time SeriesBode Magnitude PlotCoherence PlotBode Phase PlotParameters" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "model_surf.plot()" ] }, { "cell_type": "markdown", "id": "83c50e42", "metadata": {}, "source": [ "The model can be used to run a stimulus in the time domain using self.simulate_timedomain(stimulus, samplingrate). The function outputs a stimulus-response dataclass object (sr_data), which can be used to generate a plot of the output." ] }, { "cell_type": "code", "execution_count": 4, "id": "b05de7b1", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "05101500.5051015−0.500.51Bode PlotStimulus Time SeriesResponse Time SeriesBode Magnitude PlotCoherence PlotBode Phase PlotParameters" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Run time domain simulations\n", "\n", "# Define a stimulus\n", "samplingrate = 100 # Hz\n", "stim = bp.make_prts(type=\"prts_20s\", samplingrate_Hz=samplingrate)\n", "time = np.arange(0, len(stim) / samplingrate, 1 / samplingrate)\n", "\n", "# Run time domain simulation\n", "data_sim = model_surf.simulate_timedomain(stim, samplingrate)\n", "\n", "data_sim.plot()\n" ] }, { "cell_type": "markdown", "id": "25d6fcd0", "metadata": {}, "source": [ "In the following example, experimental data is added to the model and the model parameters are fitted to this reference dataset." ] }, { "cell_type": "code", "execution_count": 5, "id": "be7b95b9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "balancepyModel(ModelName=Peterka 2018,\n", " ParameterSet( \n", " mgh: value=8.05, bounds=(10, 20), fixed=True,\n", " J: value=0.9, bounds=(0, 0), fixed=True,\n", " Kp: value=10.83, bounds=(8.457096571348341, 20.13594421749605), fixed=False,\n", " Kd: value=3.89, bounds=(0.805437768699842, 8.05437768699842), fixed=False,\n", " W: value=0.58, bounds=(0.01, 1), fixed=False,\n", " dt: value=0.16, bounds=(0.1, 0.3), fixed=False,\n", " Kt: value=0.01, bounds=(0, 0.05), fixed=False\n", " ),\n", " Frequencies=25 frequencies from 0.05 to 2.4499999999999997,\n", " Fit Reference=Defined as data_exp.frf,\n", " Sampling Rate=90 Hz)\n" ] }, { "data": { "image/svg+xml": [ "05101500.5051015−2−10150.1251201250.1251200.550.12512−400−2000ExperimentalSimulatedBode PlotStimulus Time SeriesResponse Time SeriesBode Magnitude PlotCoherence PlotBode Phase PlotParameters" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Experiment consisted of a simultaneous visual and surface tilt perturbation\n", "# here, only the surface tilt is analyzed and used for fitting\n", "\n", "# Load experimental data\n", "com, stim, time = bp.getdata_legacy(\n", " (data_path+'d1_vis1_surf1.csv'), \n", " height, \n", " resample = True, \n", " cut_to_cycles = True,\n", " end_time = 220,\n", " cycle_start_samples = 20*90,\n", " cycle_length_samples = 20*90,\n", " # stimulus = 'stim_pitch',\n", " stimulus = 'analog4'\n", " )\n", "\n", "# create a sr_data object instance\n", "data_exp = bp.sr_data(90,-stim, com, 'prts')\n", "\n", "# Create model instance\n", "model_surf = P18(weight, height)\n", "\n", "# Add experimental data to model and perform fit\n", "model_surf.fit(data_exp)\n", "\n", "# Create figure\n", "figure = model_surf.plot()\n", "\n", "# show model details after fitting\n", "print(model_surf)\n", "\n", "# Show figure with experimental data and simulations using fit results\n", "figure.show()" ] }, { "cell_type": "markdown", "id": "6c1b8318", "metadata": {}, "source": [ "In this example, a visual stimulus is added to the model and a fit is performed." ] }, { "cell_type": "code", "execution_count": 6, "id": "f70ae70b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "balancepyModel(ModelName=Peterka 2018,\n", " ParameterSet( \n", " mgh: value=8.05, bounds=(10, 20), fixed=True,\n", " J: value=0.9, bounds=(0, 0), fixed=True,\n", " Kp: value=8.46, bounds=(8.457096571348341, 20.13594421749605), fixed=False,\n", " Kd: value=3.83, bounds=(0.805437768699842, 8.05437768699842), fixed=False,\n", " W: value=0.06, bounds=(0.01, 1), fixed=False,\n", " dt: value=0.25, bounds=(0.1, 0.3), fixed=False,\n", " Kt: value=0.02, bounds=(0, 0.05), fixed=False\n", " ),\n", " Frequencies=13 frequencies from 0.1 to 2.5,\n", " Fit Reference=Defined as data_exp.frf,\n", " Sampling Rate=90 Hz)\n" ] }, { "data": { "image/svg+xml": [ "0246800.502468−0.500.50.1234567891200.20.40.1234567891200.050.10.150.20.12345678912−400−2000ExperimentalSimulatedBode PlotStimulus Time SeriesResponse Time SeriesBode Magnitude PlotCoherence PlotBode Phase PlotParameters" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Data analysis and model fit for 10s long visual prts stimulus\n", "# Define subject antrhopometry\n", "weight = 53\n", "height = 1.66\n", "\n", "# create a model instance for the subject\n", "model_vis = P18(weight, height)\n", "# Load experimental data\n", "com, stim, time = bp.getdata_legacy(\n", " (data_path+'d1_vis2.csv'), \n", " height, \n", " resample=True, \n", " cut_to_cycles=True,\n", " end_time=220,\n", " cycle_start_samples=20*90,\n", " cycle_length_samples=10*90,\n", " stimulus = 'stim_pitch',\n", " )\n", "\n", "model_vis.params['W'] = 0.1\n", "data_exp = bp.sr_data(90, stim, com, 'prts')\n", "\n", "model_vis.fit(data_exp)\n", "\n", "print(model_vis)\n", "\n", "figure = model_vis.plot()\n", "figure.show()" ] }, { "cell_type": "markdown", "id": "4f8c6995", "metadata": {}, "source": [ "In this example a model instance is created using a config file. \n", "The alternative use of a config file can be useful for batch processing and to ensure consistent model parameters and settings across different runs." ] }, { "cell_type": "code", "execution_count": 7, "id": "00f99c4a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "balancepyModel(ModelName=Peterka2018,\n", " ParameterSet( \n", " mgh: value=8.05, bounds=(10, 20), fixed=True,\n", " J: value=0.9, bounds=(0, 0), fixed=True,\n", " Kp: value=8.46, bounds=(8.457096571348341, 20.13594421749605), fixed=False,\n", " Kd: value=3.83, bounds=(0.805437768699842, 8.05437768699842), fixed=False,\n", " W: value=0.06, bounds=(0.01, 1), fixed=False,\n", " dt: value=0.25, bounds=(0.1, 0.3), fixed=False,\n", " Kt: value=0.02, bounds=(0, 0.05), fixed=False\n", " ),\n", " Frequencies=13 frequencies from 0.1 to 2.5,\n", " Fit Reference=Defined as data_exp.frf,\n", " Sampling Rate=90 Hz)\n" ] }, { "data": { "image/svg+xml": [ "0246800.502468−0.500.50.1234567891200.20.40.1234567891200.050.10.150.20.12345678912−400−2000ExperimentalSimulatedBode PlotStimulus Time SeriesResponse Time SeriesBode Magnitude PlotCoherence PlotBode Phase PlotParameters" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "config = {\n", " \"ModelName\": \"Peterka2018\",\n", " \"samplingrate_Hz\": 90,\n", " \"mass_kg\": 53,\n", " \"height_m\": 1.66,\n", " \"data_exp\": data_exp\n", "}\n", "\n", "model_config = P18(config=config)\n", "model_config.fit()\n", "\n", "figure = model_config.plot()\n", "\n", "print(model_config)\n", "\n", "figure.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.12 (balancepy)", "language": "python", "name": "balancepy-py312" }, "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.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }