2. Master Pendulum: Switching and Contact-Aware Modes#
This notebook uses the actual MasterPendulum class to demonstrate mode selection,
hysteresis, and contact-aware switching.
2.1. Overview#
Configure a contact-enabled FEM pendulum as the reference model.
Instantiate
MasterPendulumand initialize it.Set a contact-aware
mode_selectorand hysteresis.Simulate and inspect synchronization events.
2.2. Learning Goals#
Understand how to configure the
MasterPendulumfor contact-aware switching.Understanding effects of hysteresis and dwell time on mode switching.
2.3. Prerequisites#
You should be familiar with the FMU, OpenSim, and FEM pendulum components. This notebook focuses on switching behavior and does not re-derive the models.
import numpy as np
import matplotlib.pyplot as plt
from demos.ControlledPendulum.src.master_pendulum.orchestration.master_pendulum import MasterPendulum
from demos.ControlledPendulum.src.master_pendulum.components.fem import pendulum_config as config
from syssimx.core.multi_comp import Hysteresis
2.4. Configure FEM Parameters#
We enable contact so the MasterPendulum uses its contact-aware switching logic.
The FEM model acts as the reference for mass/inertia/length synchronization.
init_params = config.InitialConditionParameters()
init_params.angular_position_deg = 20.0
init_params.angular_velocity = 0.0
init_params.drive_torque = 0.0
mat_params = config.MaterialParameters()
mat_params.E_pendulum = 2.1e11
mat_params.nu_pendulum = 0.3
mat_params.rho_pendulum = 7800
sim_params = config.SimulationParameters()
sim_params.tau = 0.01
sim_params.t_end = 0.5
sim_params.with_contact = True
sim_params.use_gravity = True
contact_params = config.ContactParameters()
contact_params.kn = 1e10
anim_params = config.AnimationParameters()
anim_params.animate = False
fem_parameters = {
"mat_params": mat_params,
"contact_params": contact_params,
"init_params": init_params,
"sim_params": sim_params,
"anim_params": anim_params,
}
2.5. Instantiate MasterPendulum#
Initialization will:
initialize FEM first,
synchronize parameters to FMU and OpenSim,
select the contact-aware mode selector if contact is enabled.
pendulum = MasterPendulum(name="MasterPendulum", initial_mode="FEM")
pendulum.set_parameters(**{"FEM": fem_parameters})
pendulum.initialize(t0=0.0)
2.6. Mode Selector and Hysteresis#
We are overriding the default mode selector to use contact gap distance for switching.
In this scenario the MasterPendulum will switch to FMU mode when the pendulum is far from the wall, in the intermediate gap range it will use the OpenSim model, and when in contact it will use the FEM model.
This kind of mode selector is computationally expensive since it requires for each step to update the internal grid functions of the FEM model to compute the contact gap distance. In practice, you would likely want to use a more efficient switching criterion (e.g. based on the pendulum angle or velocity) and only update the FEM state and compute the gap distance when close to the wall.
If there is no contact, the mode selector will return “FMU”.
def contact_mode_selector(t, state):
if not pendulum._with_contact:
return "FMU"
else:
theta = state['theta']['value']
if theta > 0.25:
return "FMU"
elif theta > 0.15:
return "OpenSim"
else:
return "FEM"
pendulum.mode_selector = contact_mode_selector
Additionally, we set a hysteresis dwell time to prevent rapid switching when the pendulum is near the switching thresholds.
dwell_time = 0.05
pendulum.hysteresis = Hysteresis(dwell_time=dwell_time)
2.7. Simulate and Record Synchronization Events#
We step the system, log the active mode, and inspect sync_events to verify
that switching preserves the shared state.
pendulum.setup_monitoring()
pendulum.display_monitoring()
t = 0.0
dt = sim_params.tau
t_end = sim_params.t_end
mode_log = []
while t < t_end:
pendulum.set_inputs({"tau": 0.0}, t=t)
pendulum.do_step(t, dt)
mode_log.append((t, pendulum.active_mode))
t += dt
pendulum.fem.animate_stress()
2.8. Visualize Mode Switching#
t_vals = {mode: [] for mode in pendulum.models.keys()}
torque_vals = {mode: [] for mode in pendulum.models.keys()}
alpha_vals = {mode: [] for mode in pendulum.models.keys()}
omega_vals = {mode: [] for mode in pendulum.models.keys()}
q_vals = {mode: [] for mode in pendulum.models.keys()}
for mode in pendulum.models.keys():
t_vals[mode], res_dict = pendulum.models[mode].get_history_arrays()
alpha_vals[mode] = res_dict["alpha"]
omega_vals[mode] = res_dict["omega"]
q_vals[mode] = res_dict["theta"]
histories = {}
for mode in pendulum.models.keys():
histories[mode] = {
"time": t_vals[mode],
"alpha": alpha_vals[mode],
"omega": omega_vals[mode],
"q": q_vals[mode],
}