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,374 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva simone.rva {at} gmail {dot} com
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 os
10
+
11
+ import numpy as np
12
+
13
+
14
+ try:
15
+ import pyopencl as cl
16
+ import pyopencl.array as cla
17
+ except ImportError as exc:
18
+ cl = None
19
+ cla = None
20
+ _PYOPENCL_IMPORT_ERROR = exc
21
+ else:
22
+ _PYOPENCL_IMPORT_ERROR = None
23
+
24
+
25
+ def get_gl_sharing_context_properties():
26
+ """Return properties for sharing the currently-active OpenGL context.
27
+
28
+ PyOpenCL historically documented this helper at the top-level namespace,
29
+ while 2026.1.x keeps the implementation in ``pyopencl.tools``. Support
30
+ both layouts so the PyParticles compatibility layer works across them.
31
+ """
32
+ if cl is None:
33
+ raise RuntimeError("PyOpenCL is not available") from _PYOPENCL_IMPORT_ERROR
34
+
35
+ helper = getattr(cl, "get_gl_sharing_context_properties", None)
36
+ if helper is None:
37
+ from pyopencl.tools import get_gl_sharing_context_properties as helper
38
+ return list(helper())
39
+
40
+
41
+ OCLC_X = np.uint8(0b10000000)
42
+ OCLC_V = np.uint8(0b01000000)
43
+ OCLC_A = np.uint8(0b00100000)
44
+ OCLC_M = np.uint8(0b00010000)
45
+
46
+
47
+ class OpenCLcontext(object):
48
+ """Shared OpenCL context and particle buffers.
49
+
50
+ The modern implementation tracks which side contains the newest value for
51
+ X/V/A/M. It can also create a context sharing the currently-active OpenGL
52
+ context. In GL-sharing mode normal particle arrays remain ordinary OpenCL
53
+ buffers; a renderer may create :class:`pyopencl.GLBuffer` objects in the
54
+ same context and copy positions VRAM-to-VRAM without crossing PCIe.
55
+
56
+ When ``PYOPENCL_CTX`` explicitly selects a device, GL-sharing context
57
+ creation honors that same selection. PyParticles3 will not silently move
58
+ the simulation to a different OpenCL device merely because another device
59
+ supports ``cl_khr_gl_sharing``.
60
+ """
61
+
62
+ _VALID_NAMES = ("X", "V", "A", "M")
63
+
64
+ def __init__(
65
+ self,
66
+ size,
67
+ dim,
68
+ mask=(OCLC_X | OCLC_V | OCLC_A | OCLC_M),
69
+ dtype=np.float32,
70
+ gl_sharing=False,
71
+ ):
72
+ if cl is None:
73
+ raise RuntimeError("PyOpenCL is not available") from _PYOPENCL_IMPORT_ERROR
74
+
75
+ np_dtype = np.dtype(dtype)
76
+ if np_dtype != np.dtype(np.float32):
77
+ raise TypeError("PyParticles OpenCL kernels currently support only float32")
78
+
79
+ self.__dtype = np_dtype.type
80
+ self.__size = int(size)
81
+ self.__dim = int(dim)
82
+ self.__opt_arrays = {}
83
+ self.__gl_sharing = bool(gl_sharing)
84
+ self.__platform = None
85
+ self.__device = None
86
+
87
+ try:
88
+ if self.__gl_sharing:
89
+ self.__cl_context = self._create_gl_sharing_context()
90
+ else:
91
+ self.__cl_context = cl.create_some_context(interactive=False)
92
+ if self.__cl_context.devices:
93
+ self.__device = self.__cl_context.devices[0]
94
+ self.__platform = self.__device.platform
95
+ except Exception as exc:
96
+ if self.__gl_sharing:
97
+ raise RuntimeError(
98
+ "No OpenCL context could share the active OpenGL context: %s"
99
+ % exc
100
+ ) from exc
101
+ raise RuntimeError("No usable OpenCL context could be created") from exc
102
+
103
+ self.__cl_queue = cl.CommandQueue(
104
+ self.__cl_context,
105
+ properties=cl.command_queue_properties.PROFILING_ENABLE,
106
+ )
107
+
108
+ self.__V_cla = self._new_array((self.__size, self.__dim)) if mask & OCLC_V else None
109
+ self.__X_cla = self._new_array((self.__size, self.__dim)) if mask & OCLC_X else None
110
+ self.__A_cla = self._new_array((self.__size, self.__dim)) if mask & OCLC_A else None
111
+ self.__M_cla = self._new_array((self.__size, 1)) if mask & OCLC_M else None
112
+
113
+ self.__buffer_state = {
114
+ "X": "host" if self.__X_cla is not None else None,
115
+ "V": "host" if self.__V_cla is not None else None,
116
+ "A": "host" if self.__A_cla is not None else None,
117
+ "M": "host" if self.__M_cla is not None else None,
118
+ }
119
+ self.reset_transfer_stats()
120
+
121
+ def _gl_sharing_candidate_devices(self):
122
+ """Return candidate devices without overriding an explicit selection."""
123
+ selector = os.environ.get("PYOPENCL_CTX", "").strip()
124
+ if selector:
125
+ selected_context = cl.create_some_context(interactive=False)
126
+ devices = list(selected_context.devices)
127
+ if not devices:
128
+ raise RuntimeError(
129
+ "PYOPENCL_CTX=%r did not select an OpenCL device" % selector
130
+ )
131
+ return devices, selector
132
+
133
+ devices = []
134
+ for platform in cl.get_platforms():
135
+ devices.extend(platform.get_devices())
136
+ return devices, None
137
+
138
+ def _create_gl_sharing_context(self):
139
+ if not hasattr(cl, "have_gl") or not cl.have_gl():
140
+ raise RuntimeError("PyOpenCL was built without OpenGL interoperability")
141
+
142
+ sharing = get_gl_sharing_context_properties()
143
+ candidates, selector = self._gl_sharing_candidate_devices()
144
+ last_error = None
145
+
146
+ for device in candidates:
147
+ platform = device.platform
148
+ if "cl_khr_gl_sharing" not in device.extensions.split():
149
+ if selector is not None:
150
+ raise RuntimeError(
151
+ "PYOPENCL_CTX=%s selected %s, which does not advertise "
152
+ "cl_khr_gl_sharing"
153
+ % (selector, device.name.strip())
154
+ )
155
+ continue
156
+
157
+ properties = [
158
+ (cl.context_properties.PLATFORM, platform),
159
+ *sharing,
160
+ ]
161
+ try:
162
+ context = cl.Context(devices=[device], properties=properties)
163
+ except Exception as exc:
164
+ last_error = exc
165
+ if selector is not None:
166
+ raise RuntimeError(
167
+ "PYOPENCL_CTX=%s selected %s, but that device could not "
168
+ "share the active OpenGL context: %s"
169
+ % (selector, device.name.strip(), exc)
170
+ ) from exc
171
+ continue
172
+
173
+ self.__platform = platform
174
+ self.__device = device
175
+ return context
176
+
177
+ if last_error is not None:
178
+ raise RuntimeError("No CL/GL sharing device accepted the active GL context") from last_error
179
+ raise RuntimeError("No OpenCL device advertises cl_khr_gl_sharing")
180
+
181
+ def _new_array(self, shape, dtype=None):
182
+ if dtype is None:
183
+ dtype = self.__dtype
184
+ return cla.Array(self.__cl_queue, shape, np.dtype(dtype).type)
185
+
186
+ def _array_for_name(self, name):
187
+ name = str(name).upper()
188
+ arrays = {
189
+ "X": self.__X_cla,
190
+ "V": self.__V_cla,
191
+ "A": self.__A_cla,
192
+ "M": self.__M_cla,
193
+ }
194
+ if name not in arrays:
195
+ raise KeyError("Unknown OpenCL particle buffer %r" % name)
196
+ array = arrays[name]
197
+ if array is None:
198
+ raise ValueError("OpenCL particle buffer %s was not allocated" % name)
199
+ return name, array
200
+
201
+ def _record_transfer(self, direction, name, nbytes):
202
+ self.__transfer_stats[direction + "_calls"] += 1
203
+ self.__transfer_stats[direction + "_bytes"] += int(nbytes)
204
+ self.__transfer_stats["by_buffer"][name][direction + "_calls"] += 1
205
+ self.__transfer_stats["by_buffer"][name][direction + "_bytes"] += int(nbytes)
206
+
207
+ def reset_transfer_stats(self):
208
+ self.__transfer_stats = {
209
+ "h2d_calls": 0,
210
+ "d2h_calls": 0,
211
+ "h2d_bytes": 0,
212
+ "d2h_bytes": 0,
213
+ "by_buffer": {
214
+ name: {
215
+ "h2d_calls": 0,
216
+ "d2h_calls": 0,
217
+ "h2d_bytes": 0,
218
+ "d2h_bytes": 0,
219
+ }
220
+ for name in self._VALID_NAMES
221
+ },
222
+ }
223
+
224
+ def get_transfer_stats(self):
225
+ return {
226
+ "h2d_calls": self.__transfer_stats["h2d_calls"],
227
+ "d2h_calls": self.__transfer_stats["d2h_calls"],
228
+ "h2d_bytes": self.__transfer_stats["h2d_bytes"],
229
+ "d2h_bytes": self.__transfer_stats["d2h_bytes"],
230
+ "by_buffer": {
231
+ name: dict(values)
232
+ for name, values in self.__transfer_stats["by_buffer"].items()
233
+ },
234
+ }
235
+
236
+ transfer_stats = property(get_transfer_stats)
237
+
238
+ def get_buffer_state(self, name):
239
+ name, _ = self._array_for_name(name)
240
+ return self.__buffer_state[name]
241
+
242
+ def mark_host_modified(self, name):
243
+ name, _ = self._array_for_name(name)
244
+ self.__buffer_state[name] = "host"
245
+
246
+ def mark_device_modified(self, name):
247
+ name, _ = self._array_for_name(name)
248
+ self.__buffer_state[name] = "device"
249
+
250
+ def mark_synced(self, name):
251
+ name, _ = self._array_for_name(name)
252
+ self.__buffer_state[name] = "sync"
253
+
254
+ def sync_to_device(self, name, host_array):
255
+ """Upload *host_array* only when the host contains the newest value."""
256
+ name, device_array = self._array_for_name(name)
257
+ if self.__buffer_state[name] == "host":
258
+ host = np.ascontiguousarray(host_array, dtype=device_array.dtype)
259
+ device_array.set(host, queue=self.__cl_queue)
260
+ self._record_transfer("h2d", name, host.nbytes)
261
+ self.__buffer_state[name] = "sync"
262
+ return device_array
263
+
264
+ def sync_to_host(self, name, host_array):
265
+ """Download a device buffer only when the device contains newer data."""
266
+ name, device_array = self._array_for_name(name)
267
+ if self.__buffer_state[name] == "device":
268
+ device_array.get(queue=self.__cl_queue, ary=host_array)
269
+ self._record_transfer("d2h", name, np.asarray(host_array).nbytes)
270
+ self.__buffer_state[name] = "sync"
271
+ return host_array
272
+
273
+ def set_from_host(self, name, host_array):
274
+ """Declare the host authoritative and synchronize it to the device."""
275
+ self.mark_host_modified(name)
276
+ return self.sync_to_device(name, host_array)
277
+
278
+ def create_gl_buffer(self, gl_buffer_id, flags=None):
279
+ """Wrap an OpenGL buffer object in this GL-sharing OpenCL context."""
280
+ if not self.__gl_sharing:
281
+ raise RuntimeError("This OpenCL context was not created for GL sharing")
282
+ if flags is None:
283
+ flags = cl.mem_flags.READ_WRITE
284
+ return cl.GLBuffer(self.__cl_context, flags, int(gl_buffer_id))
285
+
286
+ def acquire_gl_objects(self, objects, wait_for=None):
287
+ if not self.__gl_sharing:
288
+ raise RuntimeError("This OpenCL context was not created for GL sharing")
289
+ return cl.enqueue_acquire_gl_objects(
290
+ self.__cl_queue, list(objects), wait_for=wait_for
291
+ )
292
+
293
+ def release_gl_objects(self, objects, wait_for=None):
294
+ if not self.__gl_sharing:
295
+ raise RuntimeError("This OpenCL context was not created for GL sharing")
296
+ return cl.enqueue_release_gl_objects(
297
+ self.__cl_queue, list(objects), wait_for=wait_for
298
+ )
299
+
300
+ def add_array_by_name(self, key, size=None, dim=None, dtype=None):
301
+ if dim is None:
302
+ dim = self.__dim
303
+ if size is None:
304
+ size = self.__size
305
+ if dtype is None:
306
+ dtype = self.dtype
307
+
308
+ self.__opt_arrays[key] = self._new_array(
309
+ (int(size), int(dim)),
310
+ dtype=np.dtype(dtype).type,
311
+ )
312
+
313
+ def get_by_name(self, key):
314
+ return self.__opt_arrays[key]
315
+
316
+ def get_dtype(self):
317
+ return self.__dtype
318
+
319
+ dtype = property(get_dtype, doc="return the dtype of the context")
320
+
321
+ def get_size(self):
322
+ return self.__size
323
+
324
+ size = property(get_size)
325
+
326
+ def get_dim(self):
327
+ return self.__dim
328
+
329
+ dim = property(get_dim)
330
+
331
+ def get_CL_context(self):
332
+ return self.__cl_context
333
+
334
+ CL_context = property(get_CL_context, doc="return the opencl context")
335
+
336
+ def get_CL_queue(self):
337
+ return self.__cl_queue
338
+
339
+ CL_queue = property(get_CL_queue, doc="return the command queue")
340
+
341
+ def get_X_cla(self):
342
+ return self.__X_cla
343
+
344
+ X_cla = property(get_X_cla, doc="return the positions array")
345
+
346
+ def get_A_cla(self):
347
+ return self.__A_cla
348
+
349
+ A_cla = property(get_A_cla, doc="return the acceleration array")
350
+
351
+ def get_V_cla(self):
352
+ return self.__V_cla
353
+
354
+ V_cla = property(get_V_cla, doc="return the velocity array")
355
+
356
+ def get_M_cla(self):
357
+ return self.__M_cla
358
+
359
+ M_cla = property(get_M_cla, doc="return the masses array")
360
+
361
+ def get_gl_sharing(self):
362
+ return self.__gl_sharing
363
+
364
+ gl_sharing = property(get_gl_sharing)
365
+
366
+ def get_device(self):
367
+ return self.__device
368
+
369
+ device = property(get_device)
370
+
371
+ def get_platform(self):
372
+ return self.__platform
373
+
374
+ platform = property(get_platform)