PyParticles3 0.4.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.
Files changed (100) hide show
  1. pyparticles/__init__.py +26 -0
  2. pyparticles/__main__.py +7 -0
  3. pyparticles/animation/__init__.py +21 -0
  4. pyparticles/animation/animated_cli.py +53 -0
  5. pyparticles/animation/animated_ogl.py +691 -0
  6. pyparticles/animation/animated_ogl_compat.py +243 -0
  7. pyparticles/animation/animated_scatter.py +94 -0
  8. pyparticles/animation/animation.py +251 -0
  9. pyparticles/animation/test_animation.py +310 -0
  10. pyparticles/demo/__init__.py +33 -0
  11. pyparticles/demo/bubble.py +106 -0
  12. pyparticles/demo/electromagnetic_demo.py +128 -0
  13. pyparticles/demo/electrostatic_demo.py +126 -0
  14. pyparticles/demo/fountain.py +478 -0
  15. pyparticles/demo/gas_lennard_jones.py +87 -0
  16. pyparticles/demo/gravity_clusters.py +73 -0
  17. pyparticles/demo/solar_system.py +222 -0
  18. pyparticles/demo/springs.py +141 -0
  19. pyparticles/demo/springs_constr.py +135 -0
  20. pyparticles/demo/test.py +34 -0
  21. pyparticles/forces/__init__.py +21 -0
  22. pyparticles/forces/const_force.py +140 -0
  23. pyparticles/forces/damping.py +174 -0
  24. pyparticles/forces/drag.py +167 -0
  25. pyparticles/forces/electromagnetic.py +105 -0
  26. pyparticles/forces/electromagnetic_field.py +95 -0
  27. pyparticles/forces/electrostatic.py +72 -0
  28. pyparticles/forces/force.py +75 -0
  29. pyparticles/forces/force_constrained.py +32 -0
  30. pyparticles/forces/fused_const_drag.py +445 -0
  31. pyparticles/forces/gravity.py +345 -0
  32. pyparticles/forces/lennard_jones.py +74 -0
  33. pyparticles/forces/linear_spring.py +86 -0
  34. pyparticles/forces/linear_spring_constrained.py +72 -0
  35. pyparticles/forces/multiple_force.py +134 -0
  36. pyparticles/forces/pseudo_bubble.py +226 -0
  37. pyparticles/forces/van_der_waals_force.py +60 -0
  38. pyparticles/forces/vector_field_force.py +52 -0
  39. pyparticles/geometry/__init__.py +21 -0
  40. pyparticles/geometry/dist.py +24 -0
  41. pyparticles/geometry/intersection.py +62 -0
  42. pyparticles/geometry/transformations.py +387 -0
  43. pyparticles/main/__init__.py +21 -0
  44. pyparticles/main/main.py +466 -0
  45. pyparticles/measures/__init__.py +21 -0
  46. pyparticles/measures/elastic_potential_energy.py +67 -0
  47. pyparticles/measures/gravitational_potential_energy.py +69 -0
  48. pyparticles/measures/kinetic_energy.py +68 -0
  49. pyparticles/measures/mass.py +59 -0
  50. pyparticles/measures/measure.py +156 -0
  51. pyparticles/measures/momentum.py +144 -0
  52. pyparticles/measures/total_energy.py +68 -0
  53. pyparticles/ode/__init__.py +21 -0
  54. pyparticles/ode/euler_solver.py +214 -0
  55. pyparticles/ode/euler_solver_constrained.py +49 -0
  56. pyparticles/ode/leapfrog_solver.py +37 -0
  57. pyparticles/ode/leapfrog_solver_constrained.py +53 -0
  58. pyparticles/ode/midpoint_solver.py +43 -0
  59. pyparticles/ode/midpoint_solver_constrained.py +60 -0
  60. pyparticles/ode/ode_solver.py +134 -0
  61. pyparticles/ode/ode_solver_constrained.py +43 -0
  62. pyparticles/ode/runge_kutta_solver.py +79 -0
  63. pyparticles/ode/runge_kutta_solver_constrained.py +98 -0
  64. pyparticles/ode/sim_time.py +56 -0
  65. pyparticles/ode/stormer_verlet_solver.py +50 -0
  66. pyparticles/ode/stormer_verlet_solver_constrained.py +73 -0
  67. pyparticles/ogl/__init__.py +21 -0
  68. pyparticles/ogl/axis_ogl.py +210 -0
  69. pyparticles/ogl/draw_particles_ogl.py +313 -0
  70. pyparticles/ogl/draw_particles_ogl_compat.py +509 -0
  71. pyparticles/ogl/draw_vector_field.py +221 -0
  72. pyparticles/ogl/opencl_gl_vbo.py +485 -0
  73. pyparticles/ogl/trackball.py +131 -0
  74. pyparticles/ogl/translate_scene.py +87 -0
  75. pyparticles/pset/__init__.py +21 -0
  76. pyparticles/pset/boundary.py +69 -0
  77. pyparticles/pset/cluster.py +28 -0
  78. pyparticles/pset/constrained_force_interactions.py +63 -0
  79. pyparticles/pset/constrained_x.py +158 -0
  80. pyparticles/pset/constraint.py +42 -0
  81. pyparticles/pset/default_boundary.py +43 -0
  82. pyparticles/pset/file_cluster.py +131 -0
  83. pyparticles/pset/logger.py +152 -0
  84. pyparticles/pset/octree.py +451 -0
  85. pyparticles/pset/opencl_context.py +374 -0
  86. pyparticles/pset/particles_set.py +499 -0
  87. pyparticles/pset/periodic_boundary.py +36 -0
  88. pyparticles/pset/rand_cluster.py +188 -0
  89. pyparticles/pset/rebound_boundary.py +68 -0
  90. pyparticles/utils/__init__.py +21 -0
  91. pyparticles/utils/parse_args.py +72 -0
  92. pyparticles/utils/problem_config.py +608 -0
  93. pyparticles/utils/pypart_global.py +122 -0
  94. pyparticles/utils/time_formatter.py +50 -0
  95. pyparticles3-0.4.0.dist-info/METADATA +395 -0
  96. pyparticles3-0.4.0.dist-info/RECORD +100 -0
  97. pyparticles3-0.4.0.dist-info/WHEEL +5 -0
  98. pyparticles3-0.4.0.dist-info/entry_points.txt +3 -0
  99. pyparticles3-0.4.0.dist-info/licenses/LICENSE-gpl-3.0.txt +674 -0
  100. pyparticles3-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,59 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import numpy as np
18
+
19
+ class Mass( object ):
20
+ """
21
+ 'Meaure' for computing the total mass of the particle system
22
+ """
23
+ def __init__( self , pset=None, force=None ):
24
+ super( Mass , self ).__init__( pset=pset , force=force )
25
+ self.__M = 0.0
26
+
27
+
28
+ def update_measure( self ):
29
+ """
30
+ Compute and return the totale mass of the system
31
+ """
32
+ self.__M = np.sum( self.pset.M[:] )
33
+ return self.__M
34
+
35
+
36
+ def value(self):
37
+ """
38
+ Return the current measured total mass
39
+ """
40
+ return self.__M
41
+
42
+
43
+ def shape(self ):
44
+ """
45
+ return a tuple containing the shape of the measures dataset
46
+ """
47
+ return 1,
48
+
49
+ def dim( self ):
50
+ """
51
+ return the dimension of the measure: 1 for the mass
52
+ """
53
+ return 1
54
+
55
+ def name(self):
56
+ """
57
+ Return the string: "mass"
58
+ """
59
+ return "mass"
@@ -0,0 +1,156 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import numpy as np
18
+
19
+ class Measure( object ):
20
+ """
21
+ Main abstract class for defining the measurement procedures of the system, for example the total kinetic energy.
22
+ """
23
+ def __init__( self , pset=None , force=None ):
24
+ """
25
+ Constructor
26
+
27
+ :param pset: The particles set
28
+ :param force: The model of the used force
29
+ """
30
+ self.__pset = pset
31
+ self.__force = force
32
+ self.__par = dict()
33
+
34
+ self.__str_f = "%f"
35
+
36
+ def get_pset( self ):
37
+ return self.__pset
38
+
39
+ def set_pset( self , pset ):
40
+ self.__pset = pset
41
+
42
+ pset = property( get_pset , set_pset , doc="set and get the current measured particle set" )
43
+
44
+
45
+ def get_force( self ):
46
+ return self.__force
47
+
48
+ def set_force( self , force ):
49
+ self.__force = force
50
+
51
+ force = property( get_force , set_force , doc="set and get the current force model" )
52
+
53
+
54
+ def get_parameter( self , name , val ):
55
+ """
56
+ return the reference to the dict of the used parameter
57
+ A parameter should be the volume, some constant ....
58
+ """
59
+ return self.__par
60
+
61
+ parameter = property( get_parameter , doc="return the reference to the dict of the used paramenter" )
62
+
63
+
64
+ def update_measure( self ):
65
+ """
66
+ compute and return the value of the measured quantity
67
+ """
68
+ NotImplementedError(" %s : is virtual and must be overridden." % sys._getframe().f_code.co_name )
69
+
70
+
71
+ def value( self ):
72
+ """
73
+ Return the value of the current measure
74
+ """
75
+ NotImplementedError(" %s : is virtual and must be overridden." % sys._getframe().f_code.co_name )
76
+
77
+ def set_str_format( self , f="%5.3f" ):
78
+ self.__str_f = f
79
+
80
+ def get_str_format( self ):
81
+ return self.__str_f
82
+
83
+ str_format = property( get_str_format , set_str_format , doc="get ad set the string format for representing the value" )
84
+
85
+ def value_str( self ):
86
+ """
87
+ return a string containig the value of the current neasure formmatted according to the format defined with the str_property format. By default if uses the simple floaf format
88
+ """
89
+ return self.str_format % ( self.value() )
90
+
91
+ def shape(self ):
92
+ """
93
+ return a tuple containing the shape of the measures dataset
94
+ """
95
+ NotImplementedError(" %s : is virtual and must be overridden." % sys._getframe().f_code.co_name )
96
+
97
+ def dim( self ):
98
+ """
99
+ return the dimension of the measure, for the dimensionless measure it must return 1 (like kinetic energy or mass)
100
+ """
101
+ NotImplementedError(" %s : is virtual and must be overridden." % sys._getframe().f_code.co_name )
102
+
103
+ def name(self):
104
+ """
105
+ return a string containig the name of the measure
106
+ """
107
+ NotImplementedError(" %s : is virtual and must be overridden." % sys._getframe().f_code.co_name )
108
+
109
+
110
+
111
+
112
+
113
+ class MeasureParticles( Measure ):
114
+ """
115
+ Abstract class used fopr measuring a subset of partiles or a set of singles particles.
116
+ """
117
+ def __init__( self , pset=None , force=None , subset=None , model="part_by_part" ):
118
+ """
119
+ Constructor:
120
+
121
+ :param pset: The particles set
122
+ :param force: The model of the used force
123
+ :param subset: a numpy 1D array containing the indicies of the measured particles
124
+ :param model: a strung describing the model for the measure: "part_by_part" or "subsystem"
125
+
126
+ """
127
+ self.__subset = np.copy(subset)
128
+
129
+ self.__model = None
130
+
131
+ self.model = model
132
+
133
+ super( MeasureParticles , self ).__init__( pset=pset , force=force )
134
+
135
+
136
+ def set_subset( self , subset ):
137
+ self.__subset = np.copy(subset)
138
+
139
+ def get_subset( self ):
140
+ return self.__subset
141
+
142
+
143
+ subset = property( get_subset , set_subset , doc="get and set the subset of particles to be measured" )
144
+
145
+
146
+ def get_model ( self ):
147
+ return self.__model
148
+
149
+ def set_model ( self , model ):
150
+ if model not in [ "part_by_part" , "subsystem" ] :
151
+ ValueError
152
+
153
+ self.__model = model
154
+
155
+ model = property( get_model , set_model , doc="set and get the measurement model: \"part_by_part\" or \"subsystem\" ")
156
+
@@ -0,0 +1,144 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import numpy as np
18
+ import scipy.spatial.distance as dist
19
+
20
+ import pyparticles.measures.measure as me
21
+
22
+ class MomentumSystem( me.Measure ):
23
+ """
24
+ 'Measure' for computing the total momentum of the particle system
25
+ """
26
+ def __init__( self , pset=None ):
27
+
28
+ if pset != None :
29
+ self.__P = np.zeros(( pset.dim ))
30
+ else :
31
+ self.__P = None
32
+
33
+ super( MomentumSystem , self ).__init__( pset=pset , force=None )
34
+
35
+
36
+ def value(self):
37
+ """
38
+ return the current value of the total momentum
39
+ """
40
+ return self.__P
41
+
42
+
43
+ def update_measure( self ):
44
+ """
45
+ Compute and return the total momentum of the system
46
+ """
47
+
48
+ self.__P = np.sum( self.pset.V * self.pset.M , 0 )
49
+
50
+ return self.__P
51
+
52
+
53
+ def shape( self ):
54
+ """
55
+ return a tuple containing the shape of the measures dataset
56
+ """
57
+ return ( self.pset.dim )
58
+
59
+ def dim( self ):
60
+ """
61
+ return the dimension of the measure: Dim for the momentum
62
+ """
63
+ return self.pset.dim
64
+
65
+ def name(self):
66
+ """
67
+ Return the string: "momentum"
68
+ """
69
+ return "momentum"
70
+
71
+
72
+
73
+ class MomentumParticles( me.MeasureParticles ):
74
+ """
75
+ 'Measure' for computing the momentum particle by particle or of a subsystem
76
+ Example: ::
77
+
78
+ P = MomentumParticles( pset , subset=np.array([1,4,5]) , model="part_by_part")
79
+ P.update_measure()
80
+ print( P.value )
81
+ > [[ 1.1 , 2.3 , 3.2 ],
82
+ > [ 1.7 , 5.2 , 6.9 ],
83
+ > [ 1.8 , 2.3 , 1.7 ]
84
+ > ]
85
+
86
+ Constructor:
87
+
88
+ :param pset The particles set
89
+ :param force The model of the used force
90
+ :param subset a numpy 1D array containing the indicies of the measured particles
91
+ :param model a strung describing the model for the measure: "part_by_part" or "subsystem"
92
+ """
93
+ def __init__( self , pset=None , force=None , subset=None , model="part_by_part" ):
94
+
95
+ if pset != None and model == "subsystem" :
96
+ self.__P = np.zeros(( pset.dim ))
97
+ elif pset != None and model == "part_by_part" and subset != None :
98
+ self.__P = np.zeros(( len(subset) , pset.dim ))
99
+ else :
100
+ self.__P = None
101
+
102
+ super( MomentumParticles , self ).__init__( pset , force , subset , model )
103
+
104
+
105
+ def value(self):
106
+ """
107
+ return the current value of the momentum
108
+ """
109
+ return self.__P
110
+
111
+
112
+ def update_measure( self ):
113
+ """
114
+ Compute and return the total momentum of the specified particles
115
+ """
116
+
117
+ if self.model == "part_by_part" :
118
+ self.__P = self.pset.V[self.subset] * self.pset.M[self.subset]
119
+ else :
120
+ self.__P = np.sum( self.pset.V[self.subset] * self.pset.M[self.subset] , 0 )
121
+
122
+ return self.__P
123
+
124
+
125
+ def shape( self ):
126
+ """
127
+ return a tuple containing the shape of the measures dataset
128
+ """
129
+ if self.model == "part_by_part" :
130
+ return ( len( self.subset ) , pset.dim )
131
+ else :
132
+ return ( 1 , pset.dim )
133
+
134
+ def dim( self ):
135
+ """
136
+ return the dimension of the measure: Dim: (2D or 3D) for the momentum
137
+ """
138
+ return self.pset.dim
139
+
140
+ def name(self):
141
+ """
142
+ Return the string: "momentum"
143
+ """
144
+ return "momentum"
@@ -0,0 +1,68 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import numpy as np
18
+ import scipy.spatial.distance as dist
19
+
20
+ import pyparticles.measures.measure as me
21
+
22
+ class TotalEnergy( me.Measure ):
23
+ """
24
+ Class derived from Measure used for computing the total energy of the particle system
25
+ given a potential and a kinetic energy it simply sum the two value
26
+ """
27
+ def __init__( self , kinetic , potential ):
28
+
29
+ self.__kinetic = kinetic
30
+ self.__potential = potential
31
+
32
+ self.__tot = 0.0
33
+
34
+ super( TotalEnergy , self ).__init__( pset=None , force=None )
35
+
36
+
37
+ def value(self):
38
+ """
39
+ return the current value of the potential energy
40
+ """
41
+ self.__tot = self.__kinetic.value() + self.__potential.value()
42
+ return self.__tot
43
+
44
+
45
+ def update_measure( self ):
46
+ """
47
+ Compute and return the total energy on the current state of pset
48
+ """
49
+ return self.__tot
50
+
51
+
52
+ def shape( self ):
53
+ """
54
+ return a tuple containing the shape of the measures dataset
55
+ """
56
+ return 1,
57
+
58
+ def dim( self ):
59
+ """
60
+ return the dimension of the measure: 1 for the energy
61
+ """
62
+ return 1
63
+
64
+ def name(self):
65
+ """
66
+ Return the string: "total energy"
67
+ """
68
+ return "total energy"
@@ -0,0 +1,21 @@
1
+ # PyParticles : Particles simulation in python
2
+ #Copyright (C) 2012 Simone Riva
3
+ #
4
+ #This program is free software: you can redistribute it and/or modify
5
+ #it under the terms of the GNU General Public License as published by
6
+ #the Free Software Foundation, either version 3 of the License, or
7
+ #(at your option) any later version.
8
+ #
9
+ #This program is distributed in the hope that it will be useful,
10
+ #but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ #GNU General Public License for more details.
13
+ #
14
+ #You should have received a copy of the GNU General Public License
15
+ #along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+
18
+ import os
19
+ import glob
20
+
21
+ __all__ = [ os.path.splitext( os.path.basename(f) )[0] for f in glob.glob(os.path.dirname(os.path.abspath(__file__))+"/*.py")]
@@ -0,0 +1,214 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ import numpy as np
10
+
11
+ import pyparticles.ode.ode_solver as os
12
+ import pyparticles.pset.opencl_context as occ
13
+
14
+ try:
15
+ import pyopencl as cl
16
+ except ImportError:
17
+ cl = None
18
+
19
+
20
+ class EulerSolver(os.OdeSolver):
21
+ def __init__(self, force, p_set, dt):
22
+ super(EulerSolver, self).__init__(force, p_set, dt)
23
+
24
+ def __step__(self, dt):
25
+ self.force.update_force(self.pset)
26
+ self.pset.V[:] = self.pset.V + self.force.A * dt
27
+ self.pset.X[:] = self.pset.X + self.pset.V * dt
28
+ self.pset.update_boundary()
29
+
30
+
31
+ class EulerSolverOCL(os.OdeSolver):
32
+ """Euler integrator with persistent OpenCL X/V/A buffers.
33
+
34
+ ``sync_velocity`` defaults to True to preserve the historical API where
35
+ ``pset.V`` is current immediately after every step. Rendering-only demos
36
+ may disable it. Boundaries implementing ``needs_update`` then trigger a V
37
+ download only on frames where a particle actually crosses the domain.
38
+
39
+ ``sync_positions`` defaults to True because the legacy renderer consumes
40
+ host ``pset.X`` every frame. Compute-only users, or a renderer backed by an
41
+ OpenCL/OpenGL shared buffer, may disable it so X never crosses PCIe during
42
+ the integration hot path. A host-side boundary still forces X current.
43
+
44
+ Device forces may optionally expose ``euler_step_device``. Fused calls
45
+ receive the current simulation time and step count so device-side boundary
46
+ or source models can evolve without synchronizing through the host.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ force,
52
+ p_set,
53
+ dt,
54
+ ocl_context=None,
55
+ sync_velocity=True,
56
+ sync_positions=True,
57
+ ):
58
+ if cl is None:
59
+ raise RuntimeError("PyOpenCL is required for EulerSolverOCL")
60
+ if p_set.dim != 3:
61
+ raise ValueError("EulerSolverOCL currently supports only 3 dimensions")
62
+
63
+ super(EulerSolverOCL, self).__init__(force, p_set, dt)
64
+
65
+ if ocl_context is None:
66
+ self.__occ = occ.OpenCLcontext(
67
+ self.pset.size,
68
+ self.pset.dim,
69
+ occ.OCLC_X | occ.OCLC_V | occ.OCLC_A,
70
+ )
71
+ else:
72
+ self.__occ = ocl_context
73
+
74
+ self.__sync_velocity = bool(sync_velocity)
75
+ self.__sync_positions = bool(sync_positions)
76
+ self.__init_prog_cl()
77
+
78
+ def __init_prog_cl(self):
79
+ source = r"""
80
+ __kernel void euler(
81
+ __global float *V,
82
+ __global const float *A,
83
+ __global float *X,
84
+ float dt)
85
+ {
86
+ int i = get_global_id(0);
87
+ int i0 = 3*i;
88
+ int i1 = i0 + 1;
89
+ int i2 = i0 + 2;
90
+
91
+ V[i0] = V[i0] + A[i0]*dt;
92
+ V[i1] = V[i1] + A[i1]*dt;
93
+ V[i2] = V[i2] + A[i2]*dt;
94
+
95
+ X[i0] = X[i0] + V[i0]*dt;
96
+ X[i1] = X[i1] + V[i1]*dt;
97
+ X[i2] = X[i2] + V[i2]*dt;
98
+ }
99
+ """
100
+ self.__cl_program = cl.Program(self.__occ.CL_context, source).build()
101
+ self.__euler_kernel = cl.Kernel(self.__cl_program, "euler")
102
+
103
+ def _has_device_force(self):
104
+ return (
105
+ hasattr(self.force, "update_force_device")
106
+ and getattr(self.force, "ocl_context", None) is self.__occ
107
+ )
108
+
109
+ def _has_fused_euler_force(self):
110
+ return (
111
+ hasattr(self.force, "euler_step_device")
112
+ and getattr(self.force, "ocl_context", None) is self.__occ
113
+ )
114
+
115
+ def __step__(self, dt):
116
+ if self._has_fused_euler_force():
117
+ self.__occ.sync_to_device("X", self.pset.X)
118
+ self.__occ.sync_to_device("V", self.pset.V)
119
+ self.force.euler_step_device(
120
+ self.pset,
121
+ dt,
122
+ sim_time=self.time,
123
+ step=self.steps_cnt,
124
+ )
125
+ else:
126
+ if self._has_device_force():
127
+ self.force.update_force_device(self.pset)
128
+ else:
129
+ self.__occ.sync_to_host("X", self.pset.X)
130
+ self.__occ.sync_to_host("V", self.pset.V)
131
+ self.force.update_force(self.pset)
132
+ self.__occ.set_from_host("A", self.force.A)
133
+
134
+ self.__occ.sync_to_device("X", self.pset.X)
135
+ self.__occ.sync_to_device("V", self.pset.V)
136
+
137
+ self.__euler_kernel(
138
+ self.__occ.CL_queue,
139
+ (self.pset.size,),
140
+ None,
141
+ self.__occ.V_cla.data,
142
+ self.__occ.A_cla.data,
143
+ self.__occ.X_cla.data,
144
+ np.float32(dt),
145
+ )
146
+ self.__occ.mark_device_modified("X")
147
+ self.__occ.mark_device_modified("V")
148
+
149
+ boundary = self.pset.boundary
150
+ need_host_positions = (
151
+ self.__sync_positions
152
+ or self.pset.log_X_enabled
153
+ or boundary is not None
154
+ )
155
+ if need_host_positions:
156
+ self.__occ.sync_to_host("X", self.pset.X)
157
+
158
+ boundary_active = False
159
+ if boundary is not None:
160
+ needs_update = getattr(boundary, "needs_update", None)
161
+ if needs_update is None:
162
+ boundary_active = True
163
+ else:
164
+ boundary_active = bool(needs_update(self.pset))
165
+
166
+ need_host_velocity = (
167
+ self.__sync_velocity
168
+ or self.pset.log_V_enabled
169
+ or boundary_active
170
+ )
171
+ if need_host_velocity:
172
+ self.__occ.sync_to_host("V", self.pset.V)
173
+
174
+ if boundary is not None and boundary_active:
175
+ changed = boundary.boundary(self.pset)
176
+ changed = True if changed is None else bool(changed)
177
+ if changed:
178
+ self.__occ.mark_host_modified("X")
179
+ self.__occ.mark_host_modified("V")
180
+
181
+ def sync_to_host(self, velocity=True):
182
+ """Explicitly synchronize resident state for external host consumers."""
183
+ self.__occ.sync_to_host("X", self.pset.X)
184
+ if velocity:
185
+ self.__occ.sync_to_host("V", self.pset.V)
186
+ return self.pset
187
+
188
+ def notify_host_modified(self, positions=True, velocities=True):
189
+ """Tell the solver that external code changed host X and/or V."""
190
+ if positions:
191
+ self.__occ.mark_host_modified("X")
192
+ if velocities:
193
+ self.__occ.mark_host_modified("V")
194
+
195
+ def get_ocl_context(self):
196
+ return self.__occ
197
+
198
+ ocl_context = property(get_ocl_context)
199
+
200
+ def get_sync_velocity(self):
201
+ return self.__sync_velocity
202
+
203
+ def set_sync_velocity(self, value):
204
+ self.__sync_velocity = bool(value)
205
+
206
+ sync_velocity = property(get_sync_velocity, set_sync_velocity)
207
+
208
+ def get_sync_positions(self):
209
+ return self.__sync_positions
210
+
211
+ def set_sync_positions(self, value):
212
+ self.__sync_positions = bool(value)
213
+
214
+ sync_positions = property(get_sync_positions, set_sync_positions)