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,345 @@
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
+ import scipy.spatial.distance as dist
11
+
12
+ import pyparticles.forces.force as fr
13
+ import pyparticles.pset.opencl_context as occ
14
+
15
+ try:
16
+ import pyopencl as cl
17
+ except ImportError:
18
+ cl = None
19
+
20
+
21
+ class Gravity(fr.Force):
22
+ """Compute pairwise Newtonian gravitational acceleration."""
23
+
24
+ def __init__(self, size, dim=3, m=None, Consts=1.0):
25
+ self.__dim = int(dim)
26
+ self.__size = int(size)
27
+ self.__G = Consts
28
+ self.__A = np.zeros((size, dim))
29
+ self.__Fm = np.zeros((size, size))
30
+ self.__V = np.zeros((size, size))
31
+ self.__D = np.zeros((size, size))
32
+ self.__M = np.zeros((size, size))
33
+
34
+ if m is not None:
35
+ self.set_masses(m)
36
+
37
+ def set_masses(self, m):
38
+ self.__M[:, :] = m
39
+
40
+ def update_force(self, p_set):
41
+ self.__D[:] = dist.squareform(dist.pdist(p_set.X, "euclidean"))
42
+
43
+ with np.errstate(divide="ignore", invalid="ignore"):
44
+ self.__Fm[:] = -self.__G * self.__M / self.__D**3.0
45
+ np.fill_diagonal(self.__Fm, 0.0)
46
+
47
+ for i in range(self.__dim):
48
+ self.__V[:, :] = p_set.X[:, i]
49
+ self.__V[:, :] = (self.__V.T - p_set.X[:, i]).T
50
+ self.__A[:, i] = np.sum(self.__Fm * self.__V, axis=0)
51
+
52
+ return self.__A
53
+
54
+ def getA(self):
55
+ return self.__A
56
+
57
+ A = property(getA)
58
+
59
+ def getF(self):
60
+ return self.__A * self.__M[:, 0:1]
61
+
62
+ F = property(getF)
63
+
64
+
65
+ class GravityOCL(fr.Force):
66
+ """OpenCL implementation of 3-D pairwise gravity.
67
+
68
+ ``kernel_mode='tiled'`` is the optimized default. Each work-group loads a
69
+ tile of positions and masses into OpenCL local memory and reuses it for all
70
+ target particles in the group. ``kernel_mode='naive'`` preserves the
71
+ original global-memory algorithm for regression and benchmarking.
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ size,
77
+ dim=3,
78
+ m=None,
79
+ Consts=1.0,
80
+ ocl_context=None,
81
+ kernel_mode="tiled",
82
+ tile_size=128,
83
+ ):
84
+ if cl is None:
85
+ raise RuntimeError("PyOpenCL is required for GravityOCL")
86
+ if int(dim) != 3:
87
+ raise ValueError("GravityOCL currently supports only 3 dimensions")
88
+ if kernel_mode not in ("tiled", "naive"):
89
+ raise ValueError("kernel_mode must be 'tiled' or 'naive'")
90
+
91
+ self.__dim = int(dim)
92
+ self.__size = int(size)
93
+
94
+ if ocl_context is None:
95
+ self.__occ = occ.OpenCLcontext(
96
+ size,
97
+ dim,
98
+ occ.OCLC_X | occ.OCLC_A | occ.OCLC_M,
99
+ )
100
+ else:
101
+ self.__occ = ocl_context
102
+
103
+ self.__dtype = self.__occ.dtype
104
+ self.__G = self.__dtype(Consts)
105
+ self.__A = np.zeros((size, dim), dtype=self.__dtype)
106
+ self.__M = np.zeros((size, 1), dtype=self.__dtype)
107
+ self.__kernel_mode = kernel_mode
108
+ self.__tile_size = self._choose_tile_size(tile_size)
109
+
110
+ self.__init_prog_cl()
111
+ if m is not None:
112
+ self.set_masses(m)
113
+
114
+ def _choose_tile_size(self, requested):
115
+ requested = max(1, int(requested))
116
+ device = self.__occ.CL_queue.device
117
+ max_wg = int(device.max_work_group_size)
118
+
119
+ # One float4 position plus one float mass per work-item.
120
+ max_by_local_mem = max(1, int(device.local_mem_size) // 20)
121
+ limit = min(requested, max_wg, max_by_local_mem)
122
+
123
+ # Prefer a power of two no larger than the requested/device limit.
124
+ tile = 1
125
+ while tile * 2 <= limit:
126
+ tile *= 2
127
+ return tile
128
+
129
+ def __init_prog_cl(self):
130
+ source = r"""
131
+ inline void store_acceleration(
132
+ int i,
133
+ int accumulate,
134
+ float ax,
135
+ float ay,
136
+ float az,
137
+ __global float *A)
138
+ {
139
+ int i0 = 3*i;
140
+ if (accumulate)
141
+ {
142
+ A[i0] += ax;
143
+ A[i0+1] += ay;
144
+ A[i0+2] += az;
145
+ }
146
+ else
147
+ {
148
+ A[i0] = ax;
149
+ A[i0+1] = ay;
150
+ A[i0+2] = az;
151
+ }
152
+ }
153
+
154
+ __kernel void gravity_naive(
155
+ __global const float *X,
156
+ __global const float *M,
157
+ float G,
158
+ int count,
159
+ int accumulate,
160
+ __global float *A)
161
+ {
162
+ int i = get_global_id(0);
163
+ if (i >= count) return;
164
+
165
+ int i0 = 3*i;
166
+ float xi = X[i0];
167
+ float yi = X[i0+1];
168
+ float zi = X[i0+2];
169
+ float ax = 0.0f;
170
+ float ay = 0.0f;
171
+ float az = 0.0f;
172
+
173
+ for (int n = 0; n < count; ++n)
174
+ {
175
+ if (n == i) continue;
176
+
177
+ float dx = xi - X[3*n];
178
+ float dy = yi - X[3*n+1];
179
+ float dz = zi - X[3*n+2];
180
+ float r2 = dx*dx + dy*dy + dz*dz;
181
+ if (r2 == 0.0f) continue;
182
+
183
+ float inv_r = rsqrt(r2);
184
+ float inv_r3 = inv_r * inv_r * inv_r;
185
+ float f = -G * M[n] * inv_r3;
186
+ ax += f * dx;
187
+ ay += f * dy;
188
+ az += f * dz;
189
+ }
190
+
191
+ store_acceleration(i, accumulate, ax, ay, az, A);
192
+ }
193
+
194
+ __kernel void gravity_tiled(
195
+ __global const float *X,
196
+ __global const float *M,
197
+ float G,
198
+ int count,
199
+ int accumulate,
200
+ __local float4 *tile_pos,
201
+ __local float *tile_mass,
202
+ __global float *A)
203
+ {
204
+ int i = get_global_id(0);
205
+ int lid = get_local_id(0);
206
+ int lsize = get_local_size(0);
207
+
208
+ float xi = 0.0f;
209
+ float yi = 0.0f;
210
+ float zi = 0.0f;
211
+ if (i < count)
212
+ {
213
+ int i0 = 3*i;
214
+ xi = X[i0];
215
+ yi = X[i0+1];
216
+ zi = X[i0+2];
217
+ }
218
+
219
+ float ax = 0.0f;
220
+ float ay = 0.0f;
221
+ float az = 0.0f;
222
+
223
+ for (int base = 0; base < count; base += lsize)
224
+ {
225
+ int j = base + lid;
226
+ if (j < count)
227
+ {
228
+ int j0 = 3*j;
229
+ tile_pos[lid] = (float4)(X[j0], X[j0+1], X[j0+2], 0.0f);
230
+ tile_mass[lid] = M[j];
231
+ }
232
+ else
233
+ {
234
+ tile_pos[lid] = (float4)(0.0f, 0.0f, 0.0f, 0.0f);
235
+ tile_mass[lid] = 0.0f;
236
+ }
237
+
238
+ barrier(CLK_LOCAL_MEM_FENCE);
239
+
240
+ if (i < count)
241
+ {
242
+ int tile_count = min(lsize, count - base);
243
+ for (int k = 0; k < tile_count; ++k)
244
+ {
245
+ int source_index = base + k;
246
+ if (source_index == i) continue;
247
+
248
+ float4 p = tile_pos[k];
249
+ float dx = xi - p.x;
250
+ float dy = yi - p.y;
251
+ float dz = zi - p.z;
252
+ float r2 = dx*dx + dy*dy + dz*dz;
253
+ if (r2 == 0.0f) continue;
254
+
255
+ float inv_r = rsqrt(r2);
256
+ float inv_r3 = inv_r * inv_r * inv_r;
257
+ float f = -G * tile_mass[k] * inv_r3;
258
+ ax += f * dx;
259
+ ay += f * dy;
260
+ az += f * dz;
261
+ }
262
+ }
263
+
264
+ barrier(CLK_LOCAL_MEM_FENCE);
265
+ }
266
+
267
+ if (i < count)
268
+ store_acceleration(i, accumulate, ax, ay, az, A);
269
+ }
270
+ """
271
+ self.__cl_program = cl.Program(self.__occ.CL_context, source).build()
272
+ self.__naive_kernel = cl.Kernel(self.__cl_program, "gravity_naive")
273
+ self.__tiled_kernel = cl.Kernel(self.__cl_program, "gravity_tiled")
274
+
275
+ def set_masses(self, m):
276
+ self.__M[:] = np.asarray(m, dtype=self.__dtype)
277
+ self.__occ.set_from_host("M", self.__M)
278
+
279
+ def update_force_device(self, p_set, accumulate=False, host_authoritative=False):
280
+ if host_authoritative:
281
+ self.__occ.mark_host_modified("X")
282
+ self.__occ.sync_to_device("X", p_set.X)
283
+
284
+ if self.__kernel_mode == "naive":
285
+ self.__naive_kernel(
286
+ self.__occ.CL_queue,
287
+ (self.__size,),
288
+ None,
289
+ self.__occ.X_cla.data,
290
+ self.__occ.M_cla.data,
291
+ self.__G,
292
+ np.int32(self.__size),
293
+ np.int32(bool(accumulate)),
294
+ self.__occ.A_cla.data,
295
+ )
296
+ else:
297
+ tile = self.__tile_size
298
+ global_size = ((self.__size + tile - 1) // tile) * tile
299
+ self.__tiled_kernel(
300
+ self.__occ.CL_queue,
301
+ (global_size,),
302
+ (tile,),
303
+ self.__occ.X_cla.data,
304
+ self.__occ.M_cla.data,
305
+ self.__G,
306
+ np.int32(self.__size),
307
+ np.int32(bool(accumulate)),
308
+ cl.LocalMemory(tile * 16),
309
+ cl.LocalMemory(tile * 4),
310
+ self.__occ.A_cla.data,
311
+ )
312
+
313
+ self.__occ.mark_device_modified("A")
314
+ return self.__occ.A_cla
315
+
316
+ def update_force(self, p_set):
317
+ self.update_force_device(p_set, accumulate=False, host_authoritative=True)
318
+ self.__occ.sync_to_host("A", self.__A)
319
+ return self.__A
320
+
321
+ def getA(self):
322
+ return self.__A
323
+
324
+ A = property(getA)
325
+
326
+ def getF(self):
327
+ self.__occ.sync_to_host("A", self.__A)
328
+ return self.__A * self.__M
329
+
330
+ F = property(getF)
331
+
332
+ def get_ocl_context(self):
333
+ return self.__occ
334
+
335
+ ocl_context = property(get_ocl_context)
336
+
337
+ def get_kernel_mode(self):
338
+ return self.__kernel_mode
339
+
340
+ kernel_mode = property(get_kernel_mode)
341
+
342
+ def get_tile_size(self):
343
+ return self.__tile_size
344
+
345
+ tile_size = property(get_tile_size)
@@ -0,0 +1,74 @@
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.forces.force as fr
21
+
22
+
23
+ class LenardJones(fr.Force):
24
+ r"""Compute the Lennard-Jones force between particles."""
25
+
26
+ def __init__(self, size, dim=3, m=None, Consts=(1.0, 1.0)):
27
+ self.__dim = dim
28
+ self.__size = size
29
+ self.__E = Consts[0]
30
+ self.__O = Consts[1]
31
+
32
+ self.__M = np.zeros((size, 1))
33
+ pair_count = (size * (size - 1)) // 2
34
+ self.__pF = np.zeros(pair_count)
35
+ self.__V = np.zeros((size, size))
36
+ self.__A = np.zeros((size, dim))
37
+
38
+ if m is not None:
39
+ self.set_masses(m)
40
+
41
+ def set_masses(self, m):
42
+ self.__M[:] = m
43
+
44
+ def update_force(self, p_set):
45
+ r = dist.pdist(p_set.X, "euclidean")
46
+
47
+ self.__pF[:] = (
48
+ 4.0
49
+ * self.__E
50
+ * (
51
+ 12.0 * self.__O**12.0 / r**13.0
52
+ - 6.0 * self.__O**6.0 / r**7.0
53
+ )
54
+ / r
55
+ )
56
+
57
+ F = dist.squareform(self.__pF)
58
+
59
+ for i in range(p_set.dim):
60
+ self.__V[:, :] = p_set.X[:, i]
61
+ self.__V[:, :] = (self.__V[:, :].T - p_set.X[:, i]).T
62
+ self.__A[:, i] = np.sum(F * self.__V[:, :] / self.__M.T, axis=0)
63
+
64
+ return self.__A
65
+
66
+ def getA(self):
67
+ return self.__A
68
+
69
+ A = property(getA, doc="Return the current accelerations")
70
+
71
+ def getF(self):
72
+ return self.__A * self.__M
73
+
74
+ F = property(getF, doc="Return the current forces")
@@ -0,0 +1,86 @@
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 sys
19
+ import scipy.spatial.distance as dist
20
+
21
+ import pyparticles.forces.force as fr
22
+
23
+ class LinearSpring( fr.Force ) :
24
+ r"""
25
+ Compute the forces according to the Hooke's law.
26
+
27
+ .. math::
28
+
29
+ F_i = -k X
30
+
31
+ :param size: size of the particles system
32
+ :param dim: dimension of the system
33
+ :param m: an array containing the masses
34
+ :param const: spring constant ( K )
35
+ """
36
+ def __init__(self , size , dim=3 , m=None , Consts=1.0 ):
37
+
38
+ self.__dim = dim
39
+ self.__size = size
40
+
41
+ self.__K = Consts
42
+
43
+ self.__A = np.zeros( ( size , dim ) )
44
+ self.__F = np.zeros( ( size , dim ) )
45
+ self.__Fm = np.zeros( ( size , size ) )
46
+
47
+ self.__M = np.zeros( ( size , 1 ) )
48
+ if m != None :
49
+ self.set_masses( m )
50
+
51
+
52
+ def set_masses( self , m ):
53
+ """
54
+ set the masses of the particles
55
+ """
56
+ self.__M[:] = m
57
+
58
+
59
+ def update_force( self , p_set ):
60
+
61
+ for i in range( self.__dim ):
62
+ self.__Fm[:,:] = p_set.X[:,i]
63
+ self.__Fm[:,:] = -self.__K * ( self.__Fm[:,:].T - p_set.X[:,i] ).T
64
+
65
+ self.__F[:,i] = np.sum( self.__Fm , 0 )
66
+
67
+ self.__A[:,:] = self.__F[:,:] / self.__M[:]
68
+
69
+ return self.__A
70
+
71
+ def getA(self):
72
+ return self.__A
73
+
74
+ A = property( getA )
75
+
76
+
77
+ def getF(self):
78
+ return self.__F
79
+
80
+ F = property( getF )
81
+
82
+
83
+ def get_const( self ):
84
+ return self.__K
85
+
86
+ const = property( get_const )
@@ -0,0 +1,72 @@
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
+ from scipy.sparse import dok_matrix
19
+
20
+ import pyparticles.forces.force_constrained as fcr
21
+
22
+
23
+ class LinearSpringConstrained(fcr.ForceConstrained):
24
+ def __init__(self, size, dim, m=None, Consts=1.0, f_inter=None):
25
+ super(LinearSpringConstrained, self).__init__(
26
+ size, dim, m, Consts, f_inter=f_inter
27
+ )
28
+
29
+ self.__dim = dim
30
+ self.__size = size
31
+ self.__K = Consts
32
+
33
+ self.__A = np.zeros((size, dim))
34
+ self.__F = np.zeros((size, dim))
35
+ self.__Fm = dok_matrix((size, size), dtype=float)
36
+ self.__M = np.zeros((size, 1))
37
+
38
+ if m is not None:
39
+ self.set_masses(m)
40
+
41
+ def set_masses(self, m):
42
+ self.__M[:] = m
43
+
44
+ def update_force(self, pset):
45
+ connections = list(self.force_interactions.sparse.keys())
46
+
47
+ for i in range(self.__dim):
48
+ self.__Fm.clear()
49
+ for k0, k1 in connections:
50
+ self.__Fm[k0, k1] = pset.X[k1, i]
51
+ self.__Fm[k1, k0] = pset.X[k0, i]
52
+
53
+ force_matrix = -self.__K * (self.__Fm.T - self.__Fm).T
54
+ self.__F[:, i] = np.asarray(force_matrix.sum(axis=0)).ravel()
55
+
56
+ self.__A[:] = self.__F / self.__M
57
+ return self.__A
58
+
59
+ def getA(self):
60
+ return self.__A
61
+
62
+ A = property(getA)
63
+
64
+ def getF(self):
65
+ return self.__F
66
+
67
+ F = property(getF)
68
+
69
+ def get_const(self):
70
+ return self.__K
71
+
72
+ const = property(get_const)
@@ -0,0 +1,134 @@
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
+
12
+ class MultipleForce(object):
13
+ """Combine several host force models into one acceleration field."""
14
+
15
+ def __init__(self, size, dim=3, m=None, Conts=None):
16
+ self.__forces = []
17
+ self.__M = np.zeros((size, 1))
18
+ self.__A = np.zeros((size, dim))
19
+ self.__F = np.zeros((size, dim))
20
+
21
+ if m is not None:
22
+ self.set_masses(m)
23
+
24
+ def append_force(self, force):
25
+ self.__forces.append(force)
26
+
27
+ def set_masses(self, m):
28
+ self.__M[:] = m
29
+ for force in self.__forces:
30
+ force.set_masses(m)
31
+
32
+ def update_force(self, p_set):
33
+ self.__A[:] = 0.0
34
+ for force in self.__forces:
35
+ self.__A[:] += force.update_force(p_set)
36
+
37
+ self.__F[:] = self.__A * self.__M
38
+ return self.__A
39
+
40
+ def getA(self):
41
+ return self.__A
42
+
43
+ A = property(getA)
44
+
45
+ def getF(self):
46
+ return self.__F
47
+
48
+ F = property(getF)
49
+
50
+
51
+ class MultipleForceOCL(object):
52
+ """Compose OpenCL forces directly in the shared device acceleration buffer.
53
+
54
+ Every appended force must expose ``update_force_device`` and share the
55
+ exact same :class:`OpenCLcontext`. The first force writes A and subsequent
56
+ forces accumulate into it, so no intermediate acceleration array crosses
57
+ PCIe.
58
+ """
59
+
60
+ def __init__(self, size, dim=3, m=None, ocl_context=None):
61
+ if ocl_context is None:
62
+ raise ValueError("MultipleForceOCL requires a shared OpenCL context")
63
+
64
+ self.__size = int(size)
65
+ self.__dim = int(dim)
66
+ self.__occ = ocl_context
67
+ self.__dtype = self.__occ.dtype
68
+ self.__forces = []
69
+ self.__M = np.zeros((size, 1), dtype=self.__dtype)
70
+ self.__A = np.zeros((size, dim), dtype=self.__dtype)
71
+ self.__F = np.zeros((size, dim), dtype=self.__dtype)
72
+
73
+ if m is not None:
74
+ self.set_masses(m)
75
+
76
+ def append_force(self, force):
77
+ if not hasattr(force, "update_force_device"):
78
+ raise TypeError("MultipleForceOCL accepts only device-capable forces")
79
+ if getattr(force, "ocl_context", None) is not self.__occ:
80
+ raise ValueError("All MultipleForceOCL forces must share one OpenCL context")
81
+ self.__forces.append(force)
82
+
83
+ def set_masses(self, m):
84
+ self.__M[:] = np.asarray(m, dtype=self.__dtype)
85
+ self.__occ.set_from_host("M", self.__M)
86
+
87
+ # Keep each component's host-side force property coherent. These are
88
+ # one-time setup transfers and do not affect the integration hot path.
89
+ for force in self.__forces:
90
+ force.set_masses(self.__M)
91
+
92
+ def update_force_device(self, p_set, accumulate=False, host_authoritative=False):
93
+ if not self.__forces:
94
+ self.__occ.A_cla.fill(self.__dtype(0.0), queue=self.__occ.CL_queue)
95
+ self.__occ.mark_device_modified("A")
96
+ return self.__occ.A_cla
97
+
98
+ # A composed force is normally the complete force model. If callers
99
+ # request accumulation into an existing A, every component accumulates;
100
+ # otherwise the first component initializes A and the rest add to it.
101
+ first_accumulate = bool(accumulate)
102
+ for index, force in enumerate(self.__forces):
103
+ force.update_force_device(
104
+ p_set,
105
+ accumulate=(first_accumulate or index > 0),
106
+ host_authoritative=host_authoritative,
107
+ )
108
+ self.__occ.mark_device_modified("A")
109
+ return self.__occ.A_cla
110
+
111
+ def update_force(self, p_set):
112
+ # Preserve the Force-like host API for callers outside a device solver.
113
+ self.update_force_device(p_set, host_authoritative=True)
114
+ self.__occ.sync_to_host("A", self.__A)
115
+ self.__F[:] = self.__A * self.__M
116
+ return self.__A
117
+
118
+ def getA(self):
119
+ self.__occ.sync_to_host("A", self.__A)
120
+ return self.__A
121
+
122
+ A = property(getA)
123
+
124
+ def getF(self):
125
+ self.__occ.sync_to_host("A", self.__A)
126
+ self.__F[:] = self.__A * self.__M
127
+ return self.__F
128
+
129
+ F = property(getF)
130
+
131
+ def get_ocl_context(self):
132
+ return self.__occ
133
+
134
+ ocl_context = property(get_ocl_context)