11import jax .numpy as jnp
2- from jax import vmap , lax
2+ from jax import vmap , lax , jit
3+ from tqdm import tqdm
34from diffmpm .node import Nodes
45from diffmpm .particle import Particles
56from diffmpm .shapefn import ShapeFn
7+ from jax .tree_util import register_pytree_node_class
8+ from jax_tqdm import loop_tqdm
9+ from functools import partial
610
11+ from jax import debug
712
13+
14+ @register_pytree_node_class
815class Mesh1D :
916 """
1017 1D Mesh class with nodes, elements, and particles.
@@ -16,8 +23,14 @@ def __init__(
1623 material ,
1724 domain_size ,
1825 boundary_nodes ,
26+ * ,
1927 ppe = 1 ,
2028 particle_distribution = "uniform" ,
29+ elements = None ,
30+ nodes = None ,
31+ particles = None ,
32+ shapefn = None ,
33+ dim = 1 ,
2134 ):
2235 """
2336 Construct a 1D Mesh.
@@ -36,20 +49,82 @@ def __init__(
3649 ppe : int
3750 Number of particles per element in Mesh.
3851 """
39- self .dim = 1
52+ self .dim = dim
4053 self .material = material
41- self .shapefn = ShapeFn (self .dim )
54+ self .shapefn = (
55+ ShapeFn (self .dim )
56+ if (
57+ shapefn is None
58+ or type (shapefn ) is object
59+ or isinstance (shapefn , Mesh1D )
60+ )
61+ else shapefn
62+ )
4263 self .domain_size = domain_size
4364 self .nelements = nelements
4465 self .element_length = domain_size / nelements
45- self .elements = jnp .arange (nelements )
46- self .nodes = Nodes (nelements + 1 )
47- self .nodes .position = jnp .arange (nelements + 1 ) * self .element_length
66+ self .elements = jnp .arange (nelements ) if elements is None else elements
67+ nnodes = nelements + 1
68+ self .nodes = (
69+ Nodes (
70+ nnodes ,
71+ jnp .arange (nelements + 1 ) * self .element_length ,
72+ jnp .zeros (nnodes ),
73+ jnp .zeros (nnodes ),
74+ jnp .zeros (nnodes ),
75+ jnp .zeros (nnodes ),
76+ jnp .zeros (nnodes ),
77+ jnp .zeros (nnodes ),
78+ )
79+ if (
80+ nodes is None
81+ or type (nodes ) is object
82+ or isinstance (nodes , Mesh1D )
83+ )
84+ else nodes
85+ )
4886 self .boundary_nodes = boundary_nodes
4987 self .ppe = ppe
50- self .particles = self ._init_particles (particle_distribution )
88+ self .particles = (
89+ self ._init_particles (particle_distribution )
90+ if (
91+ particles is None
92+ or type (particles ) is object
93+ or isinstance (particles , Mesh1D )
94+ )
95+ else particles
96+ )
5197 return
5298
99+ def tree_flatten (self ):
100+ "Flatten Pytree for JAX JIT compatibility."
101+ children = (self .nodes , self .particles )
102+ aux_data = (
103+ (
104+ self .nelements ,
105+ self .material ,
106+ self .domain_size ,
107+ self .boundary_nodes ,
108+ ),
109+ {
110+ "dim" : self .dim ,
111+ "shapefn" : self .shapefn ,
112+ "elements" : self .elements ,
113+ "ppe" : self .ppe ,
114+ },
115+ )
116+ return (children , aux_data )
117+
118+ @classmethod
119+ def tree_unflatten (cls , aux_data , children ):
120+ "Unflatten Pytree for JAX JIT compatibility."
121+ return cls (
122+ * aux_data [0 ],
123+ nodes = children [0 ],
124+ particles = children [1 ],
125+ ** aux_data [1 ],
126+ )
127+
53128 def _init_particles (self , distribution = "uniform" ):
54129 temp_px = jnp .linspace (0 , self .element_length , self .ppe + 1 )
55130 if distribution == "uniform" :
@@ -62,16 +137,23 @@ def _init_particles(self, distribution="uniform"):
62137 particle_element_ids = jnp .repeat (
63138 jnp .arange (self .nelements ), self .ppe
64139 )
140+ nparticles = (self .ppe * self .nelements ,)
65141 particles = Particles (
66142 pmass ,
67143 particles_x ,
68144 particles_xi ,
69- self .material ,
70145 self .material .density ,
71- self .ppe ,
72- self .nelements ,
73146 particle_element_ids ,
74- self .domain_size ,
147+ jnp .zeros (nparticles ),
148+ jnp .zeros (nparticles ),
149+ jnp .zeros (nparticles ),
150+ jnp .zeros (nparticles ),
151+ jnp .zeros (nparticles ),
152+ jnp .zeros (nparticles ),
153+ ppe = self .ppe ,
154+ nelements = self .nelements ,
155+ nparticles = nparticles ,
156+ material = self .material ,
75157 )
76158 return particles
77159 else :
@@ -101,6 +183,17 @@ def _get_element_node_vel(self, element_idx):
101183 """
102184 return self .nodes .velocity [jnp .asarray ([element_idx , element_idx + 1 ])]
103185
186+ def set_particle_velocity (self , vel ):
187+ """
188+ Set the velocities of all particles.
189+
190+ Arguments
191+ ---------
192+ vel : array_like
193+ Velocity for each particle in the mesh.
194+ """
195+ self .particles .velocity = vel
196+
104197 def _update_particle_element_ids (self ):
105198 """
106199 Find the element that the particles belong to.
@@ -109,6 +202,7 @@ def _update_particle_element_ids(self):
109202 element, it sets the element index to -1.
110203 """
111204
205+ @jit
112206 def f (x ):
113207 idl = (
114208 len (self .nodes .position )
@@ -332,6 +426,7 @@ def _transfer_node_force_vel_par(self, dt):
332426 )
333427 )
334428
429+ # @jit
335430 def _update_par_pos_node_mom (self , dt ):
336431 """
337432 Update particle position based on nodal momentum.
@@ -451,6 +546,7 @@ def step(pid, args):
451546 0 , len (self .particles ), step , args
452547 )
453548
549+ # @jit
454550 def _update_node_fint_par_mass (self ):
455551 r"""
456552 Update the nodal internal force based on particle mass.
@@ -500,6 +596,7 @@ def step(pid, args):
500596 0 , len (self .particles ), step , args
501597 )
502598
599+ # @jit
503600 def _update_node_fext_par_fext (self ):
504601 r"""
505602 Update the nodal external force based on particle f_ext.
@@ -529,21 +626,30 @@ def step(pid, args):
529626 0 , len (self .particles ), step , args
530627 )
531628
532- def solve (self , ** kwargs ):
533- """Solve the mesh using explicit scheme (for now)."""
629+ def solve (self , nsteps = 100 , mpm_scheme = "USF" , ** kwargs ):
630+ """
631+ Solve the mesh using explicit scheme (for now).
632+ """
534633 # TODO: Add flow control and argument checking
535- result = {"position" : [], "velocity" : []}
536- b1 = jnp .pi * 0.5 / self .domain_size
537- self .particles .velocity = 0.1 * jnp .sin (b1 * self .particles .x )
538- for step in range (kwargs ["nsteps" ]):
634+ result = {
635+ "position" : [],
636+ "velocity" : [],
637+ }
638+ for _ in tqdm (range (nsteps )):
639+ # print("="*30)
539640 self ._update_particle_natural_coords ()
641+ # print(f"P.xi: {self.particles.xi}")
540642 self ._update_particle_element_ids ()
643+ # print(f"P.eid: {self.particles.element_ids}")
541644 self ._update_node_momentum_par_vel ()
645+ # print(f"P.v: {self.particles.velocity}")
542646 self ._update_node_mass_par_mass ()
647+ # print(f"P.m: {self.particles.mass}")
543648 self ._update_nodes_bc_mom_vel ()
544- if kwargs [ " mpm_scheme" ] == "USF" :
649+ if mpm_scheme == "USF" :
545650 self ._update_nodes_mom_vel ()
546651 self ._update_particle_strain (kwargs ["dt" ])
652+ # print(f"P.strain: {self.particles.strain}")
547653 self ._update_par_vol_density ()
548654 self ._update_particle_stress ()
549655
@@ -553,16 +659,98 @@ def solve(self, **kwargs):
553659 self ._update_node_momentum_force (kwargs ["dt" ])
554660 self ._transfer_node_force_vel_par (kwargs ["dt" ])
555661 self ._update_par_pos_node_mom (kwargs ["dt" ])
556- if kwargs [ " mpm_scheme" ] == "MUSL" :
662+ if mpm_scheme == "MUSL" :
557663 self ._update_node_momentum_par_vel ()
558664 self ._update_nodes_bc_mom_vel ()
559665
560- if kwargs [ " mpm_scheme" ] == "MUSL" or kwargs [ "mpm_scheme" ] == " USL" :
666+ if mpm_scheme in { "MUSL" , " USL"} :
561667 self ._update_nodes_mom_vel ()
562668 self ._update_particle_strain (kwargs ["dt" ])
563669 self ._update_par_vol_density ()
564670 self ._update_particle_stress ()
565671 self .nodes .reset_values ()
566672 result ["position" ].append (self .particles .x )
567673 result ["velocity" ].append (self .particles .velocity )
674+ result = {k : jnp .asarray (v ) for k , v in result .items ()}
675+ return result
676+
677+ # @partial(jit, static_argnames=["nsteps", "mpm_scheme"])
678+ def solve_jit (self , nsteps = 100 , mpm_scheme = 0 , ** kwargs ):
679+ """
680+ Solve the mesh using explicit scheme (for now).
681+
682+ mpm_scheme:
683+ {
684+ "USF": 0,
685+ "USL": 1,
686+ "MUSL": 2
687+ }
688+ """
689+ # TODO: Add flow control and argument checking
690+ result = {
691+ "position" : jnp .zeros ((nsteps , self .particles .nparticles )),
692+ "velocity" : jnp .zeros ((nsteps , self .particles .nparticles )),
693+ }
694+
695+ @loop_tqdm (nsteps )
696+ def step (i , data ):
697+ self , mpm_scheme , dt , result = data
698+ self ._update_particle_natural_coords ()
699+ self ._update_particle_element_ids ()
700+ self ._update_node_momentum_par_vel ()
701+ self ._update_node_mass_par_mass ()
702+ self ._update_nodes_bc_mom_vel ()
703+
704+ # debug.breakpoint()
705+ # if mpm_scheme == 0:
706+ # def f(data):
707+ # s, dt = data
708+ # s._update_nodes_mom_vel()
709+ # s._update_particle_strain(dt)
710+ # s._update_par_vol_density()
711+ # s._update_particle_stress()
712+ # return None
713+
714+ # lax.cond(mpm_scheme == 0, f, lambda data: None, (self, dt))
715+ self ._update_nodes_mom_vel ()
716+ self ._update_particle_strain (dt )
717+ self ._update_par_vol_density ()
718+ self ._update_particle_stress ()
719+
720+ self ._update_node_fint_par_mass ()
721+ self ._update_node_fext_par_fext ()
722+ self ._update_nodes_bc_force ()
723+ self ._update_node_momentum_force (dt )
724+ self ._transfer_node_force_vel_par (dt )
725+ self ._update_par_pos_node_mom (dt )
726+
727+ # def f(s):
728+ # s._update_node_momentum_par_vel()
729+ # s._update_nodes_bc_mom_vel()
730+ # return None
731+
732+ # lax.cond(mpm_scheme == 2, f, lambda s: None, self)
733+
734+ # def f(data):
735+ # s, dt = data
736+ # s._update_nodes_mom_vel()
737+ # s._update_particle_strain(dt)
738+ # s._update_par_vol_density()
739+ # s._update_particle_stress()
740+ # return None
741+
742+ # lax.cond(mpm_scheme in {1, 2}, f, lambda data: None, (self, dt))
743+ self .nodes .reset_values ()
744+ result ["position" ] = (
745+ result ["position" ].at [i , :].set (self .particles .x )
746+ )
747+ result ["velocity" ] = (
748+ result ["velocity" ].at [i , :].set (self .particles .velocity )
749+ )
750+ return (self , mpm_scheme , dt , result )
751+
752+ _ , _ , _ , result = lax .fori_loop (
753+ 0 , nsteps , step , (self , 0 , kwargs ["dt" ], result )
754+ )
755+ result = {k : jnp .asarray (v ) for k , v in result .items ()}
568756 return result
0 commit comments