PyParticles3 0.4.0rc1__tar.gz → 0.4.0rc2__tar.gz

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 (153) hide show
  1. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/CHANGELOG.md +22 -1
  2. {pyparticles3-0.4.0rc1/PyParticles3.egg-info → pyparticles3-0.4.0rc2}/PKG-INFO +63 -5
  3. pyparticles3-0.4.0rc2/PUBLISHING.md +179 -0
  4. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2/PyParticles3.egg-info}/PKG-INFO +63 -5
  5. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/PyParticles3.egg-info/SOURCES.txt +1 -0
  6. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/README.md +62 -4
  7. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/opencl_context.py +58 -19
  8. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/utils/pypart_global.py +37 -2
  9. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyproject.toml +1 -1
  10. pyparticles3-0.4.0rc2/tests/test_opencl_device_selection.py +82 -0
  11. pyparticles3-0.4.0rc1/PUBLISHING.md +0 -100
  12. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/LICENSE-gpl-3.0.txt +0 -0
  13. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/MANIFEST.in +0 -0
  14. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/PyParticles3.egg-info/dependency_links.txt +0 -0
  15. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/PyParticles3.egg-info/entry_points.txt +0 -0
  16. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/PyParticles3.egg-info/requires.txt +0 -0
  17. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/PyParticles3.egg-info/top_level.txt +0 -0
  18. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/README.rst +0 -0
  19. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Config File.rst +0 -0
  20. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Logo/pyparticles_logo.svg +0 -0
  21. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Makefile +0 -0
  22. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Packages Descriptions.rst +0 -0
  23. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Programming Example II.rst +0 -0
  24. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Programming Example.rst +0 -0
  25. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/Testing.rst +0 -0
  26. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/conf.py +0 -0
  27. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/img/dam_an.jpg +0 -0
  28. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/img/dhar.png +0 -0
  29. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/img/fall.png +0 -0
  30. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/img/fall_an.jpg +0 -0
  31. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/img/har.png +0 -0
  32. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/img/har_an.jpg +0 -0
  33. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/index.rst +0 -0
  34. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/intro.rst +0 -0
  35. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/logo.png +0 -0
  36. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/logo_pyp.png +0 -0
  37. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/make.bat +0 -0
  38. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/modules.rst +0 -0
  39. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.animation.rst +0 -0
  40. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.demo.rst +0 -0
  41. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.forces.rst +0 -0
  42. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.geometry.rst +0 -0
  43. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.main.rst +0 -0
  44. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.measures.rst +0 -0
  45. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.ode.rst +0 -0
  46. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.ogl.rst +0 -0
  47. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.pset.rst +0 -0
  48. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.rst +0 -0
  49. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/doc/pyparticles.utils.rst +0 -0
  50. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/example/cluster_demo.cfg +0 -0
  51. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/example/solar_sys.csv +0 -0
  52. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/example/solar_system.csv +0 -0
  53. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/man/install.sh +0 -0
  54. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/man/pyparticles.1 +0 -0
  55. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/__init__.py +0 -0
  56. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/__main__.py +0 -0
  57. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/__init__.py +0 -0
  58. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/animated_cli.py +0 -0
  59. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/animated_ogl.py +0 -0
  60. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/animated_ogl_compat.py +0 -0
  61. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/animated_scatter.py +0 -0
  62. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/animation.py +0 -0
  63. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/animation/test_animation.py +0 -0
  64. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/__init__.py +0 -0
  65. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/bubble.py +0 -0
  66. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/electromagnetic_demo.py +0 -0
  67. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/electrostatic_demo.py +0 -0
  68. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/fountain.py +0 -0
  69. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/gas_lennard_jones.py +0 -0
  70. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/gravity_clusters.py +0 -0
  71. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/solar_system.py +0 -0
  72. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/springs.py +0 -0
  73. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/springs_constr.py +0 -0
  74. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/demo/test.py +0 -0
  75. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/__init__.py +0 -0
  76. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/const_force.py +0 -0
  77. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/damping.py +0 -0
  78. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/drag.py +0 -0
  79. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/electromagnetic.py +0 -0
  80. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/electromagnetic_field.py +0 -0
  81. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/electrostatic.py +0 -0
  82. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/force.py +0 -0
  83. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/force_constrained.py +0 -0
  84. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/fused_const_drag.py +0 -0
  85. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/gravity.py +0 -0
  86. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/lennard_jones.py +0 -0
  87. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/linear_spring.py +0 -0
  88. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/linear_spring_constrained.py +0 -0
  89. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/multiple_force.py +0 -0
  90. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/pseudo_bubble.py +0 -0
  91. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/van_der_waals_force.py +0 -0
  92. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/forces/vector_field_force.py +0 -0
  93. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/geometry/__init__.py +0 -0
  94. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/geometry/dist.py +0 -0
  95. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/geometry/intersection.py +0 -0
  96. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/geometry/transformations.py +0 -0
  97. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/main/__init__.py +0 -0
  98. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/main/main.py +0 -0
  99. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/__init__.py +0 -0
  100. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/elastic_potential_energy.py +0 -0
  101. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/gravitational_potential_energy.py +0 -0
  102. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/kinetic_energy.py +0 -0
  103. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/mass.py +0 -0
  104. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/measure.py +0 -0
  105. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/momentum.py +0 -0
  106. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/measures/total_energy.py +0 -0
  107. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/__init__.py +0 -0
  108. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/euler_solver.py +0 -0
  109. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/euler_solver_constrained.py +0 -0
  110. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/leapfrog_solver.py +0 -0
  111. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/leapfrog_solver_constrained.py +0 -0
  112. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/midpoint_solver.py +0 -0
  113. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/midpoint_solver_constrained.py +0 -0
  114. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/ode_solver.py +0 -0
  115. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/ode_solver_constrained.py +0 -0
  116. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/runge_kutta_solver.py +0 -0
  117. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/runge_kutta_solver_constrained.py +0 -0
  118. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/sim_time.py +0 -0
  119. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/stormer_verlet_solver.py +0 -0
  120. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ode/stormer_verlet_solver_constrained.py +0 -0
  121. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/__init__.py +0 -0
  122. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/axis_ogl.py +0 -0
  123. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/draw_particles_ogl.py +0 -0
  124. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/draw_particles_ogl_compat.py +0 -0
  125. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/draw_vector_field.py +0 -0
  126. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/opencl_gl_vbo.py +0 -0
  127. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/trackball.py +0 -0
  128. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/ogl/translate_scene.py +0 -0
  129. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/__init__.py +0 -0
  130. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/boundary.py +0 -0
  131. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/cluster.py +0 -0
  132. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/constrained_force_interactions.py +0 -0
  133. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/constrained_x.py +0 -0
  134. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/constraint.py +0 -0
  135. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/default_boundary.py +0 -0
  136. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/file_cluster.py +0 -0
  137. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/logger.py +0 -0
  138. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/octree.py +0 -0
  139. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/particles_set.py +0 -0
  140. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/periodic_boundary.py +0 -0
  141. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/rand_cluster.py +0 -0
  142. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/pset/rebound_boundary.py +0 -0
  143. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/utils/__init__.py +0 -0
  144. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/utils/parse_args.py +0 -0
  145. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/utils/problem_config.py +0 -0
  146. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/pyparticles/utils/time_formatter.py +0 -0
  147. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/setup.cfg +0 -0
  148. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/setup.py +0 -0
  149. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/tests/test_demo_compat.py +0 -0
  150. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/tests/test_opencl_compute_only.py +0 -0
  151. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/tests/test_opencl_regression.py +0 -0
  152. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/tests/test_opencl_residency.py +0 -0
  153. {pyparticles3-0.4.0rc1 → pyparticles3-0.4.0rc2}/tests/test_particles_set_compat.py +0 -0
@@ -2,9 +2,30 @@
2
2
 
3
3
  All notable changes to the modernized distribution are documented here.
4
4
 
5
+ ## 0.4.0rc2 - release candidate
6
+
7
+ Follow-up release candidate based on installation and multi-ICD validation of `0.4.0rc1`.
8
+
9
+ ### OpenCL/OpenGL interoperability
10
+
11
+ - document that `PyParticles3[opencl]` guarantees OpenCL compute support but does not guarantee a PyOpenCL build with OpenGL interoperability;
12
+ - add an explicit runtime warning when `pyopencl.have_gl()` is `False`;
13
+ - document rebuilding PyOpenCL from source with `PYOPENCL_ENABLE_GL=ON` and verifying `pyopencl.have_gl() is True`;
14
+ - preserve an explicit `PYOPENCL_CTX` device selection when creating a CL/GL sharing context;
15
+ - do not silently migrate an explicitly selected Intel CPU OpenCL workload to an NVIDIA GPU merely to obtain `cl_khr_gl_sharing`;
16
+ - improve CL/GL failure messages so host-synchronized fallback identifies why sharing is unavailable.
17
+
18
+ ### Validation
19
+
20
+ - validated OpenCL gravity kernels on NVIDIA GTX 1060 and Intel Xeon E5-2697 v2 OpenCL implementations;
21
+ - validated CPU/OpenCL numerical agreement over complete Euler integration runs;
22
+ - validated persistent X/V device residency and transfer accounting;
23
+ - validated clean PyPI installation and all published CLI entry points;
24
+ - validated the high-performance fountain CL/GL path with a GL-enabled PyOpenCL build.
25
+
5
26
  ## 0.4.0rc1 - release candidate
6
27
 
7
- First release candidate planned under the **PyParticles3** distribution name.
28
+ First release candidate under the **PyParticles3** distribution name.
8
29
 
9
30
  ### Packaging
10
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyParticles3
3
- Version: 0.4.0rc1
3
+ Version: 0.4.0rc2
4
4
  Summary: Modern educational particle simulation toolbox with OpenCL/OpenGL acceleration
5
5
  Author: Simone Riva
6
6
  Maintainer: jamaj69
@@ -64,15 +64,13 @@ The goal is to preserve the original project's unusually clear, educational arch
64
64
 
65
65
  ## Installation
66
66
 
67
- The first PyPI release is planned as `PyParticles3 0.4.0rc1`.
68
-
69
- Once published:
67
+ The current release candidate is `PyParticles3 0.4.0rc2`.
70
68
 
71
69
  ```bash
72
70
  python -m pip install PyParticles3
73
71
  ```
74
72
 
75
- For OpenCL support:
73
+ For OpenCL compute support:
76
74
 
77
75
  ```bash
78
76
  python -m pip install 'PyParticles3[opencl]'
@@ -80,6 +78,66 @@ python -m pip install 'PyParticles3[opencl]'
80
78
 
81
79
  The OpenCL runtime/ICD and an OpenGL/FreeGLUT implementation are system-level dependencies and are not installed by pip.
82
80
 
81
+ ### OpenCL/OpenGL interoperability requires a GL-enabled PyOpenCL build
82
+
83
+ `PyParticles3[opencl]` installs PyOpenCL and is sufficient for OpenCL **compute**. It does **not** guarantee that the installed PyOpenCL binary was compiled with OpenGL interoperability enabled.
84
+
85
+ Check the installed build with:
86
+
87
+ ```bash
88
+ python - <<'PY'
89
+ import pyopencl as cl
90
+ print("PyOpenCL:", cl.VERSION_TEXT)
91
+ print("have_gl :", cl.have_gl())
92
+ PY
93
+ ```
94
+
95
+ For the fast OpenCL/OpenGL shared-buffer path used by the `fountain` demo, the required result is:
96
+
97
+ ```text
98
+ have_gl : True
99
+ ```
100
+
101
+ If a PyPI wheel reports `have_gl : False`, OpenCL compute still works, but PyParticles3 cannot create `GLBuffer` objects and high-particle-count rendering must fall back to host synchronization. That fallback can be dramatically slower.
102
+
103
+ PyOpenCL's source-build documentation requires `PYOPENCL_ENABLE_GL=ON` to enable GL interoperability. A typical pip rebuild is:
104
+
105
+ ```bash
106
+ python -m pip uninstall -y pyopencl
107
+
108
+ PYOPENCL_ENABLE_GL=ON \
109
+ python -m pip install \
110
+ --no-binary=pyopencl \
111
+ --no-cache-dir \
112
+ -v \
113
+ 'pyopencl>=2026.1'
114
+ ```
115
+
116
+ Then verify again:
117
+
118
+ ```bash
119
+ python - <<'PY'
120
+ import pyopencl as cl
121
+ assert cl.have_gl(), "PyOpenCL was built without OpenGL interoperability"
122
+ print("PyOpenCL GL interoperability: enabled")
123
+ PY
124
+ ```
125
+
126
+ Building PyOpenCL from source also requires a C++17 compiler, Python/build dependencies, OpenCL headers and loader libraries, and OpenGL development headers appropriate to the operating system.
127
+
128
+ ## Selecting an OpenCL device
129
+
130
+ PyParticles3 follows PyOpenCL's `PYOPENCL_CTX` selection for compute contexts. For example, on a system where platform 0 is an NVIDIA GPU and platform 1 is an Intel CPU:
131
+
132
+ ```bash
133
+ PYOPENCL_CTX=0:0 pyparticles3 --demo fountain
134
+ PYOPENCL_CTX=1:0 pyparticles3 --demo fountain
135
+ ```
136
+
137
+ As of `0.4.0rc2`, an explicit `PYOPENCL_CTX` selection is also honored when PyParticles3 attempts to create a CL/GL sharing context. PyParticles3 will not silently move the simulation to another OpenCL device just because that other device supports `cl_khr_gl_sharing`.
138
+
139
+ This matters on mixed systems. An Intel Xeon CPU OpenCL device can execute the simulation kernels but may not advertise or support `cl_khr_gl_sharing` with the active NVIDIA OpenGL context. In that case PyParticles3 keeps the selected Intel compute device and falls back to host-synchronized rendering instead of silently switching compute to the NVIDIA GPU.
140
+
83
141
  ## Current import namespace
84
142
 
85
143
  The initial PyParticles3 packaging release intentionally keeps the historical Python import namespace:
@@ -0,0 +1,179 @@
1
+ # Publishing PyParticles3 to PyPI
2
+
3
+ This repository is prepared to publish the `PyParticles3` distribution through PyPI Trusted Publishing.
4
+
5
+ ## Why Trusted Publishing
6
+
7
+ The release workflow uses GitHub Actions OIDC instead of storing a long-lived PyPI API token in the repository. PyPI issues a short-lived credential to the trusted workflow at publication time.
8
+
9
+ ## PyPI publisher configuration
10
+
11
+ The PyPI project is published from this GitHub repository with these values:
12
+
13
+ ```text
14
+ PyPI project name : PyParticles3
15
+ GitHub owner : jamaj69
16
+ Repository : pyparticles
17
+ Workflow : release.yml
18
+ Environment : pypi
19
+ ```
20
+
21
+ ## GitHub environment
22
+
23
+ The release workflow uses the GitHub Actions environment:
24
+
25
+ ```text
26
+ pypi
27
+ ```
28
+
29
+ For a public release project, required reviewers on this environment are recommended so a tag cannot publish without an explicit approval step.
30
+
31
+ ## Release candidates
32
+
33
+ The first published candidate was:
34
+
35
+ ```text
36
+ PyParticles3 0.4.0rc1
37
+ ```
38
+
39
+ The current candidate is:
40
+
41
+ ```text
42
+ PyParticles3 0.4.0rc2
43
+ ```
44
+
45
+ `0.4.0rc2` adds explicit diagnostics for PyOpenCL builds without GL interoperability and ensures that an explicit `PYOPENCL_CTX` selection is not silently replaced by another device when CL/GL sharing is attempted.
46
+
47
+ After CI is green, publish the candidate with:
48
+
49
+ ```bash
50
+ git tag -a v0.4.0rc2 -m "PyParticles3 0.4.0rc2"
51
+ git push origin v0.4.0rc2
52
+ ```
53
+
54
+ `.github/workflows/release.yml` will:
55
+
56
+ 1. build the wheel and source distribution;
57
+ 2. run `twine check`;
58
+ 3. pass the artifacts to a separate publish job;
59
+ 4. request an OIDC publishing credential;
60
+ 5. upload the release to PyPI.
61
+
62
+ ## Repository references shown by PyPI
63
+
64
+ `pyproject.toml` declares:
65
+
66
+ ```text
67
+ Homepage https://github.com/jamaj69/pyparticles
68
+ Source https://github.com/jamaj69/pyparticles
69
+ Documentation https://github.com/jamaj69/pyparticles#readme
70
+ Issues https://github.com/jamaj69/pyparticles/issues
71
+ Original https://github.com/simon-r/PyParticles
72
+ ```
73
+
74
+ Because releases are uploaded through GitHub Trusted Publishing from `jamaj69/pyparticles`, PyPI can verify links belonging to that GitHub repository.
75
+
76
+ ## Release discipline
77
+
78
+ Do not reuse a version already uploaded to PyPI. If a release artifact must change, increment the version first.
79
+
80
+ Recommended sequence:
81
+
82
+ ```text
83
+ 0.4.0rc1
84
+ 0.4.0rc2
85
+ 0.4.0rc3 (only if another release candidate is needed)
86
+ 0.4.0
87
+ ```
88
+
89
+ ## Local verification before tagging
90
+
91
+ ```bash
92
+ python -m pip install -U build twine
93
+ rm -rf build dist *.egg-info
94
+ python -m build
95
+ python -m twine check dist/*
96
+ ```
97
+
98
+ Then test the wheel in a clean environment, outside the source tree:
99
+
100
+ ```bash
101
+ python -m venv /tmp/pyparticles3-release-test
102
+ source /tmp/pyparticles3-release-test/bin/activate
103
+ cd /tmp
104
+ unset PYTHONPATH
105
+ python -m pip install '/path/to/dist/PyParticles3-0.4.0rc2-py3-none-any.whl[opencl]'
106
+ python -m pip check
107
+ pyparticles3 --version
108
+ ```
109
+
110
+ ## OpenCL compute validation
111
+
112
+ List available platforms/devices and test each desired ICD explicitly. For example:
113
+
114
+ ```bash
115
+ python - <<'PY'
116
+ import pyopencl as cl
117
+ for pi, platform in enumerate(cl.get_platforms()):
118
+ for di, device in enumerate(platform.get_devices()):
119
+ print(f"{pi}:{di} {platform.name} {device.name}")
120
+ PY
121
+ ```
122
+
123
+ Then select devices with `PYOPENCL_CTX`, for example:
124
+
125
+ ```bash
126
+ PYOPENCL_CTX=0:0 pyparticles3 --demo fountain
127
+ PYOPENCL_CTX=1:0 pyparticles3 --demo fountain
128
+ ```
129
+
130
+ The selected device must remain the selected compute device. `0.4.0rc2` must not silently migrate an explicit Intel CPU selection to an NVIDIA GPU merely to obtain CL/GL sharing.
131
+
132
+ ## CL/GL release validation
133
+
134
+ The optional `opencl` extra guarantees PyOpenCL compute support only. A PyOpenCL wheel may report:
135
+
136
+ ```text
137
+ have_gl : False
138
+ ```
139
+
140
+ For CL/GL validation, verify:
141
+
142
+ ```bash
143
+ python - <<'PY'
144
+ import pyopencl as cl
145
+ print("PyOpenCL:", cl.VERSION_TEXT)
146
+ print("have_gl :", cl.have_gl())
147
+ PY
148
+ ```
149
+
150
+ The shared-buffer fountain path requires:
151
+
152
+ ```text
153
+ have_gl : True
154
+ ```
155
+
156
+ If necessary, rebuild PyOpenCL from source with GL support:
157
+
158
+ ```bash
159
+ python -m pip uninstall -y pyopencl
160
+
161
+ PYOPENCL_ENABLE_GL=ON \
162
+ python -m pip install \
163
+ --no-binary=pyopencl \
164
+ --no-cache-dir \
165
+ -v \
166
+ 'pyopencl>=2026.1'
167
+ ```
168
+
169
+ Then require the result programmatically:
170
+
171
+ ```bash
172
+ python - <<'PY'
173
+ import pyopencl as cl
174
+ assert cl.have_gl(), "PyOpenCL was built without OpenGL interoperability"
175
+ print("PyOpenCL GL interoperability: enabled")
176
+ PY
177
+ ```
178
+
179
+ A selected OpenCL device that cannot share the active OpenGL context is allowed to fall back to host-synchronized rendering, but the fallback must be explicit and the compute device must not silently change.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyParticles3
3
- Version: 0.4.0rc1
3
+ Version: 0.4.0rc2
4
4
  Summary: Modern educational particle simulation toolbox with OpenCL/OpenGL acceleration
5
5
  Author: Simone Riva
6
6
  Maintainer: jamaj69
@@ -64,15 +64,13 @@ The goal is to preserve the original project's unusually clear, educational arch
64
64
 
65
65
  ## Installation
66
66
 
67
- The first PyPI release is planned as `PyParticles3 0.4.0rc1`.
68
-
69
- Once published:
67
+ The current release candidate is `PyParticles3 0.4.0rc2`.
70
68
 
71
69
  ```bash
72
70
  python -m pip install PyParticles3
73
71
  ```
74
72
 
75
- For OpenCL support:
73
+ For OpenCL compute support:
76
74
 
77
75
  ```bash
78
76
  python -m pip install 'PyParticles3[opencl]'
@@ -80,6 +78,66 @@ python -m pip install 'PyParticles3[opencl]'
80
78
 
81
79
  The OpenCL runtime/ICD and an OpenGL/FreeGLUT implementation are system-level dependencies and are not installed by pip.
82
80
 
81
+ ### OpenCL/OpenGL interoperability requires a GL-enabled PyOpenCL build
82
+
83
+ `PyParticles3[opencl]` installs PyOpenCL and is sufficient for OpenCL **compute**. It does **not** guarantee that the installed PyOpenCL binary was compiled with OpenGL interoperability enabled.
84
+
85
+ Check the installed build with:
86
+
87
+ ```bash
88
+ python - <<'PY'
89
+ import pyopencl as cl
90
+ print("PyOpenCL:", cl.VERSION_TEXT)
91
+ print("have_gl :", cl.have_gl())
92
+ PY
93
+ ```
94
+
95
+ For the fast OpenCL/OpenGL shared-buffer path used by the `fountain` demo, the required result is:
96
+
97
+ ```text
98
+ have_gl : True
99
+ ```
100
+
101
+ If a PyPI wheel reports `have_gl : False`, OpenCL compute still works, but PyParticles3 cannot create `GLBuffer` objects and high-particle-count rendering must fall back to host synchronization. That fallback can be dramatically slower.
102
+
103
+ PyOpenCL's source-build documentation requires `PYOPENCL_ENABLE_GL=ON` to enable GL interoperability. A typical pip rebuild is:
104
+
105
+ ```bash
106
+ python -m pip uninstall -y pyopencl
107
+
108
+ PYOPENCL_ENABLE_GL=ON \
109
+ python -m pip install \
110
+ --no-binary=pyopencl \
111
+ --no-cache-dir \
112
+ -v \
113
+ 'pyopencl>=2026.1'
114
+ ```
115
+
116
+ Then verify again:
117
+
118
+ ```bash
119
+ python - <<'PY'
120
+ import pyopencl as cl
121
+ assert cl.have_gl(), "PyOpenCL was built without OpenGL interoperability"
122
+ print("PyOpenCL GL interoperability: enabled")
123
+ PY
124
+ ```
125
+
126
+ Building PyOpenCL from source also requires a C++17 compiler, Python/build dependencies, OpenCL headers and loader libraries, and OpenGL development headers appropriate to the operating system.
127
+
128
+ ## Selecting an OpenCL device
129
+
130
+ PyParticles3 follows PyOpenCL's `PYOPENCL_CTX` selection for compute contexts. For example, on a system where platform 0 is an NVIDIA GPU and platform 1 is an Intel CPU:
131
+
132
+ ```bash
133
+ PYOPENCL_CTX=0:0 pyparticles3 --demo fountain
134
+ PYOPENCL_CTX=1:0 pyparticles3 --demo fountain
135
+ ```
136
+
137
+ As of `0.4.0rc2`, an explicit `PYOPENCL_CTX` selection is also honored when PyParticles3 attempts to create a CL/GL sharing context. PyParticles3 will not silently move the simulation to another OpenCL device just because that other device supports `cl_khr_gl_sharing`.
138
+
139
+ This matters on mixed systems. An Intel Xeon CPU OpenCL device can execute the simulation kernels but may not advertise or support `cl_khr_gl_sharing` with the active NVIDIA OpenGL context. In that case PyParticles3 keeps the selected Intel compute device and falls back to host-synchronized rendering instead of silently switching compute to the NVIDIA GPU.
140
+
83
141
  ## Current import namespace
84
142
 
85
143
  The initial PyParticles3 packaging release intentionally keeps the historical Python import namespace:
@@ -144,6 +144,7 @@ pyparticles/utils/pypart_global.py
144
144
  pyparticles/utils/time_formatter.py
145
145
  tests/test_demo_compat.py
146
146
  tests/test_opencl_compute_only.py
147
+ tests/test_opencl_device_selection.py
147
148
  tests/test_opencl_regression.py
148
149
  tests/test_opencl_residency.py
149
150
  tests/test_particles_set_compat.py
@@ -23,15 +23,13 @@ The goal is to preserve the original project's unusually clear, educational arch
23
23
 
24
24
  ## Installation
25
25
 
26
- The first PyPI release is planned as `PyParticles3 0.4.0rc1`.
27
-
28
- Once published:
26
+ The current release candidate is `PyParticles3 0.4.0rc2`.
29
27
 
30
28
  ```bash
31
29
  python -m pip install PyParticles3
32
30
  ```
33
31
 
34
- For OpenCL support:
32
+ For OpenCL compute support:
35
33
 
36
34
  ```bash
37
35
  python -m pip install 'PyParticles3[opencl]'
@@ -39,6 +37,66 @@ python -m pip install 'PyParticles3[opencl]'
39
37
 
40
38
  The OpenCL runtime/ICD and an OpenGL/FreeGLUT implementation are system-level dependencies and are not installed by pip.
41
39
 
40
+ ### OpenCL/OpenGL interoperability requires a GL-enabled PyOpenCL build
41
+
42
+ `PyParticles3[opencl]` installs PyOpenCL and is sufficient for OpenCL **compute**. It does **not** guarantee that the installed PyOpenCL binary was compiled with OpenGL interoperability enabled.
43
+
44
+ Check the installed build with:
45
+
46
+ ```bash
47
+ python - <<'PY'
48
+ import pyopencl as cl
49
+ print("PyOpenCL:", cl.VERSION_TEXT)
50
+ print("have_gl :", cl.have_gl())
51
+ PY
52
+ ```
53
+
54
+ For the fast OpenCL/OpenGL shared-buffer path used by the `fountain` demo, the required result is:
55
+
56
+ ```text
57
+ have_gl : True
58
+ ```
59
+
60
+ If a PyPI wheel reports `have_gl : False`, OpenCL compute still works, but PyParticles3 cannot create `GLBuffer` objects and high-particle-count rendering must fall back to host synchronization. That fallback can be dramatically slower.
61
+
62
+ PyOpenCL's source-build documentation requires `PYOPENCL_ENABLE_GL=ON` to enable GL interoperability. A typical pip rebuild is:
63
+
64
+ ```bash
65
+ python -m pip uninstall -y pyopencl
66
+
67
+ PYOPENCL_ENABLE_GL=ON \
68
+ python -m pip install \
69
+ --no-binary=pyopencl \
70
+ --no-cache-dir \
71
+ -v \
72
+ 'pyopencl>=2026.1'
73
+ ```
74
+
75
+ Then verify again:
76
+
77
+ ```bash
78
+ python - <<'PY'
79
+ import pyopencl as cl
80
+ assert cl.have_gl(), "PyOpenCL was built without OpenGL interoperability"
81
+ print("PyOpenCL GL interoperability: enabled")
82
+ PY
83
+ ```
84
+
85
+ Building PyOpenCL from source also requires a C++17 compiler, Python/build dependencies, OpenCL headers and loader libraries, and OpenGL development headers appropriate to the operating system.
86
+
87
+ ## Selecting an OpenCL device
88
+
89
+ PyParticles3 follows PyOpenCL's `PYOPENCL_CTX` selection for compute contexts. For example, on a system where platform 0 is an NVIDIA GPU and platform 1 is an Intel CPU:
90
+
91
+ ```bash
92
+ PYOPENCL_CTX=0:0 pyparticles3 --demo fountain
93
+ PYOPENCL_CTX=1:0 pyparticles3 --demo fountain
94
+ ```
95
+
96
+ As of `0.4.0rc2`, an explicit `PYOPENCL_CTX` selection is also honored when PyParticles3 attempts to create a CL/GL sharing context. PyParticles3 will not silently move the simulation to another OpenCL device just because that other device supports `cl_khr_gl_sharing`.
97
+
98
+ This matters on mixed systems. An Intel Xeon CPU OpenCL device can execute the simulation kernels but may not advertise or support `cl_khr_gl_sharing` with the active NVIDIA OpenGL context. In that case PyParticles3 keeps the selected Intel compute device and falls back to host-synchronized rendering instead of silently switching compute to the NVIDIA GPU.
99
+
42
100
  ## Current import namespace
43
101
 
44
102
  The initial PyParticles3 packaging release intentionally keeps the historical Python import namespace:
@@ -6,6 +6,8 @@
6
6
  # the Free Software Foundation, either version 3 of the License, or
7
7
  # (at your option) any later version.
8
8
 
9
+ import os
10
+
9
11
  import numpy as np
10
12
 
11
13
 
@@ -50,6 +52,11 @@ class OpenCLcontext(object):
50
52
  context. In GL-sharing mode normal particle arrays remain ordinary OpenCL
51
53
  buffers; a renderer may create :class:`pyopencl.GLBuffer` objects in the
52
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``.
53
60
  """
54
61
 
55
62
  _VALID_NAMES = ("X", "V", "A", "M")
@@ -88,7 +95,8 @@ class OpenCLcontext(object):
88
95
  except Exception as exc:
89
96
  if self.__gl_sharing:
90
97
  raise RuntimeError(
91
- "No OpenCL context could share the active OpenGL context"
98
+ "No OpenCL context could share the active OpenGL context: %s"
99
+ % exc
92
100
  ) from exc
93
101
  raise RuntimeError("No usable OpenCL context could be created") from exc
94
102
 
@@ -110,30 +118,61 @@ class OpenCLcontext(object):
110
118
  }
111
119
  self.reset_transfer_stats()
112
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
+
113
138
  def _create_gl_sharing_context(self):
114
139
  if not hasattr(cl, "have_gl") or not cl.have_gl():
115
140
  raise RuntimeError("PyOpenCL was built without OpenGL interoperability")
116
141
 
117
142
  sharing = get_gl_sharing_context_properties()
143
+ candidates, selector = self._gl_sharing_candidate_devices()
118
144
  last_error = None
119
- for platform in cl.get_platforms():
120
- for device in platform.get_devices():
121
- if "cl_khr_gl_sharing" not in device.extensions.split():
122
- continue
123
-
124
- properties = [
125
- (cl.context_properties.PLATFORM, platform),
126
- *sharing,
127
- ]
128
- try:
129
- context = cl.Context(devices=[device], properties=properties)
130
- except Exception as exc:
131
- last_error = exc
132
- continue
133
-
134
- self.__platform = platform
135
- self.__device = device
136
- return context
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
137
176
 
138
177
  if last_error is not None:
139
178
  raise RuntimeError("No CL/GL sharing device accepted the active GL context") from last_error
@@ -22,7 +22,9 @@ import zlib
22
22
  v_major = 0
23
23
  v_minor = 4
24
24
  v_revision = 0
25
- v_prerelease = "rc1"
25
+ v_prerelease = "rc2"
26
+
27
+ _GL_INTEROP_WARNING_EMITTED = False
26
28
 
27
29
 
28
30
  def py_particle_version(r="s"):
@@ -37,6 +39,35 @@ def py_particle_version(r="s"):
37
39
  return (v_major, v_minor, v_revision)
38
40
 
39
41
 
42
+ def _warn_if_pyopencl_lacks_gl(cl):
43
+ """Emit a one-time explanation when only compute OpenCL is available."""
44
+ global _GL_INTEROP_WARNING_EMITTED
45
+
46
+ if _GL_INTEROP_WARNING_EMITTED:
47
+ return
48
+
49
+ have_gl = getattr(cl, "have_gl", None)
50
+ if have_gl is None:
51
+ return
52
+
53
+ try:
54
+ gl_enabled = bool(have_gl())
55
+ except Exception:
56
+ return
57
+
58
+ if gl_enabled:
59
+ return
60
+
61
+ _GL_INTEROP_WARNING_EMITTED = True
62
+ print("")
63
+ print("WARNING: PyOpenCL was built without OpenGL interoperability (have_gl=False).")
64
+ print("OpenCL compute remains available, but CL/GL rendering will use host")
65
+ print("synchronization and can be much slower for large particle counts.")
66
+ print("For CL/GL interoperability, rebuild PyOpenCL from source with")
67
+ print("PYOPENCL_ENABLE_GL=ON and verify that pyopencl.have_gl() is True.")
68
+ print("")
69
+
70
+
40
71
  def test_pyopencl():
41
72
  """Return True only when PyOpenCL has at least one usable device."""
42
73
  try:
@@ -45,12 +76,16 @@ def test_pyopencl():
45
76
  return False
46
77
 
47
78
  try:
48
- return any(platform.get_devices() for platform in cl.get_platforms())
79
+ usable = any(platform.get_devices() for platform in cl.get_platforms())
49
80
  except Exception:
50
81
  # Broken/missing ICDs may allow importing pyopencl while still making
51
82
  # every OpenCL operation unusable.
52
83
  return False
53
84
 
85
+ if usable:
86
+ _warn_if_pyopencl_lacks_gl(cl)
87
+ return usable
88
+
54
89
 
55
90
  def about():
56
91
  mail = zlib.decompress(
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PyParticles3"
7
- version = "0.4.0rc1"
7
+ version = "0.4.0rc2"
8
8
  description = "Modern educational particle simulation toolbox with OpenCL/OpenGL acceleration"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.11"