simian-examples 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simian/examples/all_components.py +408 -0
- simian/examples/ballthrower.py +429 -0
- simian/examples/ballthrower_engine.py +102 -0
- simian/examples/js/treemap.js +76 -0
- simian/examples/logo.png +0 -0
- simian/examples/plottypes.py +428 -0
- simian/examples/treemap.json +108 -0
- simian/examples/treemap.py +157 -0
- simian/examples/workshop_example.json +211 -0
- simian/examples/workshop_example.py +219 -0
- simian/examples/wrapped_thrower.py +233 -0
- simian_examples-3.3.0.dist-info/METADATA +24 -0
- simian_examples-3.3.0.dist-info/RECORD +15 -0
- simian_examples-3.3.0.dist-info/WHEEL +5 -0
- simian_examples-3.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""Ball Thrower example."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from simian.examples.ballthrower_engine import BallThrower
|
|
8
|
+
from simian.gui import Form, component, component_properties, utils
|
|
9
|
+
|
|
10
|
+
# Prepare often used settings.
|
|
11
|
+
COLLAPSE_PANEL = {"collapsible": True}
|
|
12
|
+
LABEL_LEFT = {"labelPosition": "left-left"}
|
|
13
|
+
INLINE = {"customClass": "form-check-inline"}
|
|
14
|
+
SHOW_LABEL = {"hideLabel": False}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
# Run as a script
|
|
19
|
+
from simian.entrypoint import assert_simian_gui_imports
|
|
20
|
+
|
|
21
|
+
assert_simian_gui_imports()
|
|
22
|
+
from simian.local import run
|
|
23
|
+
|
|
24
|
+
run("simian.examples.ballthrower", window_title="Ball Thrower example", debug=False)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def gui_event(meta_data: dict, payload: dict) -> dict:
|
|
28
|
+
"""Event handling of the application.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
meta_data: Form meta data.
|
|
32
|
+
payload: Current status of the Form's contents.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
payload: Updated Form contents.
|
|
36
|
+
"""
|
|
37
|
+
# Register the events with their callback functions.
|
|
38
|
+
Form.eventHandler(
|
|
39
|
+
ThrowBall=throwing,
|
|
40
|
+
ClearHistory=clear_history,
|
|
41
|
+
CauseError=cause_error,
|
|
42
|
+
)
|
|
43
|
+
try:
|
|
44
|
+
caller = utils.getEventFunction(meta_data, payload)
|
|
45
|
+
payload = caller(meta_data, payload)
|
|
46
|
+
|
|
47
|
+
except ImportError as exc:
|
|
48
|
+
# No method with matching name found in the current namespace. May be used to have custom
|
|
49
|
+
# event routing. Here we only log the occurrence.
|
|
50
|
+
logging.debug(exc.msg)
|
|
51
|
+
|
|
52
|
+
return payload
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def throwing(_meta_data: dict, payload: dict) -> dict:
|
|
56
|
+
"""Simulate throwing a ball.
|
|
57
|
+
|
|
58
|
+
Get the settings from the payload and multiply with the enabled states to switch off parts of
|
|
59
|
+
the simulation.
|
|
60
|
+
"""
|
|
61
|
+
enable_drag, _ = utils.getSubmissionData(payload, key="enableDrag", parent="options")
|
|
62
|
+
enable_wind, _ = utils.getSubmissionData(payload, key="enableWind", parent="options")
|
|
63
|
+
throw_speed, _ = utils.getSubmissionData(payload, key="throwSpeed")
|
|
64
|
+
throw_angle, _ = utils.getSubmissionData(payload, key="throwAngle")
|
|
65
|
+
drag_coeff = utils.getSubmissionData(payload, key="dragCoefficient")[0] * enable_drag
|
|
66
|
+
radius_ball = utils.getSubmissionData(payload, key="ballRadius")[0] * enable_drag
|
|
67
|
+
rho_air = utils.getSubmissionData(payload, key="airDensity")[0] * enable_drag
|
|
68
|
+
gravity = utils.getSubmissionData(payload, key="gravity")[0] * -1 # Point downward.
|
|
69
|
+
mass_ball, _ = utils.getSubmissionData(payload, key="ballMass") # May not be zero.
|
|
70
|
+
wind_speed = utils.getSubmissionData(payload, key="windSpeed")[0] * enable_drag * enable_wind
|
|
71
|
+
|
|
72
|
+
# Convert the speed and angle to the speed components.
|
|
73
|
+
ver_speed = throw_speed * math.sin(throw_angle / 180 * math.pi)
|
|
74
|
+
hor_speed = throw_speed * math.cos(throw_angle / 180 * math.pi)
|
|
75
|
+
|
|
76
|
+
_t, x, y, _u, _v = BallThrower.throw_ball(
|
|
77
|
+
u0=hor_speed,
|
|
78
|
+
v0=ver_speed,
|
|
79
|
+
Cd=drag_coeff,
|
|
80
|
+
r=radius_ball,
|
|
81
|
+
rho=rho_air,
|
|
82
|
+
g=gravity,
|
|
83
|
+
m=mass_ball,
|
|
84
|
+
w=wind_speed,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Create a Plotly object from the information in the payload.
|
|
88
|
+
plot_obj, _ = utils.getSubmissionData(payload, key="plot")
|
|
89
|
+
|
|
90
|
+
# Plot the new newly calculated x and y values, append the legend and put the plotly
|
|
91
|
+
# object in the submission data.
|
|
92
|
+
nr = len(plot_obj.figure.data) + 1
|
|
93
|
+
plot_obj.figure.add_scatter(x=x, y=y, name=f"Attempt {nr}", mode="lines")
|
|
94
|
+
utils.setSubmissionData(payload, key="plot", data=plot_obj)
|
|
95
|
+
|
|
96
|
+
# Update the table with the settings used for the throws.
|
|
97
|
+
new_row = [
|
|
98
|
+
nr,
|
|
99
|
+
nr,
|
|
100
|
+
round(hor_speed, 3),
|
|
101
|
+
round(ver_speed, 3),
|
|
102
|
+
wind_speed,
|
|
103
|
+
mass_ball,
|
|
104
|
+
radius_ball,
|
|
105
|
+
drag_coeff,
|
|
106
|
+
gravity,
|
|
107
|
+
rho_air,
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
if nr == 1:
|
|
111
|
+
# First throw. Only put the new row in the table.
|
|
112
|
+
new_table_values = [new_row]
|
|
113
|
+
else:
|
|
114
|
+
# Extra row. Append the row to the DataFrame from the submission data.
|
|
115
|
+
new_table_values, _ = utils.getSubmissionData(payload, key="summary")
|
|
116
|
+
new_table_values.append(dict(zip(new_table_values[0].keys(), new_row)))
|
|
117
|
+
|
|
118
|
+
utils.setSubmissionData(payload, key="summary", data=new_table_values)
|
|
119
|
+
|
|
120
|
+
# Accept all changes to settings and lower the changes flag.
|
|
121
|
+
payload["pristine"] = True
|
|
122
|
+
|
|
123
|
+
return payload
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def clear_history(_meta_data: dict, payload: dict) -> dict:
|
|
127
|
+
"""Clears the data from the plot and table."""
|
|
128
|
+
# Clear the data from the plot.
|
|
129
|
+
plot_obj, _ = utils.getSubmissionData(payload, "plot")
|
|
130
|
+
plot_obj.figure.data = []
|
|
131
|
+
utils.setSubmissionData(payload, key="plot", data=plot_obj)
|
|
132
|
+
|
|
133
|
+
# Clear the values in the table.
|
|
134
|
+
utils.setSubmissionData(payload, key="summary", data=[])
|
|
135
|
+
|
|
136
|
+
payload = utils.addAlert(payload, "History of throws cleared.", "info")
|
|
137
|
+
|
|
138
|
+
return payload
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def cause_error(_meta_data: dict, payload: dict) -> dict:
|
|
142
|
+
"""Throws an error so that the user can see the error handling in action."""
|
|
143
|
+
raise RuntimeError("Error thrown for testing the error handling mechanism.")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def gui_init(*_args) -> dict:
|
|
147
|
+
"""Initializes the form.
|
|
148
|
+
|
|
149
|
+
Initialization of the form and the components therein.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
payload: Form definition.
|
|
153
|
+
"""
|
|
154
|
+
# Create a form object as the base of the application.
|
|
155
|
+
form_obj = Form()
|
|
156
|
+
|
|
157
|
+
# Create tabs in the form.
|
|
158
|
+
tabs = component.Tabs(key="tabs", parent=form_obj)
|
|
159
|
+
tab_inputs, tab_settings = tabs.setContent(labels=["Inputs", "Used settings"])
|
|
160
|
+
|
|
161
|
+
# Create a Columns object and fill it with two empty columns.
|
|
162
|
+
col = component.Columns(key="inputColumns", parent=tab_inputs)
|
|
163
|
+
left_column, right_column = col.setContent(components=[], widths=[6, 6])
|
|
164
|
+
|
|
165
|
+
# Options Panel
|
|
166
|
+
wind_options = {"logic": _wind_options(), **INLINE}
|
|
167
|
+
|
|
168
|
+
# Turn off black formatter for readability of build tables.
|
|
169
|
+
utils.addComponentsFromTable(
|
|
170
|
+
parent=left_column,
|
|
171
|
+
build_table=[
|
|
172
|
+
# key class level options default label tooltip
|
|
173
|
+
["options", "Container", 1, SHOW_LABEL, None, "Options:", None],
|
|
174
|
+
["enableDrag", "Checkbox", 2, INLINE, False, "Enable Drag", "Enable the simulation of the effect of drag on the thrown ball."],
|
|
175
|
+
["enableWind", "Checkbox", 2, wind_options, False, "Enable Wind", "Enable the simulation of the effect of wind on the thrown ball. Requires drag to be enabled."]
|
|
176
|
+
],
|
|
177
|
+
) # fmt: skip
|
|
178
|
+
|
|
179
|
+
# Add Throw speed and angle controls.
|
|
180
|
+
add_throw_speed_controls(left_column)
|
|
181
|
+
|
|
182
|
+
# Add the Throw, Ball, Constants and Wind panels to the left column.
|
|
183
|
+
_add_options_panels(left_column)
|
|
184
|
+
|
|
185
|
+
# Fill the right column.
|
|
186
|
+
fill_right_column(right_column)
|
|
187
|
+
|
|
188
|
+
# Fill the Settings tab.
|
|
189
|
+
_fill_settings_tab(tab_settings)
|
|
190
|
+
|
|
191
|
+
# Define the logo.
|
|
192
|
+
image_file = os.path.join(os.path.dirname(__file__), "logo.png")
|
|
193
|
+
logo = utils.encodeImage(image_file)
|
|
194
|
+
|
|
195
|
+
# Put the form in the outputs.
|
|
196
|
+
payload = {
|
|
197
|
+
"form": form_obj,
|
|
198
|
+
"navbar": {
|
|
199
|
+
"logo": logo,
|
|
200
|
+
"title": "Ball Thrower",
|
|
201
|
+
"subtitle": "<small>Simian Demo</small>",
|
|
202
|
+
},
|
|
203
|
+
"showChanged": True, # Flag changes to the settings in the UI.
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return payload
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def add_throw_speed_controls(parent) -> dict:
|
|
210
|
+
"""Create and add Throw speed and angle controls."""
|
|
211
|
+
# Allow speed values between 0 and 20 m/s.
|
|
212
|
+
|
|
213
|
+
speed_options = {
|
|
214
|
+
"validate": {
|
|
215
|
+
"min": 0,
|
|
216
|
+
"max": 20 * math.sqrt(2),
|
|
217
|
+
"required": True,
|
|
218
|
+
},
|
|
219
|
+
**LABEL_LEFT,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
angle_options = {
|
|
223
|
+
"validate": {
|
|
224
|
+
"min": 0,
|
|
225
|
+
"max": 90,
|
|
226
|
+
"required": True,
|
|
227
|
+
},
|
|
228
|
+
**LABEL_LEFT,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Throw Panel
|
|
232
|
+
return utils.addComponentsFromTable(
|
|
233
|
+
parent=parent,
|
|
234
|
+
build_table=[
|
|
235
|
+
# key class level options default label tooltip
|
|
236
|
+
["throwPanel", "Panel", 1, COLLAPSE_PANEL, None, "Throw settings", None],
|
|
237
|
+
["throwSpeed", "Number", 2, speed_options, 10, "Throw speed [m/s]:", "Speed with which the ball is thrown."],
|
|
238
|
+
["throwAngle", "Number", 2, angle_options, 45, "Throw angle [degrees]:", "Angle at which the ball is thrown."]
|
|
239
|
+
],
|
|
240
|
+
) # fmt: skip
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _add_options_panels(left_column):
|
|
244
|
+
# Ball properties
|
|
245
|
+
# Hide ball panel when drag is not enabled.
|
|
246
|
+
show_drag_enabled = {"customConditional": "show=data.options.enableDrag"}
|
|
247
|
+
ball_panel_opts = {
|
|
248
|
+
**show_drag_enabled,
|
|
249
|
+
**COLLAPSE_PANEL,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Ball mass number, allow values larger than 0.
|
|
253
|
+
ball_mass_opts = {
|
|
254
|
+
"validate": {
|
|
255
|
+
"min": 0.000001,
|
|
256
|
+
"required": True,
|
|
257
|
+
},
|
|
258
|
+
**LABEL_LEFT,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Ball radius number, allow values larger than 0.
|
|
262
|
+
ball_radius_opts = {
|
|
263
|
+
"validate": {
|
|
264
|
+
"min": 0,
|
|
265
|
+
"required": True,
|
|
266
|
+
},
|
|
267
|
+
**LABEL_LEFT,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# Drag coefficient number, allow values between 0 and 10.
|
|
271
|
+
drag_coeff_opts = {
|
|
272
|
+
"validate": {
|
|
273
|
+
"min": 0,
|
|
274
|
+
"max": 10,
|
|
275
|
+
"required": True,
|
|
276
|
+
},
|
|
277
|
+
**LABEL_LEFT,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
utils.addComponentsFromTable(
|
|
281
|
+
parent=left_column,
|
|
282
|
+
build_table=[
|
|
283
|
+
# key class level options default label tooltip
|
|
284
|
+
["ballPanel", "Panel", 1, ball_panel_opts, None, "Ball settings", None],
|
|
285
|
+
["ballMass", "Number", 2, ball_mass_opts, 0.05, "Mass [kg]:", "Mass of the ball."],
|
|
286
|
+
["ballRadius", "Number", 2, ball_radius_opts, 0.1, "Radius [m]:", "Radius of the ball."],
|
|
287
|
+
["dragCoefficient", "Number", 2, drag_coeff_opts, 0.4, "Drag coefficient [-]:", "Drag coefficient of the ball."]
|
|
288
|
+
],
|
|
289
|
+
) # fmt: skip
|
|
290
|
+
|
|
291
|
+
# Constants Panel
|
|
292
|
+
# Allow gravity values between 0.1 and 20 m/s^2.
|
|
293
|
+
gravity_opts = {
|
|
294
|
+
"validate": {
|
|
295
|
+
"min": 0.1,
|
|
296
|
+
"max": 20,
|
|
297
|
+
"required": True,
|
|
298
|
+
},
|
|
299
|
+
**LABEL_LEFT,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# Air density number, allow values between 0 and 10 kg/m^3.
|
|
303
|
+
air_dens_opts = {
|
|
304
|
+
**show_drag_enabled,
|
|
305
|
+
"validate": {
|
|
306
|
+
"min": 0,
|
|
307
|
+
"max": 10,
|
|
308
|
+
"required": True,
|
|
309
|
+
},
|
|
310
|
+
**LABEL_LEFT,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
utils.addComponentsFromTable(
|
|
314
|
+
parent=left_column,
|
|
315
|
+
build_table=[
|
|
316
|
+
# key class level options default label tooltip
|
|
317
|
+
["constantsPanel", "Panel", 1, COLLAPSE_PANEL, None, "Constants", None],
|
|
318
|
+
["gravity", "Number", 2, gravity_opts, 9.81, "Gravity [m/s<sup>2</sup>]:", "Gravitational accelleration acting on the ball."],
|
|
319
|
+
["airDensity", "Number", 2, air_dens_opts, 1.29, "Air density [kg/m<sup>3</sup>]:", "Density of the air through which the ball moves."]
|
|
320
|
+
],
|
|
321
|
+
) # fmt: skip
|
|
322
|
+
|
|
323
|
+
# Wind Panel
|
|
324
|
+
# Hide wind panel when wind is not enabled.
|
|
325
|
+
wind_panel_options = {
|
|
326
|
+
"conditional": {
|
|
327
|
+
"when": "enableWind",
|
|
328
|
+
"eq": False,
|
|
329
|
+
},
|
|
330
|
+
**COLLAPSE_PANEL,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Allow wind speed number values between -10 and 10 m/s.
|
|
334
|
+
wind_options = {
|
|
335
|
+
"validate": {
|
|
336
|
+
"min": -10,
|
|
337
|
+
"max": 10,
|
|
338
|
+
"required": True,
|
|
339
|
+
},
|
|
340
|
+
**LABEL_LEFT,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
utils.addComponentsFromTable(
|
|
344
|
+
parent=left_column,
|
|
345
|
+
build_table=[
|
|
346
|
+
# key class level options default label tooltip
|
|
347
|
+
["windPanel", "Panel", 1, wind_panel_options, None, "Wind settings", None],
|
|
348
|
+
["windSpeed", "Number", 2, wind_options, -1, "Wind speed [m/s]:", "Wind speed. Positive values correspond with tailwind. Negative values correspond with headwind."]
|
|
349
|
+
],
|
|
350
|
+
) # fmt: skip
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def fill_right_column(right_column) -> None:
|
|
354
|
+
"""Fill right column of the app."""
|
|
355
|
+
# Actions Panel
|
|
356
|
+
throw_options = {"disableOnInvalid": True, **INLINE}
|
|
357
|
+
error_options = {"theme": "danger", **INLINE}
|
|
358
|
+
|
|
359
|
+
buttons_dict = utils.addComponentsFromTable(
|
|
360
|
+
parent=right_column,
|
|
361
|
+
build_table=[
|
|
362
|
+
# key class level options label tooltip
|
|
363
|
+
["ButtonPanel", "Container", 1, SHOW_LABEL, "Actions:", None],
|
|
364
|
+
["throwButton", "Button", 2, throw_options, "Throw", "Click to simulate a throw with the chosen settings."],
|
|
365
|
+
["clearButton", "Button", 2, INLINE, "Clear results", "Click to remove all of the current results."],
|
|
366
|
+
["errorButton", "Button", 2, error_options, "Cause error", "Click to cause an error and see the error handling in action."]
|
|
367
|
+
],
|
|
368
|
+
column_names=["key", "class", "level", "options", "label", "tooltip"],
|
|
369
|
+
) # fmt: skip
|
|
370
|
+
|
|
371
|
+
# Add the events to the buttons.
|
|
372
|
+
buttons_dict["throwButton"].setEvent(event_name="ThrowBall")
|
|
373
|
+
buttons_dict["clearButton"].setEvent(event_name="ClearHistory")
|
|
374
|
+
buttons_dict["errorButton"].setEvent(event_name="CauseError")
|
|
375
|
+
|
|
376
|
+
# Plot
|
|
377
|
+
# Create a plot window to plot the ball trajectories in.
|
|
378
|
+
my_plot = component.Plotly(key="plot", parent=right_column)
|
|
379
|
+
my_plot.defaultValue["data"] = []
|
|
380
|
+
my_plot.defaultValue["layout"] = {
|
|
381
|
+
"title": {"text": "Balls thrown"},
|
|
382
|
+
"xaxis": {"title": "Distance [m]"},
|
|
383
|
+
"yaxis": {"title": "Height [m]"},
|
|
384
|
+
"margin": {"t": 40, "b": 30, "l": 50},
|
|
385
|
+
}
|
|
386
|
+
my_plot.defaultValue["config"] = {"displaylogo": False}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _fill_settings_tab(tab_settings):
|
|
390
|
+
"""Fill Settings tab."""
|
|
391
|
+
# Create a table to store the throw settings in.
|
|
392
|
+
table_out = component.DataTables(key="summary", parent=tab_settings)
|
|
393
|
+
table_out.label = "Used settings per throw"
|
|
394
|
+
table_out.setFeatures(searching=False, ordering=False, paging=False)
|
|
395
|
+
table_out.setColumns(
|
|
396
|
+
*[
|
|
397
|
+
list(x)
|
|
398
|
+
for x in zip(
|
|
399
|
+
["Id", "id"],
|
|
400
|
+
["Attempt", "attempt"],
|
|
401
|
+
["Horizontal speed", "hor_speed"],
|
|
402
|
+
["Vertical speed", "ver_speed"],
|
|
403
|
+
["Wind speed", "wind_speed"],
|
|
404
|
+
["Ball mass", "ball_mass"],
|
|
405
|
+
["Radius", "radius"],
|
|
406
|
+
["Drag Coeff.", "drag"],
|
|
407
|
+
["Gravity", "gravity"],
|
|
408
|
+
["Air density", "air_density"],
|
|
409
|
+
)
|
|
410
|
+
],
|
|
411
|
+
visible=[False] + [True] * 9,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _wind_options() -> list:
|
|
416
|
+
"""Define drag enabled/disabled triggers."""
|
|
417
|
+
# Disable the Wind checkbox when the Drag checkbox is unticked.
|
|
418
|
+
wind_disable = component_properties.create_disable_logic(
|
|
419
|
+
trigger_type="simple", trigger_value=["enableDrag", False]
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# Ensure the Wind checkbox is unticked when Drag is unticked.
|
|
423
|
+
wind_disable.actions.append(
|
|
424
|
+
{
|
|
425
|
+
"type": "value",
|
|
426
|
+
"value": "value = false",
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
return wind_disable
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Ballthrower class. Implements the functional model.
|
|
2
|
+
|
|
3
|
+
Copyright 2020-2023 MonkeyProof Solutions BV.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.integrate import solve_ivp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BallThrower:
|
|
11
|
+
"""Ballthrower class. Implements the functional model."""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def throw_ball(
|
|
15
|
+
x0: float = 0,
|
|
16
|
+
y0: float = 0,
|
|
17
|
+
u0: float = 10,
|
|
18
|
+
v0: float = 10,
|
|
19
|
+
Cd: float = 0.4,
|
|
20
|
+
r: float = 0.05,
|
|
21
|
+
rho: float = 1.239,
|
|
22
|
+
g: float = -9.81,
|
|
23
|
+
m: float = 0.1,
|
|
24
|
+
w: float = 0,
|
|
25
|
+
) -> list:
|
|
26
|
+
"""Ball thrower solver.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
x0: Initial horizontal location [m]. Defaults to 0.
|
|
30
|
+
y0: Initial vertical location [m]. Defaults to 0.
|
|
31
|
+
u0: Initial horizontal speed [m/s]. Defaults to 10.
|
|
32
|
+
v0: Initial vertical speed [m/s]. Defaults to 10.
|
|
33
|
+
Cd: Drag coefficient of the ball [-]. Defaults to 0.4.
|
|
34
|
+
r: Radius of the ball [m]. Defaults to 0.05.
|
|
35
|
+
rho: Air density [kg/m3]. Defaults to 1.239.
|
|
36
|
+
g: Gravitational pull [m/s2]. Defaults to -9.81.
|
|
37
|
+
m: Mass of the ball [kg]. Defaults to 0.1.
|
|
38
|
+
w: Wind speed [m/s]. Defaults to 0.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
t: Time stamps (in seconds).
|
|
42
|
+
x: Horizontal distance travelled over time (in meters).
|
|
43
|
+
y: Vertical distance travelled over time (in meters).
|
|
44
|
+
u: Horizontal speed over time (in meters per second).
|
|
45
|
+
v: Vertical speed over time (in meters per second).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Define the zero crossing function that should abort the simulation.
|
|
49
|
+
def _zero_crossing(_t, y, *_args):
|
|
50
|
+
return y[2]
|
|
51
|
+
|
|
52
|
+
# Ensure that gravity is pointing downwards.
|
|
53
|
+
g = abs(g) * -1
|
|
54
|
+
|
|
55
|
+
_zero_crossing.terminal = True
|
|
56
|
+
_zero_crossing.direction = -1
|
|
57
|
+
|
|
58
|
+
# Solve the differential equation.
|
|
59
|
+
outputs = solve_ivp(
|
|
60
|
+
fun=BallThrower._drag_ball,
|
|
61
|
+
t_span=[0, np.inf],
|
|
62
|
+
y0=[x0, u0, y0, v0],
|
|
63
|
+
events=_zero_crossing,
|
|
64
|
+
args=(Cd, r, rho, g, m, w),
|
|
65
|
+
rtol=1e-8,
|
|
66
|
+
atol=1e-8,
|
|
67
|
+
max_step=0.1,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
t = outputs.t
|
|
71
|
+
y = outputs.y
|
|
72
|
+
|
|
73
|
+
# Return the time stamps, location and speed components.
|
|
74
|
+
return t, y[0], y[2], y[1], y[3]
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _drag_ball(
|
|
78
|
+
_t: float, y: list, Cd: float, r: float, rho: float, g: float, m: float, w: float
|
|
79
|
+
) -> list:
|
|
80
|
+
"""Calculates the location and speed of the ball at time step t.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
t: Time stamp.
|
|
84
|
+
y: The location and speed of the ball at time stamp t - 1.
|
|
85
|
+
Cd: Drag coefficient of the ball [-].
|
|
86
|
+
r: Radius of the ball [m].
|
|
87
|
+
rho: Air density [kg/m3].
|
|
88
|
+
g: Gravitational pull [m/s2].
|
|
89
|
+
m: Mass of the ball [kg].
|
|
90
|
+
w: Wind speed [m/s].
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
y: List of dx/dt, du/dt, dy/dt and dv/dt values.
|
|
94
|
+
"""
|
|
95
|
+
A = np.pi * r**2
|
|
96
|
+
|
|
97
|
+
return [
|
|
98
|
+
y[1],
|
|
99
|
+
-0.5 * rho / m * (y[1] - w) ** 2 * Cd * A * np.sign(y[1] - w),
|
|
100
|
+
y[3],
|
|
101
|
+
-0.5 * rho / m * y[3] ** 2 * Cd * A * np.sign(y[3]) + g,
|
|
102
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function fillTreemap(data, year) {
|
|
2
|
+
console.log("Year: " + year);
|
|
3
|
+
|
|
4
|
+
var idx = data.years.map((y, i) => y == year ? i : false).filter(Boolean);
|
|
5
|
+
|
|
6
|
+
var countries = idx.map((i) => data.countries[i]);
|
|
7
|
+
var parents = idx.map((i) => data.parents[i]);
|
|
8
|
+
var ids = idx.map((i) => data.ids[i]);
|
|
9
|
+
var lifeExp = idx.map((i) => data.lifeExp[i]);
|
|
10
|
+
var pop = idx.map((i) => data.pop[i]);
|
|
11
|
+
|
|
12
|
+
var layout = {
|
|
13
|
+
coloraxis: {
|
|
14
|
+
colorbar: {
|
|
15
|
+
title: {
|
|
16
|
+
"text": "lifeExp"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
colorscale: getColorScale(),
|
|
20
|
+
cmid: lifeExp.slice(-1)[0]
|
|
21
|
+
},
|
|
22
|
+
legend: {
|
|
23
|
+
tracegroupgap: 0
|
|
24
|
+
},
|
|
25
|
+
margin: {
|
|
26
|
+
t: 50,
|
|
27
|
+
l: 25,
|
|
28
|
+
r: 25,
|
|
29
|
+
b: 25
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var trace = {
|
|
34
|
+
branchvalues: "total",
|
|
35
|
+
domain: {
|
|
36
|
+
x: [0.0, 1.0],
|
|
37
|
+
y: [0.0, 1.0]
|
|
38
|
+
},
|
|
39
|
+
hovertemplate: "Country: %{label}<br>Population: %{value}<br>Continent: %{parent}<br>Life expectancy: %{color}",
|
|
40
|
+
ids: ids,
|
|
41
|
+
labels: countries,
|
|
42
|
+
marker: {
|
|
43
|
+
coloraxis: "coloraxis",
|
|
44
|
+
colors: lifeExp
|
|
45
|
+
},
|
|
46
|
+
name: "",
|
|
47
|
+
parents: parents,
|
|
48
|
+
values: pop,
|
|
49
|
+
type: "treemap"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
layout: layout,
|
|
54
|
+
data: [trace]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getColorScale() {
|
|
59
|
+
var cmap = [
|
|
60
|
+
[103, 0, 31],
|
|
61
|
+
[178, 24, 43],
|
|
62
|
+
[214, 96, 77],
|
|
63
|
+
[244, 165, 130],
|
|
64
|
+
[253, 219, 199],
|
|
65
|
+
[247, 247, 247],
|
|
66
|
+
[209, 229, 240],
|
|
67
|
+
[146, 197, 222],
|
|
68
|
+
[67, 147, 195],
|
|
69
|
+
[33, 102, 172],
|
|
70
|
+
[5, 48, 97],
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
var nCmap = cmap.length - 1;
|
|
74
|
+
|
|
75
|
+
return cmap.map((c, i) => [1.0 * i / nCmap, 'rgb(' + c[0] + ', ' + c[1] + ', ' + c[2] + ')']);
|
|
76
|
+
}
|
simian/examples/logo.png
ADDED
|
Binary file
|