1+ """
2+ Butterfly
3+ =========
4+
5+ This case simulates the motion of a rod that is initially shaped like a
6+ butterfly. The rod is released from rest and allowed to fall under gravity.
7+ The simulation tracks the position and energy of the rod over time.
8+ """
9+
110import numpy as np
211from matplotlib import pyplot as plt
312from matplotlib .colors import to_rgb
615import elastica as ea
716from elastica .utils import MaxDimension
817
18+ # %%
19+ # First, we define a simulator class that inherits from the necessary mixins.
20+
921
1022class ButterflySimulator (ea .BaseSystemCollection , ea .CallBacks ):
1123 pass
@@ -14,11 +26,10 @@ class ButterflySimulator(ea.BaseSystemCollection, ea.CallBacks):
1426butterfly_sim = ButterflySimulator ()
1527final_time = 40.0
1628
17- # Options
18- PLOT_FIGURE = True
19- SAVE_FIGURE = True
20- SAVE_RESULTS = True
21- ADD_UNSHEARABLE_ROD = False
29+ # %%
30+ # Next, we set up the test parameters for the simulation. This includes the
31+ # number of elements, the origin, the angle of inclination, the length,
32+ # radius, density, and Young's modulus of the rod.
2233
2334# setting up test params
2435# FIXME : Doesn't work with elements > 10 (the inverse rotate kernel fails)
@@ -44,6 +55,10 @@ class ButterflySimulator(ea.BaseSystemCollection, ea.CallBacks):
4455poisson_ratio = 0.5
4556shear_modulus = youngs_modulus / (poisson_ratio + 1.0 )
4657
58+ # %%
59+ # We then define the initial positions of the nodes of the rod to create the
60+ # butterfly shape.
61+
4762positions = np .empty ((MaxDimension .value (), n_elem + 1 ))
4863dl = total_length / n_elem
4964
@@ -60,6 +75,9 @@ class ButterflySimulator(ea.BaseSystemCollection, ea.CallBacks):
6075 - np .sin (angle_of_inclination ) * vertical_direction
6176)
6277
78+ # %%
79+ # Now we can create the `CosseratRod` object with the specified positions.
80+
6381butterfly_rod = ea .CosseratRod .straight_rod (
6482 n_elem ,
6583 start = origin .reshape (3 ),
@@ -76,6 +94,11 @@ class ButterflySimulator(ea.BaseSystemCollection, ea.CallBacks):
7694butterfly_sim .append (butterfly_rod )
7795
7896
97+ # %%
98+ # We define a callback class to record the position and energy of the rod
99+ # during the simulation.
100+
101+
79102# Add call backs
80103class VelocityCallBack (ea .CallBackBaseClass ):
81104 """
@@ -117,12 +140,17 @@ def make_callback(
117140 VelocityCallBack , step_skip = 100 , callback_params = recorded_history
118141)
119142
143+ # %%
144+ # We finalize the simulator and create the time-stepper.
120145
121146butterfly_sim .finalize ()
122147timestepper : ea .typing .StepperProtocol
123148timestepper = ea .PositionVerlet ()
124149# timestepper = PEFRL()
125150
151+ # %%
152+ # The simulation is run for the specified `final_time`.
153+
126154dt = 0.01 * dl
127155total_steps = int (final_time / dt )
128156print ("Total steps" , total_steps )
@@ -131,52 +159,43 @@ def make_callback(
131159for i in range (total_steps ):
132160 time = timestepper .step (butterfly_sim , time , dt )
133161
134- if PLOT_FIGURE :
135- # Plot the histories
136- fig = plt .figure (figsize = (5 , 4 ), frameon = True , dpi = 150 )
137- ax = fig .add_subplot (111 )
138- positions_history = recorded_history ["position" ]
139- # record first position
140- first_position = positions_history .pop (0 )
141- ax .plot (first_position [2 , ...], first_position [0 , ...], "r--" , lw = 2.0 )
142- n_positions = len (positions_history )
143- for i , pos in enumerate (positions_history ):
144- alpha = np .exp (i / n_positions - 1 )
145- ax .plot (pos [2 , ...], pos [0 , ...], "b" , lw = 0.6 , alpha = alpha )
146- # final position is also separate
147- last_position = positions_history .pop ()
148- ax .plot (last_position [2 , ...], last_position [0 , ...], "k--" , lw = 2.0 )
149- # don't block
150- fig .show ()
151-
152- # Plot the energies
153- energy_fig = plt .figure (figsize = (5 , 4 ), frameon = True , dpi = 150 )
154- energy_ax = energy_fig .add_subplot (111 )
155- times = np .asarray (recorded_history ["time" ])
156- te = np .asarray (recorded_history ["te" ])
157- re = np .asarray (recorded_history ["re" ])
158- be = np .asarray (recorded_history ["be" ])
159- se = np .asarray (recorded_history ["se" ])
160-
161- energy_ax .plot (times , te , c = to_rgb ("xkcd:reddish" ), lw = 2.0 , label = "Translations" )
162- energy_ax .plot (times , re , c = to_rgb ("xkcd:bluish" ), lw = 2.0 , label = "Rotation" )
163- energy_ax .plot (times , be , c = to_rgb ("xkcd:burple" ), lw = 2.0 , label = "Bend" )
164- energy_ax .plot (times , se , c = to_rgb ("xkcd:goldenrod" ), lw = 2.0 , label = "Shear" )
165- energy_ax .plot (times , te + re + be + se , c = "k" , lw = 2.0 , label = "Total energy" )
166- energy_ax .legend ()
167- # don't block
168- energy_fig .show ()
169-
170- if SAVE_FIGURE :
171- fig .savefig ("butterfly.png" )
172- energy_fig .savefig ("energies.png" )
173-
174- plt .show ()
175-
176- if SAVE_RESULTS :
177- import pickle
178-
179- filename = "butterfly_data.dat"
180- file = open (filename , "wb" )
181- pickle .dump (butterfly_rod , file )
182- file .close ()
162+ # %%
163+ # Finally, we plot the results. The position of the rod is plotted at
164+ # different time steps, and the energies are plotted as a function of time.
165+
166+ # Plot the histories
167+ fig = plt .figure (figsize = (5 , 4 ), frameon = True , dpi = 150 )
168+ ax = fig .add_subplot (111 )
169+ positions_history = recorded_history ["position" ]
170+ # record first position
171+ first_position = positions_history .pop (0 )
172+ ax .plot (first_position [2 , ...], first_position [0 , ...], "r--" , lw = 2.0 )
173+ n_positions = len (positions_history )
174+ for i , pos in enumerate (positions_history ):
175+ alpha = np .exp (i / n_positions - 1 )
176+ ax .plot (pos [2 , ...], pos [0 , ...], "b" , lw = 0.6 , alpha = alpha )
177+ # final position is also separate
178+ last_position = positions_history .pop ()
179+ ax .plot (last_position [2 , ...], last_position [0 , ...], "k--" , lw = 2.0 )
180+ # don't block
181+ fig .show ()
182+
183+ # Plot the energies
184+ energy_fig = plt .figure (figsize = (5 , 4 ), frameon = True , dpi = 150 )
185+ energy_ax = energy_fig .add_subplot (111 )
186+ times = np .asarray (recorded_history ["time" ])
187+ te = np .asarray (recorded_history ["te" ])
188+ re = np .asarray (recorded_history ["re" ])
189+ be = np .asarray (recorded_history ["be" ])
190+ se = np .asarray (recorded_history ["se" ])
191+
192+ energy_ax .plot (times , te , c = to_rgb ("xkcd:reddish" ), lw = 2.0 , label = "Translations" )
193+ energy_ax .plot (times , re , c = to_rgb ("xkcd:bluish" ), lw = 2.0 , label = "Rotation" )
194+ energy_ax .plot (times , be , c = to_rgb ("xkcd:burple" ), lw = 2.0 , label = "Bend" )
195+ energy_ax .plot (times , se , c = to_rgb ("xkcd:goldenrod" ), lw = 2.0 , label = "Shear" )
196+ energy_ax .plot (times , te + re + be + se , c = "k" , lw = 2.0 , label = "Total energy" )
197+ energy_ax .legend ()
198+ # don't block
199+ energy_fig .show ()
200+
201+ plt .show ()
0 commit comments