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.
- pyparticles/__init__.py +26 -0
- pyparticles/__main__.py +7 -0
- pyparticles/animation/__init__.py +21 -0
- pyparticles/animation/animated_cli.py +53 -0
- pyparticles/animation/animated_ogl.py +691 -0
- pyparticles/animation/animated_ogl_compat.py +243 -0
- pyparticles/animation/animated_scatter.py +94 -0
- pyparticles/animation/animation.py +251 -0
- pyparticles/animation/test_animation.py +310 -0
- pyparticles/demo/__init__.py +33 -0
- pyparticles/demo/bubble.py +106 -0
- pyparticles/demo/electromagnetic_demo.py +128 -0
- pyparticles/demo/electrostatic_demo.py +126 -0
- pyparticles/demo/fountain.py +478 -0
- pyparticles/demo/gas_lennard_jones.py +87 -0
- pyparticles/demo/gravity_clusters.py +73 -0
- pyparticles/demo/solar_system.py +222 -0
- pyparticles/demo/springs.py +141 -0
- pyparticles/demo/springs_constr.py +135 -0
- pyparticles/demo/test.py +34 -0
- pyparticles/forces/__init__.py +21 -0
- pyparticles/forces/const_force.py +140 -0
- pyparticles/forces/damping.py +174 -0
- pyparticles/forces/drag.py +167 -0
- pyparticles/forces/electromagnetic.py +105 -0
- pyparticles/forces/electromagnetic_field.py +95 -0
- pyparticles/forces/electrostatic.py +72 -0
- pyparticles/forces/force.py +75 -0
- pyparticles/forces/force_constrained.py +32 -0
- pyparticles/forces/fused_const_drag.py +445 -0
- pyparticles/forces/gravity.py +345 -0
- pyparticles/forces/lennard_jones.py +74 -0
- pyparticles/forces/linear_spring.py +86 -0
- pyparticles/forces/linear_spring_constrained.py +72 -0
- pyparticles/forces/multiple_force.py +134 -0
- pyparticles/forces/pseudo_bubble.py +226 -0
- pyparticles/forces/van_der_waals_force.py +60 -0
- pyparticles/forces/vector_field_force.py +52 -0
- pyparticles/geometry/__init__.py +21 -0
- pyparticles/geometry/dist.py +24 -0
- pyparticles/geometry/intersection.py +62 -0
- pyparticles/geometry/transformations.py +387 -0
- pyparticles/main/__init__.py +21 -0
- pyparticles/main/main.py +466 -0
- pyparticles/measures/__init__.py +21 -0
- pyparticles/measures/elastic_potential_energy.py +67 -0
- pyparticles/measures/gravitational_potential_energy.py +69 -0
- pyparticles/measures/kinetic_energy.py +68 -0
- pyparticles/measures/mass.py +59 -0
- pyparticles/measures/measure.py +156 -0
- pyparticles/measures/momentum.py +144 -0
- pyparticles/measures/total_energy.py +68 -0
- pyparticles/ode/__init__.py +21 -0
- pyparticles/ode/euler_solver.py +214 -0
- pyparticles/ode/euler_solver_constrained.py +49 -0
- pyparticles/ode/leapfrog_solver.py +37 -0
- pyparticles/ode/leapfrog_solver_constrained.py +53 -0
- pyparticles/ode/midpoint_solver.py +43 -0
- pyparticles/ode/midpoint_solver_constrained.py +60 -0
- pyparticles/ode/ode_solver.py +134 -0
- pyparticles/ode/ode_solver_constrained.py +43 -0
- pyparticles/ode/runge_kutta_solver.py +79 -0
- pyparticles/ode/runge_kutta_solver_constrained.py +98 -0
- pyparticles/ode/sim_time.py +56 -0
- pyparticles/ode/stormer_verlet_solver.py +50 -0
- pyparticles/ode/stormer_verlet_solver_constrained.py +73 -0
- pyparticles/ogl/__init__.py +21 -0
- pyparticles/ogl/axis_ogl.py +210 -0
- pyparticles/ogl/draw_particles_ogl.py +313 -0
- pyparticles/ogl/draw_particles_ogl_compat.py +509 -0
- pyparticles/ogl/draw_vector_field.py +221 -0
- pyparticles/ogl/opencl_gl_vbo.py +485 -0
- pyparticles/ogl/trackball.py +131 -0
- pyparticles/ogl/translate_scene.py +87 -0
- pyparticles/pset/__init__.py +21 -0
- pyparticles/pset/boundary.py +69 -0
- pyparticles/pset/cluster.py +28 -0
- pyparticles/pset/constrained_force_interactions.py +63 -0
- pyparticles/pset/constrained_x.py +158 -0
- pyparticles/pset/constraint.py +42 -0
- pyparticles/pset/default_boundary.py +43 -0
- pyparticles/pset/file_cluster.py +131 -0
- pyparticles/pset/logger.py +152 -0
- pyparticles/pset/octree.py +451 -0
- pyparticles/pset/opencl_context.py +374 -0
- pyparticles/pset/particles_set.py +499 -0
- pyparticles/pset/periodic_boundary.py +36 -0
- pyparticles/pset/rand_cluster.py +188 -0
- pyparticles/pset/rebound_boundary.py +68 -0
- pyparticles/utils/__init__.py +21 -0
- pyparticles/utils/parse_args.py +72 -0
- pyparticles/utils/problem_config.py +608 -0
- pyparticles/utils/pypart_global.py +122 -0
- pyparticles/utils/time_formatter.py +50 -0
- pyparticles3-0.4.0.dist-info/METADATA +395 -0
- pyparticles3-0.4.0.dist-info/RECORD +100 -0
- pyparticles3-0.4.0.dist-info/WHEEL +5 -0
- pyparticles3-0.4.0.dist-info/entry_points.txt +3 -0
- pyparticles3-0.4.0.dist-info/licenses/LICENSE-gpl-3.0.txt +674 -0
- 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)
|