11from musica .micm import MICM , SolverType
22import musica .mechanism_configuration as mc
3- import matplotlib .pyplot as plt
4- from matplotlib import animation
5- from matplotlib .animation import FFMpegWriter , PillowWriter
63import argparse
74import os
85import numpy as np
@@ -116,7 +113,7 @@ def create_lorenz_mechanism():
116113 return mechanism
117114
118115
119- def main (output = 'lorenz.mp4' , fps = 30 , n = 2 ):
116+ def main (output = 'lorenz.mp4' , fps = 30 , n = 2 , plot = True ):
120117 """
121118 Run the Lorenz polynomial chemical reaction network simulation and save an animation.
122119 Parameters
@@ -128,6 +125,8 @@ def main(output='lorenz.mp4', fps=30, n=2):
128125 Frames per second for the generated animation. Defaults to 30.
129126 n : int, optional
130127 The number of grid cells to simulate the attractor in
128+ plot : bool, optional
129+ Whether to generate the animation. Defaults to True.
131130 Notes
132131 -----
133132 This function constructs the Lorenz mechanism, initializes the MICM solver, sets
@@ -195,98 +194,105 @@ def main(output='lorenz.mp4', fps=30, n=2):
195194
196195 print ("Simulation complete." )
197196
198- def create_animation (Xs , Ys , Zs , outpath = 'lorenz.mp4' , fps = 30 ):
199- # Xs, Ys, Zs are lists of length n each containing a time-series list
200- Ncells = len (Xs )
201- if Ncells == 0 :
202- print ("No trajectory data to animate." )
203- return
204-
205- fig = plt .figure ()
206- ax = fig .add_subplot (111 , projection = '3d' )
207-
208- ax .set_xlabel ('X Concentration' )
209- ax .set_ylabel ('Y Concentration' )
210- ax .set_zlabel ('Z Concentration' )
211- ax .set_title ('Lorenz Attractor from Chemical Reaction Network' )
212-
213- xmin = min (min (xs ) for xs in Xs )
214- xmax = max (max (xs ) for xs in Xs )
215- ymin = min (min (ys ) for ys in Ys )
216- ymax = max (max (ys ) for ys in Ys )
217- zmin = min (min (zs ) for zs in Zs )
218- zmax = max (max (zs ) for zs in Zs )
219- ax .set_xlim (xmin , xmax )
220- ax .set_ylim (ymin , ymax )
221- ax .set_zlim (zmin , zmax )
222- # create one line+point per grid cell
223- cmap = plt .get_cmap ('tab10' )
224- colors = [cmap (i % 10 ) for i in range (Ncells )]
225-
226- lines = []
227- points = []
228- for idx in range (Ncells ):
229- ln , = ax .plot ([], [], [], lw = 1 , color = colors [idx ], alpha = 0.5 , label = f'cell { idx } ' )
230- pt , = ax .plot ([], [], [], 'o' , color = colors [idx ], markersize = 3 )
231- lines .append (ln )
232- points .append (pt )
233-
234- def init ():
235- artists = []
236- for ln , pt in zip (lines , points ):
237- ln .set_data ([], [])
238- ln .set_3d_properties ([])
239- pt .set_data ([], [])
240- pt .set_3d_properties ([])
241- artists .extend ([ln , pt ])
242- return artists
243-
244- def update (i ):
245- artists = []
197+ if plot :
198+ import matplotlib .pyplot as plt
199+ from matplotlib import animation
200+ from matplotlib .animation import FFMpegWriter , PillowWriter
201+
202+ running_in_cibw = os .environ .get ("CIBUILDWHEEL" , "" ).lower () in {"1" , "true" , "yes" }
203+ if running_in_cibw :
204+ print ("Detected cibuildwheel; skipping animation generation." )
205+ return Xs , Ys , Zs
206+
207+ def create_animation (Xs , Ys , Zs , outpath = 'lorenz.mp4' , fps = 30 ):
208+ # Xs, Ys, Zs are lists of length n each containing a time-series list
209+ Ncells = len (Xs )
210+ if Ncells == 0 :
211+ print ("No trajectory data to animate." )
212+ return
213+
214+ fig = plt .figure ()
215+ ax = fig .add_subplot (111 , projection = '3d' )
216+
217+ ax .set_xlabel ('X Concentration' )
218+ ax .set_ylabel ('Y Concentration' )
219+ ax .set_zlabel ('Z Concentration' )
220+ ax .set_title ('Lorenz Attractor from Chemical Reaction Network' )
221+
222+ xmin = min (min (xs ) for xs in Xs )
223+ xmax = max (max (xs ) for xs in Xs )
224+ ymin = min (min (ys ) for ys in Ys )
225+ ymax = max (max (ys ) for ys in Ys )
226+ zmin = min (min (zs ) for zs in Zs )
227+ zmax = max (max (zs ) for zs in Zs )
228+ ax .set_xlim (xmin , xmax )
229+ ax .set_ylim (ymin , ymax )
230+ ax .set_zlim (zmin , zmax )
231+ # create one line+point per grid cell
232+ cmap = plt .get_cmap ('tab10' )
233+ colors = [cmap (i % 10 ) for i in range (Ncells )]
234+
235+ lines = []
236+ points = []
246237 for idx in range (Ncells ):
247- xs = Xs [idx ]
248- ys = Ys [idx ]
249- zs = Zs [idx ]
250- # clamp i for safety
251- j = min (i , len (xs ) - 1 )
252- lines [idx ].set_data (xs [:j ], ys [:j ])
253- lines [idx ].set_3d_properties (zs [:j ])
254- if j > 0 :
255- points [idx ].set_data ([xs [j - 1 ]], [ys [j - 1 ]])
256- points [idx ].set_3d_properties ([zs [j - 1 ]])
257- artists .extend ([lines [idx ], points [idx ]])
258- return artists
259-
260- frames = len (Xs [0 ])
261- interval = 1000.0 / fps
262-
263- anim = animation .FuncAnimation (
264- fig , update , init_func = init , frames = frames , interval = interval , blit = True )
265-
266- outdir = os .path .dirname (outpath ) or '.'
267- os .makedirs (outdir , exist_ok = True )
268-
269- try :
270- ax .legend ()
271- writer = FFMpegWriter (fps = fps )
272- anim .save (outpath , writer = writer )
273- print (f"Saved animation to { outpath } " )
274- except Exception :
238+ ln , = ax .plot ([], [], [], lw = 1 , color = colors [idx ], alpha = 0.5 , label = f'cell { idx } ' )
239+ pt , = ax .plot ([], [], [], 'o' , color = colors [idx ], markersize = 3 )
240+ lines .append (ln )
241+ points .append (pt )
242+
243+ def init ():
244+ artists = []
245+ for ln , pt in zip (lines , points ):
246+ ln .set_data ([], [])
247+ ln .set_3d_properties ([])
248+ pt .set_data ([], [])
249+ pt .set_3d_properties ([])
250+ artists .extend ([ln , pt ])
251+ return artists
252+
253+ def update (i ):
254+ artists = []
255+ for idx in range (Ncells ):
256+ xs = Xs [idx ]
257+ ys = Ys [idx ]
258+ zs = Zs [idx ]
259+ # clamp i for safety
260+ j = min (i , len (xs ) - 1 )
261+ lines [idx ].set_data (xs [:j ], ys [:j ])
262+ lines [idx ].set_3d_properties (zs [:j ])
263+ if j > 0 :
264+ points [idx ].set_data ([xs [j - 1 ]], [ys [j - 1 ]])
265+ points [idx ].set_3d_properties ([zs [j - 1 ]])
266+ artists .extend ([lines [idx ], points [idx ]])
267+ return artists
268+
269+ frames = len (Xs [0 ])
270+ interval = 1000.0 / fps
271+
272+ anim = animation .FuncAnimation (
273+ fig , update , init_func = init , frames = frames , interval = interval , blit = True )
274+
275+ outdir = os .path .dirname (outpath ) or '.'
276+ os .makedirs (outdir , exist_ok = True )
277+
275278 try :
276- gif_path = os .path .splitext (outpath )[0 ] + '.gif'
277- writer = PillowWriter (fps = fps )
278- anim .save (gif_path , writer = writer )
279- print (f"FFmpeg unavailable; saved GIF to { gif_path } " )
280- except Exception as e :
281- print ("Failed to save animation:" , e )
282-
283- running_in_cibw = os .environ .get ("CIBUILDWHEEL" , "" ).lower () in {"1" , "true" , "yes" }
284- if running_in_cibw :
285- print ("Detected cibuildwheel; skipping animation generation." )
286- return
287-
288- # Save animation with defaults; CLI can override via args below
289- create_animation (Xs , Ys , Zs , outpath = output , fps = fps )
279+ ax .legend ()
280+ writer = FFMpegWriter (fps = fps )
281+ anim .save (outpath , writer = writer )
282+ print (f"Saved animation to { outpath } " )
283+ except Exception :
284+ try :
285+ gif_path = os .path .splitext (outpath )[0 ] + '.gif'
286+ writer = PillowWriter (fps = fps )
287+ anim .save (gif_path , writer = writer )
288+ print (f"FFmpeg unavailable; saved GIF to { gif_path } " )
289+ except Exception as e :
290+ print ("Failed to save animation:" , e )
291+
292+ # Save animation with defaults; CLI can override via args below
293+ create_animation (Xs , Ys , Zs , outpath = output , fps = fps )
294+
295+ return Xs , Ys , Zs
290296
291297
292298if __name__ == "__main__" :
0 commit comments