CoSApp tutorials on multimode systems
Bouncing Ball¶
[1]:
from cosapp.base import System
import numpy as np
class PointDynamics(System):
"""Point mass dynamics"""
def setup(self):
self.add_inward("mass", 1.0)
self.add_inward("acc_ext", np.zeros(3))
self.add_inward("force_ext", np.zeros(3))
self.add_outward("force", np.zeros(3))
self.add_outward("acc", np.zeros(3))
def compute(self):
self.force = self.force_ext + self.mass * self.acc_ext
self.acc = self.force / self.mass
class PointFriction(System):
"""Point mass ~ v^2 friction model"""
def setup(self):
self.add_inward('v', np.zeros(3), desc="Velocity")
self.add_inward('cf', 0.1, desc="Friction coefficient")
self.add_outward("force", np.zeros(3))
def compute(self):
self.force = (-self.cf * np.linalg.norm(self.v)) * self.v
class PointMassFreeFall(System):
"""Point mass free fall model with friction"""
def setup(self):
self.add_child(PointFriction('friction'), pulling=['cf', 'v'])
self.add_child(PointDynamics('dynamics'), pulling={
'mass': 'mass',
'force': 'force',
'acc_ext': 'g',
'acc': 'a',
})
self.connect(self.friction, self.dynamics, {"force": "force_ext"})
self.add_transient('v', der='a')
self.add_transient('x', der='v')
self.g = np.r_[0, 0, -9.81]
self.exec_order = ['friction', 'dynamics']
class BouncingBall(PointMassFreeFall):
"""Extension of `PointMassFreeFall`, containing a rebound event
and a rebound restitution coefficient.
"""
def setup(self):
super().setup()
self.add_event('rebound', trigger="x[2] <= 0")
self.add_inward('cr', 1.0, desc="Rebound restitution coefficient", limits=(0, 1))
self.add_outward_modevar("n_rebounds", init=0, dtype=int)
def transition(self):
if self.rebound.present:
self.n_rebounds += 1
v = self.v
if abs(v[2]) < 1e-6:
v[2] = 0
else:
v[2] *= -self.cr
[2]:
from cosapp.drivers import RungeKutta
from cosapp.recorders import DataFrameRecorder
ball = BouncingBall('ball')
driver = ball.add_driver(RungeKutta(order=3, dt=0.01))
driver.time_interval = (0, 20)
# Add a recorder to capture time evolution in a dataframe
driver.add_recorder(
DataFrameRecorder(includes=['x', 'v', 'a', 'norm(v)']),
period=0.05,
)
# Define a simulation scenario
driver.set_scenario(
init = {
'x': np.zeros(3),
'v': np.array([8, 0, 9.5]),
},
values = {
'mass': 1.5,
'cf': 0.20,
'cr': 0.98,
},
stop = ball.rebound.filter("norm(v) < 1"),
)
ball.run_drivers()
# Retrieve recorded data
data = driver.recorder.export_data()
data = data.drop(['Section', 'Status', 'Error code'], axis=1)
time = np.asarray(data['time'])
traj = np.asarray(data['x'].tolist())
[3]:
# Plot results
import plotly.graph_objs as go
traces = [
go.Scatter(
x = traj[:, 0],
y = traj[:, 2],
mode = 'lines',
name = 'numerical',
line = dict(color='red'),
)
]
layout = go.Layout(
title = "Trajectory",
xaxis = dict(title="x"),
yaxis = dict(
title = "z",
scaleanchor = "x",
scaleratio = 1,
),
hovermode = "x",
)
go.Figure(data=traces, layout=layout)
In the next cell, we filter the recorded data in the vicinity of a rebound. Notice that there are two entries at event time \(t \approx 1.457\), showing the discontinuity in velocity (all other quantities are here continuous).
[4]:
data[data['time'].between(1.4, 1.6)]
[4]:
| Reference | a | norm(v) | time | v | x | |
|---|---|---|---|---|---|---|
| 28 | t=1.4 | [-2.194295210349236, 0.0, -5.1152226632109015] | 6.234339 | 1.400000 | [2.6397688373522565, 0.0, -5.6478849580093815] | [6.268834123218378, 0.0, 0.3312396554682464] |
| 29 | t=1.45 | [-2.1647154566836324, 0.0, -4.7677199187252395] | 6.415203 | 1.450000 | [2.5307640955871213, 0.0, -5.894918590886763] | [6.398091277987573, 0.0, 0.04259717972470076] |
| 30 | t=1.4572051635645 | [-2.1598793362619593, 0.0, -4.718474119601484] | 6.440520 | 1.457205 | [2.515184306625334, 0.0, -5.929093248939673] | [6.416269698167304, 0.0, -2.1510571102112408e-16] |
| 31 | ball.rebound | [-2.1233265999052118, 0.0, -14.715252210789183] | 6.331524 | 1.457205 | [2.515184306625334, 0.0, 5.81051138396088] | [6.416269698167304, 0.0, -2.1510571102112408e-16] |
| 32 | t=1.5 | [-1.8600883996323319, 0.0, -13.791159219544964] | 5.740817 | 1.500000 | [2.430083155876791, 0.0, 5.201122679003909] | [6.522045524499052, 0.0, 0.2354796735062707] |
| 33 | t=1.55 | [-1.595276350588941, 0.0, -12.896379761130454] | 5.104618 | 1.550000 | [2.343872180185356, 0.0, 4.5346874583391985] | [6.64133929531685, 0.0, 0.47868872928842365] |
| 34 | t=1.6 | [-1.3680298317851862, 0.0, -12.165707371394758] | 4.520060 | 1.600000 | [2.2699306992237696, 0.0, 3.908754295027936] | [6.7566370670667295, 0.0, 0.6896227139958451] |
Time driver property recorded_events contains a list of all recorded events. Each element of the list is a named tuple EventRecord(time, events), containing the occurrence time, and the list of cascading events at that time, starting by the primitive event.
[5]:
driver.recorded_events
[5]:
[EventRecord(time=1.4572051635645256, events=[<Event ball.rebound>]),
EventRecord(time=2.5179323285203927, events=[<Event ball.rebound>]),
EventRecord(time=3.3959818563126207, events=[<Event ball.rebound>]),
EventRecord(time=4.160288296667009, events=[<Event ball.rebound>]),
EventRecord(time=4.844135950925513, events=[<Event ball.rebound>]),
EventRecord(time=5.466803595620114, events=[<Event ball.rebound>]),
EventRecord(time=6.040711897170551, events=[<Event ball.rebound>]),
EventRecord(time=6.574449489921964, events=[<Event ball.rebound>]),
EventRecord(time=7.074264201047491, events=[<Event ball.rebound>]),
EventRecord(time=7.544878421625021, events=[<Event ball.rebound>]),
EventRecord(time=7.989970249199137, events=[<Event ball.rebound>]),
EventRecord(time=8.412474526810431, events=[<Event ball.rebound>]),
EventRecord(time=8.814780162881563, events=[<Event ball.rebound>]),
EventRecord(time=9.198864468284205, events=[<Event ball.rebound>]),
EventRecord(time=9.566387546644071, events=[<Event ball.rebound>]),
EventRecord(time=9.9187604023159, events=[<Event ball.rebound>]),
EventRecord(time=10.2571952264516, events=[<Event ball.rebound>]),
EventRecord(time=10.58274326642034, events=[<Event ball.rebound>]),
EventRecord(time=10.896323842616601, events=[<Event ball.rebound>]),
EventRecord(time=11.1987469292163, events=[<Event ball.rebound>]),
EventRecord(time=11.490730966804382, events=[<Event ball.rebound>]),
EventRecord(time=11.77291708196726, events=[<Event ball.rebound>]),
EventRecord(time=12.045880568609268, events=[<Event ball.rebound>]),
EventRecord(time=12.31014025520096, events=[<Event ball.rebound>]),
EventRecord(time=12.566166211079043, events=[<Event ball.rebound>]),
EventRecord(time=12.814386137028272, events=[<Event ball.rebound>]),
EventRecord(time=13.055190712437806, events=[<Event ball.rebound>]),
EventRecord(time=13.288938099032437, events=[<Event ball.rebound>]),
EventRecord(time=13.515957756114199, events=[<Event ball.rebound>]),
EventRecord(time=13.736553690847526, events=[<Event ball.rebound>]),
EventRecord(time=13.951007249193802, events=[<Event ball.rebound>]),
EventRecord(time=14.159579520456134, events=[<Event ball.rebound>]),
EventRecord(time=14.362513417912487, events=[<Event ball.rebound>]),
EventRecord(time=14.560035489019723, events=[<Event ball.rebound>, <Event ball.stop>])]
Time driver property event_data contains the same fields as the recorder, but only at event times:
[6]:
driver.event_data.drop(['Section', 'Status', 'Error code'], axis=1)
[6]:
| Reference | a | norm(v) | time | v | x | |
|---|---|---|---|---|---|---|
| 0 | t=1.4572051635645 | [-2.1598793362619593, 0.0, -4.718474119601484] | 6.440520 | 1.457205 | [2.515184306625334, 0.0, -5.929093248939673] | [6.416269698167304, 0.0, -2.1510571102112408e-16] |
| 1 | ball.rebound | [-2.1233265999052118, 0.0, -14.715252210789183] | 6.331524 | 1.457205 | [2.515184306625334, 0.0, 5.81051138396088] | [6.416269698167304, 0.0, -2.1510571102112408e-16] |
| 2 | t=2.5179323285204 | [-1.0268297864677984, 0.0, -6.686250073284417] | 4.966027 | 2.517932 | [1.5507816107708086, 0.0, -4.717679606530486] | [8.508373346715413, 0.0, -3.434752482434078e-15] |
| 3 | ball.rebound | [-1.0083142767195679, 0.0, -12.816074868228071] | 4.876481 | 2.517932 | [1.5507816107708086, 0.0, 4.623326014399876] | [8.508373346715413, 0.0, -3.434752482434078e-15] |
| 4 | t=3.3959818563126 | [-0.6356294918622606, 0.0, -7.5593022006804995] | 4.188136 | 3.395982 | [1.1382678785776474, 0.0, -4.030487952730085] | [9.674569363224247, 0.0, -6.495498583447556e-14] |
| ... | ... | ... | ... | ... | ... | ... |
| 63 | ball.rebound | [-0.028226418088618272, 0.0, -9.945877703173165] | 1.020215 | 14.159580 | [0.20750341921211013, 0.0, 0.9988900438801666] | [14.530243383100348, 0.0, -1.6014967130217883e... |
| 64 | t=14.362513417912 | [-0.02760102624437481, 0.0, -9.676058041450657] | 1.012755 | 14.362513 | [0.20440066794188802, 0.0, -0.9919133278064214] | [14.572036847736637, 0.0, 8.374377580278036e-16] |
| 65 | ball.rebound | [-0.027071711600498465, 0.0, -9.938745840105016] | 0.993333 | 14.362513 | [0.20440066794188802, 0.0, 0.972075061250293] | [14.572036847736637, 0.0, 8.374377580278036e-16] |
| 66 | t=14.56003548902 | [-0.026501918384141097, 0.0, -9.682995123964924] | 0.986435 | 14.560035 | [0.20149775240327483, 0.0, -0.9656356454798545] | [14.612122771161538, 0.0, -4.0365524507993134e... |
| 67 | ball.rebound | [-0.02599421231491442, 0.0, -9.932080365335649] | 0.967537 | 14.560035 | [0.20149775240327483, 0.0, 0.9463229325702573] | [14.612122771161538, 0.0, -4.0365524507993134e... |
68 rows × 6 columns