xcoll 0.3.6__py3-none-any.whl → 0.5.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.

Potentially problematic release.


This version of xcoll might be problematic. Click here for more details.

Files changed (350) hide show
  1. xcoll/__init__.py +13 -4
  2. xcoll/beam_elements/__init__.py +14 -6
  3. xcoll/beam_elements/absorber.py +41 -7
  4. xcoll/beam_elements/base.py +1202 -247
  5. xcoll/beam_elements/blowup.py +198 -0
  6. xcoll/beam_elements/elements_src/black_absorber.h +136 -0
  7. xcoll/beam_elements/elements_src/black_crystal.h +129 -0
  8. xcoll/beam_elements/elements_src/blowup.h +42 -0
  9. xcoll/beam_elements/elements_src/emittance_monitor.h +109 -0
  10. xcoll/beam_elements/{collimators_src → elements_src}/everest_block.h +59 -30
  11. xcoll/beam_elements/elements_src/everest_collimator.h +237 -0
  12. xcoll/beam_elements/elements_src/everest_crystal.h +280 -0
  13. xcoll/beam_elements/everest.py +65 -119
  14. xcoll/beam_elements/monitor.py +428 -0
  15. xcoll/colldb.py +276 -747
  16. xcoll/general.py +5 -5
  17. xcoll/headers/checks.h +1 -1
  18. xcoll/headers/particle_states.h +2 -2
  19. xcoll/initial_distribution.py +207 -0
  20. xcoll/install.py +179 -0
  21. xcoll/interaction_record/__init__.py +1 -0
  22. xcoll/interaction_record/interaction_record.py +298 -0
  23. xcoll/interaction_record/interaction_record_src/interaction_record.h +98 -0
  24. xcoll/{impacts → interaction_record}/interaction_types.py +11 -4
  25. xcoll/line_tools.py +82 -0
  26. xcoll/lossmap.py +219 -0
  27. xcoll/manager.py +2 -937
  28. xcoll/rf_sweep.py +1 -1
  29. xcoll/scattering_routines/everest/amorphous.h +232 -0
  30. xcoll/scattering_routines/everest/channeling.h +240 -0
  31. xcoll/scattering_routines/everest/crystal_parameters.h +137 -0
  32. xcoll/scattering_routines/everest/everest.h +11 -30
  33. xcoll/scattering_routines/everest/everest.py +13 -10
  34. xcoll/scattering_routines/everest/jaw.h +28 -197
  35. xcoll/scattering_routines/everest/materials.py +37 -15
  36. xcoll/scattering_routines/everest/multiple_coulomb_scattering.h +31 -10
  37. xcoll/scattering_routines/everest/nuclear_interaction.h +86 -0
  38. xcoll/scattering_routines/everest/properties.h +6 -1
  39. xcoll/scattering_routines/fluka/flukaio/lib/libFlukaIO64.a +0 -0
  40. xcoll/scattering_routines/geant4/collimasim/.git +1 -0
  41. xcoll/scattering_routines/geant4/collimasim/.gitignore +12 -0
  42. xcoll/scattering_routines/geant4/collimasim/.gitmodules +3 -0
  43. xcoll/scattering_routines/geant4/collimasim/CMakeLists.txt +26 -0
  44. xcoll/scattering_routines/geant4/collimasim/README.md +21 -0
  45. xcoll/scattering_routines/geant4/collimasim/docs/Makefile +20 -0
  46. xcoll/scattering_routines/geant4/collimasim/docs/make.bat +35 -0
  47. xcoll/scattering_routines/geant4/collimasim/docs/source/collimasim.rst +10 -0
  48. xcoll/scattering_routines/geant4/collimasim/docs/source/conf.py +59 -0
  49. xcoll/scattering_routines/geant4/collimasim/docs/source/index.rst +26 -0
  50. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.appveyor.yml +37 -0
  51. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.clang-format +19 -0
  52. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.clang-tidy +65 -0
  53. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.cmake-format.yaml +73 -0
  54. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.git +1 -0
  55. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/CODEOWNERS +9 -0
  56. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/CONTRIBUTING.md +386 -0
  57. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml +45 -0
  58. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/ISSUE_TEMPLATE/config.yml +8 -0
  59. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/dependabot.yml +16 -0
  60. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/labeler.yml +8 -0
  61. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/labeler_merged.yml +3 -0
  62. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/pull_request_template.md +19 -0
  63. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/workflows/ci.yml +969 -0
  64. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/workflows/configure.yml +84 -0
  65. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/workflows/format.yml +48 -0
  66. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/workflows/labeler.yml +16 -0
  67. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.github/workflows/pip.yml +103 -0
  68. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.gitignore +45 -0
  69. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.pre-commit-config.yaml +151 -0
  70. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/.readthedocs.yml +3 -0
  71. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/CMakeLists.txt +297 -0
  72. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/LICENSE +29 -0
  73. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/MANIFEST.in +6 -0
  74. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/README.rst +180 -0
  75. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/Doxyfile +23 -0
  76. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/Makefile +192 -0
  77. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/_static/theme_overrides.css +11 -0
  78. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/chrono.rst +81 -0
  79. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/custom.rst +93 -0
  80. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/eigen.rst +310 -0
  81. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/functional.rst +109 -0
  82. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/index.rst +43 -0
  83. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/overview.rst +171 -0
  84. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/stl.rst +251 -0
  85. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/cast/strings.rst +305 -0
  86. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/classes.rst +1297 -0
  87. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/embedding.rst +262 -0
  88. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/exceptions.rst +396 -0
  89. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/functions.rst +568 -0
  90. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/misc.rst +337 -0
  91. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/pycpp/index.rst +13 -0
  92. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/pycpp/numpy.rst +463 -0
  93. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/pycpp/object.rst +286 -0
  94. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/pycpp/utilities.rst +155 -0
  95. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/advanced/smart_ptrs.rst +174 -0
  96. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/basics.rst +308 -0
  97. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/benchmark.py +91 -0
  98. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/benchmark.rst +95 -0
  99. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/changelog.rst +2050 -0
  100. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/classes.rst +542 -0
  101. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/cmake/index.rst +8 -0
  102. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/compiling.rst +648 -0
  103. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/conf.py +381 -0
  104. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/faq.rst +343 -0
  105. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/index.rst +48 -0
  106. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/installing.rst +105 -0
  107. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/limitations.rst +72 -0
  108. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/pybind11-logo.png +0 -0
  109. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/pybind11_vs_boost_python1.png +0 -0
  110. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/pybind11_vs_boost_python1.svg +427 -0
  111. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/pybind11_vs_boost_python2.png +0 -0
  112. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/pybind11_vs_boost_python2.svg +427 -0
  113. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/reference.rst +130 -0
  114. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/release.rst +96 -0
  115. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/requirements.txt +8 -0
  116. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/docs/upgrade.rst +548 -0
  117. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/attr.h +605 -0
  118. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/buffer_info.h +144 -0
  119. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/cast.h +1432 -0
  120. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/chrono.h +213 -0
  121. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/common.h +2 -0
  122. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/complex.h +65 -0
  123. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/class.h +709 -0
  124. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/common.h +1021 -0
  125. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/descr.h +104 -0
  126. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/init.h +346 -0
  127. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/internals.h +467 -0
  128. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/type_caster_base.h +978 -0
  129. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/detail/typeid.h +55 -0
  130. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/eigen.h +606 -0
  131. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/embed.h +284 -0
  132. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/eval.h +163 -0
  133. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/functional.h +121 -0
  134. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/gil.h +193 -0
  135. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/iostream.h +275 -0
  136. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/numpy.h +1741 -0
  137. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/operators.h +163 -0
  138. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/options.h +65 -0
  139. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/pybind11.h +2497 -0
  140. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/pytypes.h +1879 -0
  141. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/stl/filesystem.h +103 -0
  142. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/stl.h +375 -0
  143. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/include/pybind11/stl_bind.h +747 -0
  144. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/noxfile.py +88 -0
  145. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/__init__.py +11 -0
  146. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/__main__.py +52 -0
  147. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/_version.py +12 -0
  148. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/_version.pyi +6 -0
  149. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/commands.py +21 -0
  150. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/py.typed +0 -0
  151. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/setup_helpers.py +482 -0
  152. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pybind11/setup_helpers.pyi +63 -0
  153. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/pyproject.toml +41 -0
  154. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/setup.cfg +56 -0
  155. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/setup.py +155 -0
  156. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/CMakeLists.txt +503 -0
  157. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/conftest.py +208 -0
  158. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/constructor_stats.h +275 -0
  159. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/cross_module_gil_utils.cpp +73 -0
  160. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/env.py +33 -0
  161. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/extra_python_package/pytest.ini +0 -0
  162. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/extra_python_package/test_files.py +279 -0
  163. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/extra_setuptools/pytest.ini +0 -0
  164. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/extra_setuptools/test_setuphelper.py +143 -0
  165. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/local_bindings.h +85 -0
  166. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/object.h +179 -0
  167. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/pybind11_cross_module_tests.cpp +151 -0
  168. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/pybind11_tests.cpp +91 -0
  169. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/pybind11_tests.h +85 -0
  170. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/pytest.ini +19 -0
  171. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/requirements.txt +12 -0
  172. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_async.cpp +26 -0
  173. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_async.py +25 -0
  174. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_buffers.cpp +216 -0
  175. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_buffers.py +163 -0
  176. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_builtin_casters.cpp +286 -0
  177. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_builtin_casters.py +536 -0
  178. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_call_policies.cpp +107 -0
  179. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_call_policies.py +248 -0
  180. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_callbacks.cpp +227 -0
  181. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_callbacks.py +202 -0
  182. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_chrono.cpp +84 -0
  183. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_chrono.py +210 -0
  184. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_class.cpp +550 -0
  185. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_class.py +473 -0
  186. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/CMakeLists.txt +84 -0
  187. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/embed.cpp +21 -0
  188. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt +28 -0
  189. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt +39 -0
  190. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt +46 -0
  191. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/main.cpp +6 -0
  192. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +41 -0
  193. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +35 -0
  194. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +41 -0
  195. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_cmake_build/test.py +10 -0
  196. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_constants_and_functions.cpp +165 -0
  197. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_constants_and_functions.py +53 -0
  198. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_copy_move.cpp +238 -0
  199. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_copy_move.py +126 -0
  200. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_custom_type_casters.cpp +141 -0
  201. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_custom_type_casters.py +117 -0
  202. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_custom_type_setup.cpp +41 -0
  203. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_custom_type_setup.py +50 -0
  204. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_docstring_options.cpp +69 -0
  205. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_docstring_options.py +42 -0
  206. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_eigen.cpp +348 -0
  207. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_eigen.py +771 -0
  208. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_embed/CMakeLists.txt +47 -0
  209. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_embed/catch.cpp +22 -0
  210. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_embed/external_module.cpp +23 -0
  211. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_embed/test_interpreter.cpp +326 -0
  212. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_embed/test_interpreter.py +15 -0
  213. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_enum.cpp +148 -0
  214. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_enum.py +272 -0
  215. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_eval.cpp +119 -0
  216. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_eval.py +51 -0
  217. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_eval_call.py +5 -0
  218. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_exceptions.cpp +285 -0
  219. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_exceptions.h +12 -0
  220. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_exceptions.py +265 -0
  221. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_factory_constructors.cpp +397 -0
  222. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_factory_constructors.py +520 -0
  223. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_gil_scoped.cpp +49 -0
  224. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_gil_scoped.py +94 -0
  225. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_iostream.cpp +125 -0
  226. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_iostream.py +331 -0
  227. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_kwargs_and_defaults.cpp +153 -0
  228. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_kwargs_and_defaults.py +284 -0
  229. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_local_bindings.cpp +107 -0
  230. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_local_bindings.py +257 -0
  231. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_methods_and_attributes.cpp +412 -0
  232. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_methods_and_attributes.py +517 -0
  233. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_modules.cpp +102 -0
  234. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_modules.py +92 -0
  235. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_multiple_inheritance.cpp +233 -0
  236. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_multiple_inheritance.py +360 -0
  237. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_numpy_array.cpp +472 -0
  238. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_numpy_array.py +593 -0
  239. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_numpy_dtypes.cpp +524 -0
  240. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_numpy_dtypes.py +441 -0
  241. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_numpy_vectorize.cpp +103 -0
  242. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_numpy_vectorize.py +267 -0
  243. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_opaque_types.cpp +73 -0
  244. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_opaque_types.py +59 -0
  245. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_operator_overloading.cpp +235 -0
  246. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_operator_overloading.py +146 -0
  247. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_pickling.cpp +189 -0
  248. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_pickling.py +82 -0
  249. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_pytypes.cpp +560 -0
  250. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_pytypes.py +651 -0
  251. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_sequences_and_iterators.cpp +500 -0
  252. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_sequences_and_iterators.py +253 -0
  253. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_smart_ptr.cpp +452 -0
  254. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_smart_ptr.py +318 -0
  255. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_stl.cpp +342 -0
  256. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_stl.py +291 -0
  257. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_stl_binders.cpp +131 -0
  258. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_stl_binders.py +318 -0
  259. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_tagbased_polymorphic.cpp +144 -0
  260. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_tagbased_polymorphic.py +29 -0
  261. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_thread.cpp +66 -0
  262. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_thread.py +44 -0
  263. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_union.cpp +22 -0
  264. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_union.py +9 -0
  265. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_virtual_functions.cpp +510 -0
  266. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/test_virtual_functions.py +408 -0
  267. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/valgrind-numpy-scipy.supp +140 -0
  268. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tests/valgrind-python.supp +117 -0
  269. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/FindCatch.cmake +70 -0
  270. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/FindEigen3.cmake +86 -0
  271. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/FindPythonLibsNew.cmake +257 -0
  272. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/check-style.sh +44 -0
  273. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/cmake_uninstall.cmake.in +23 -0
  274. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/libsize.py +39 -0
  275. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/make_changelog.py +64 -0
  276. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/pybind11Common.cmake +402 -0
  277. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/pybind11Config.cmake.in +233 -0
  278. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/pybind11NewTools.cmake +276 -0
  279. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/pybind11Tools.cmake +214 -0
  280. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/pyproject.toml +3 -0
  281. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/setup_global.py.in +65 -0
  282. xcoll/scattering_routines/geant4/collimasim/lib/pybind11/tools/setup_main.py.in +41 -0
  283. xcoll/scattering_routines/geant4/collimasim/pyproject.toml +8 -0
  284. xcoll/scattering_routines/geant4/collimasim/setup.py +144 -0
  285. xcoll/scattering_routines/geant4/collimasim/src/collimasim/BDSPyATInterface.cpp +403 -0
  286. xcoll/scattering_routines/geant4/collimasim/src/collimasim/BDSPyATInterface.hh +100 -0
  287. xcoll/scattering_routines/geant4/collimasim/src/collimasim/BDSXtrackInterface.cpp +763 -0
  288. xcoll/scattering_routines/geant4/collimasim/src/collimasim/BDSXtrackInterface.hh +118 -0
  289. xcoll/scattering_routines/geant4/collimasim/src/collimasim/__init__.py +8 -0
  290. xcoll/scattering_routines/geant4/collimasim/src/collimasim/bindings.cpp +63 -0
  291. xcoll/scattering_routines/geant4/collimasim/src/collimasim/pyCollimatorPass.py +142 -0
  292. xcoll/scattering_routines/geant4/collimasim/src/collimasim/xtrack_collimator.py +556 -0
  293. xcoll/scattering_routines/geant4/collimasim/src/collimasim.egg-info/PKG-INFO +6 -0
  294. xcoll/scattering_routines/geant4/collimasim/src/collimasim.egg-info/SOURCES.txt +24 -0
  295. xcoll/scattering_routines/geant4/collimasim/src/collimasim.egg-info/dependency_links.txt +1 -0
  296. xcoll/scattering_routines/geant4/collimasim/src/collimasim.egg-info/not-zip-safe +1 -0
  297. xcoll/scattering_routines/geant4/collimasim/src/collimasim.egg-info/top_level.txt +1 -0
  298. xcoll/scattering_routines/geant4/collimasim/tests/README.md +25 -0
  299. xcoll/scattering_routines/geant4/collimasim/tests/resources/CollDB_forions.dat +25 -0
  300. xcoll/scattering_routines/geant4/collimasim/tests/resources/CollDB_new_example.dat +18 -0
  301. xcoll/scattering_routines/geant4/collimasim/tests/resources/CollDB_old_example.dat +68 -0
  302. xcoll/scattering_routines/geant4/collimasim/tests/resources/CollDB_testing.dat +15 -0
  303. xcoll/scattering_routines/geant4/collimasim/tests/resources/CollDB_yaml_example.yaml +110 -0
  304. xcoll/scattering_routines/geant4/collimasim/tests/resources/collgaps.dat +7 -0
  305. xcoll/scattering_routines/geant4/collimasim/tests/resources/collgaps_pyat_test.dat +3 -0
  306. xcoll/scattering_routines/geant4/collimasim/tests/resources/collonly_twiss_file_example.tfs +54 -0
  307. xcoll/scattering_routines/geant4/collimasim/tests/resources/settings.gmad +3 -0
  308. xcoll/scattering_routines/geant4/collimasim/tests/resources/settings_black_absorber.gmad +3 -0
  309. xcoll/scattering_routines/geant4/collimasim/tests/resources/settings_ions.gmad +5 -0
  310. xcoll/scattering_routines/geant4/collimasim/tests/resources/twiss_file_testing.tfs +51 -0
  311. xcoll/scattering_routines/geant4/collimasim/tests/test_pyat.py +65 -0
  312. xcoll/scattering_routines/geant4/collimasim/tests/test_pyat_passmethod.py +59 -0
  313. xcoll/scattering_routines/geant4/collimasim/tests/test_pyat_tracking.py +102 -0
  314. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack.py +75 -0
  315. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_angle.py +74 -0
  316. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_colldb_load.py +84 -0
  317. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_interaction.py +159 -0
  318. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_interaction_ion.py +99 -0
  319. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_ions.py +78 -0
  320. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_lost_energy.py +88 -0
  321. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_tilt.py +80 -0
  322. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_tracking.py +97 -0
  323. xcoll/scattering_routines/geant4/collimasim/tests/test_xtrack_tracking_ions.py +96 -0
  324. xcoll/scattering_routines/geometry/__init__.py +6 -0
  325. xcoll/scattering_routines/geometry/collimator_geometry.h +218 -0
  326. xcoll/scattering_routines/geometry/crystal_geometry.h +153 -0
  327. xcoll/scattering_routines/geometry/geometry.py +26 -0
  328. xcoll/scattering_routines/geometry/get_s.h +92 -0
  329. xcoll/scattering_routines/geometry/methods.h +111 -0
  330. xcoll/scattering_routines/geometry/objects.h +154 -0
  331. xcoll/scattering_routines/geometry/rotation.h +23 -0
  332. xcoll/scattering_routines/geometry/segments.h +226 -0
  333. xcoll/scattering_routines/geometry/sort.h +184 -0
  334. {xcoll-0.3.6.dist-info → xcoll-0.5.0.dist-info}/METADATA +1 -1
  335. xcoll-0.5.0.dist-info/RECORD +413 -0
  336. xcoll/beam_elements/collimators_src/absorber.h +0 -141
  337. xcoll/beam_elements/collimators_src/everest_collimator.h +0 -142
  338. xcoll/beam_elements/collimators_src/everest_crystal.h +0 -115
  339. xcoll/collimator_settings.py +0 -457
  340. xcoll/impacts/__init__.py +0 -1
  341. xcoll/impacts/impacts.py +0 -102
  342. xcoll/impacts/impacts_src/impacts.h +0 -99
  343. xcoll/scattering_routines/everest/crystal.h +0 -1302
  344. xcoll/scattering_routines/everest/scatter.h +0 -169
  345. xcoll/scattering_routines/everest/scatter_crystal.h +0 -260
  346. xcoll/scattering_routines/fluka/build_fluka_input.py +0 -58
  347. xcoll-0.3.6.dist-info/RECORD +0 -111
  348. {xcoll-0.3.6.dist-info → xcoll-0.5.0.dist-info}/LICENSE +0 -0
  349. {xcoll-0.3.6.dist-info → xcoll-0.5.0.dist-info}/NOTICE +0 -0
  350. {xcoll-0.3.6.dist-info → xcoll-0.5.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  # copyright ############################### #
2
2
  # This file is part of the Xcoll Package. #
3
- # Copyright (c) CERN, 2023. #
3
+ # Copyright (c) CERN, 2024. #
4
4
  # ######################################### #
5
5
 
6
6
  import numpy as np
@@ -8,11 +8,14 @@ import numpy as np
8
8
  import xobjects as xo
9
9
  import xtrack as xt
10
10
 
11
- from ..collimator_settings import _get_LR, _set_LR, _get_LRUD, _set_LRUD
12
- from ..impacts import CollimatorImpacts
11
+ from ..interaction_record import InteractionRecord
13
12
  from ..general import _pkg_root
14
13
 
15
14
 
15
+ OPEN_JAW = 3.
16
+ OPEN_GAP = 999.
17
+
18
+
16
19
  class InvalidXcoll(xt.BeamElement):
17
20
  _xofields = {
18
21
  'length': xo.Float64
@@ -22,7 +25,7 @@ class InvalidXcoll(xt.BeamElement):
22
25
  behaves_like_drift = True
23
26
  allow_track = False
24
27
  skip_in_loss_location_refinement = True
25
- allow_backtrack = True
28
+ allow_loss_refinement = True
26
29
 
27
30
  # InvalidXcoll catches unallowed cases, like backtracking through a collimator
28
31
  _extra_c_sources = [
@@ -42,7 +45,9 @@ class InvalidXcoll(xt.BeamElement):
42
45
 
43
46
  class BaseBlock(xt.BeamElement):
44
47
  _xofields = {
45
- 'length': xo.Float64
48
+ 'length': xo.Float64,
49
+ 'active': xo.Int8,
50
+ '_record_interactions': xo.Int8
46
51
  }
47
52
 
48
53
  isthick = True
@@ -50,8 +55,12 @@ class BaseBlock(xt.BeamElement):
50
55
  behaves_like_drift = True
51
56
  skip_in_loss_location_refinement = True
52
57
 
58
+ _skip_in_to_dict = ['_record_interactions']
59
+ _store_in_to_dict = ['record_impacts', 'record_exits', 'record_scatterings']
60
+
53
61
  _depends_on = [InvalidXcoll]
54
- _internal_record_class = CollimatorImpacts
62
+
63
+ _internal_record_class = InteractionRecord
55
64
 
56
65
  # This is an abstract class and cannot be instantiated
57
66
  def __new__(cls, *args, **kwargs):
@@ -60,55 +69,111 @@ class BaseBlock(xt.BeamElement):
60
69
  instance = super().__new__(cls)
61
70
  return instance
62
71
 
72
+ def __init__(self, **kwargs):
73
+ if '_xobject' not in kwargs:
74
+ kwargs.setdefault('active', True)
75
+ super().__init__(**kwargs)
76
+
63
77
  def enable_scattering(self):
64
78
  if hasattr(self, '_tracking'):
79
+ if hasattr(self, 'optics') and self.optics is None and \
80
+ (hasattr(self, '_gap_L_set_manually') and self._gap_L_set_manually() \
81
+ or hasattr(self, '_gap_R_set_manually') and self._gap_R_set_manually()):
82
+ raise ValueError("Gap set but optics not yet assigned! "
83
+ + "Cannot enable scattering.")
65
84
  self._tracking = True
66
85
 
67
86
  def disable_scattering(self):
68
87
  if hasattr(self, '_tracking'):
69
88
  self._tracking = False
70
89
 
90
+ @property
91
+ def record_impacts(self):
92
+ return bool(self._record_interactions % 2)
93
+
94
+ @record_impacts.setter
95
+ def record_impacts(self, val):
96
+ if val and not self.record_impacts:
97
+ self._record_interactions += 1
98
+ elif not val and self.record_impacts:
99
+ self._record_interactions -= 1
100
+
101
+ @property
102
+ def record_exits(self):
103
+ return bool((self._record_interactions >> 1) % 2)
104
+
105
+ @record_exits.setter
106
+ def record_exits(self, val):
107
+ if val and not self.record_exits:
108
+ self._record_interactions += 2
109
+ elif not val and self.record_exits:
110
+ self._record_interactions -= 2
111
+
112
+ @property
113
+ def record_scatterings(self):
114
+ return bool((self._record_interactions >> 2) % 2)
115
+
116
+ @record_scatterings.setter
117
+ def record_scatterings(self, val):
118
+ if val and not self.record_scatterings:
119
+ self._record_interactions += 4
120
+ elif not val and self.record_scatterings:
121
+ self._record_interactions -= 4
122
+
123
+ def _verify_consistency(self):
124
+ assert isinstance(self.active, bool) or self.active in [0, 1]
125
+ assert self._record_interactions in list(range(8))
126
+
71
127
  def get_backtrack_element(self, _context=None, _buffer=None, _offset=None):
72
128
  return InvalidXcoll(length=-self.length,
73
129
  _context=_context, _buffer=_buffer, _offset=_offset)
74
130
 
75
131
 
76
- class BaseCollimator(xt.BeamElement):
77
- _xofields = {
78
- 'inactive_front': xo.Float64, # Drift before jaws
79
- 'active_length': xo.Float64, # Length of jaws
80
- 'inactive_back': xo.Float64, # Drift after jaws
81
- 'jaw_L': xo.Float64, # left jaw (distance to ref)
82
- 'jaw_R': xo.Float64, # right jaw
83
- 'ref_x': xo.Float64, # center of collimator reference frame
84
- 'ref_y': xo.Float64,
85
- 'sin_zL': xo.Float64, # angle of left jaw
86
- 'cos_zL': xo.Float64,
87
- 'sin_zR': xo.Float64, # angle of right jaw
88
- 'cos_zR': xo.Float64,
89
- 'sin_yL': xo.Float64, # tilt of left jaw (around jaw midpoint)
90
- 'cos_yL': xo.Float64,
91
- 'tan_yL': xo.Float64,
92
- 'sin_yR': xo.Float64, # tilt of right jaw (around jaw midpoint)
93
- 'cos_yR': xo.Float64,
94
- 'tan_yR': xo.Float64,
95
- '_side': xo.Int8, # is it a onesided collimator?
96
- 'active': xo.Int8
132
+ class BaseCollimator(BaseBlock):
133
+ _xofields = {**BaseBlock._xofields,
134
+ # Collimator angle
135
+ '_sin_zL': xo.Float64,
136
+ '_cos_zL': xo.Float64,
137
+ '_sin_zR': xo.Float64,
138
+ '_cos_zR': xo.Float64,
139
+ '_sin_zDiff': xo.Float64, # Angle of right jaw: difference with respect to angle of left jaw
140
+ '_cos_zDiff': xo.Float64,
141
+ '_jaws_parallel': xo.Int8,
142
+ # Jaw corners (this is the x-coordinate in the rotated frame)
143
+ '_jaw_LU': xo.Float64, # left upstream
144
+ '_jaw_RU': xo.Float64, # right upstream
145
+ '_jaw_LD': xo.Float64, # left downstream
146
+ '_jaw_RD': xo.Float64, # right downstream
147
+ # Tilts (superfluous but added to speed up calculations)
148
+ '_sin_yL': xo.Float64,
149
+ '_cos_yL': xo.Float64,
150
+ '_tan_yL': xo.Float64,
151
+ '_sin_yR': xo.Float64,
152
+ '_cos_yR': xo.Float64,
153
+ '_tan_yR': xo.Float64,
154
+ # Other
155
+ '_side': xo.Int8,
156
+ # These are not used in C, but need to be an xofield to get them in the to_dict:
157
+ '_align': xo.Int8,
158
+ '_gap_L': xo.Float64,
159
+ '_gap_R': xo.Float64,
160
+ '_nemitt_x': xo.Float64,
161
+ '_nemitt_y': xo.Float64
97
162
  }
98
163
 
99
- isthick = True
100
- allow_track = False
101
- behaves_like_drift = True
102
- skip_in_loss_location_refinement = True
164
+ isthick = BaseBlock.isthick
165
+ allow_track = BaseBlock.allow_track
166
+ behaves_like_drift = BaseBlock.behaves_like_drift
167
+ skip_in_loss_location_refinement = BaseBlock.skip_in_loss_location_refinement
168
+ allow_double_sided = True
103
169
 
104
- _skip_in_to_dict = ['jaw_L', 'jaw_R', 'ref_x', 'ref_y',
105
- 'sin_yL', 'cos_yL', 'tan_yL', 'sin_yR', 'cos_yR', 'tan_yR',
106
- 'sin_zL', 'cos_zL', 'sin_zR', 'cos_zR', '_side']
107
- _store_in_to_dict = ['angle', 'tilt', 'jaw', 'reference_center', 'side']
108
- # Extra fields (only in Python): angle_L, angle_R, tilt_L, tilt_R, jaw_LU, jaw_LD, jaw_RU, jaw_RD
170
+ _skip_in_to_dict = [*BaseBlock._skip_in_to_dict, *[f for f in _xofields if f.startswith('_')]]
171
+ _store_in_to_dict = [*BaseBlock._store_in_to_dict, 'angle', 'jaw', 'tilt', 'gap', 'side', 'align', 'emittance']
172
+
173
+ _depends_on = [BaseBlock]
174
+
175
+ _internal_record_class = BaseBlock._internal_record_class
109
176
 
110
- _depends_on = [InvalidXcoll, xt.Drift, xt.XYShift, xt.SRotation, xt.YRotation]
111
- _internal_record_class = CollimatorImpacts
112
177
 
113
178
  # This is an abstract class and cannot be instantiated
114
179
  def __new__(cls, *args, **kwargs):
@@ -118,200 +183,700 @@ class BaseCollimator(xt.BeamElement):
118
183
  return instance
119
184
 
120
185
  def __init__(self, **kwargs):
186
+ to_assign = {}
121
187
  if '_xobject' not in kwargs:
122
- # Set jaw
123
- if 'jaw' in kwargs:
124
- if 'jaw_L' in kwargs or 'jaw_R' in kwargs:
125
- raise ValuError("Cannot use both 'jaw' and 'jaw_L/R'!")
126
- _set_LR(kwargs, 'jaw', kwargs.pop('jaw'), neg=True)
127
- else:
128
- kwargs.setdefault('jaw_L', 1)
129
- kwargs.setdefault('jaw_R', -1)
130
-
131
- # Set reference_center
132
- if 'reference_center' in kwargs:
133
- if 'ref_x' in kwargs or 'ref_y' in kwargs:
134
- raise ValuError("Cannot use both 'reference_center' and 'ref_x/y'!")
135
- _set_LR(kwargs, 'ref', kwargs.pop('reference_center'), name_L='_x', name_R='_y')
136
- else:
137
- kwargs.setdefault('ref_x', 0)
138
- kwargs.setdefault('ref_y', 0)
139
-
140
188
  # Set side
141
- kwargs['_side'] = _side_setter(kwargs.pop('side', 'both'))
189
+ to_assign['side'] = kwargs.pop('side', 'both')
142
190
 
143
191
  # Set angle
144
192
  if 'angle' in kwargs:
145
- if 'angle_L' in kwargs or 'angle_R' in kwargs:
146
- raise ValuError("Cannot use both 'angle' and 'angle_L/R'!")
147
- kwargs['sin_zL'], kwargs['cos_zL'], _, kwargs['sin_zR'], kwargs['cos_zR'], _ \
148
- = _angle_setter(kwargs.pop('angle', 0))
193
+ for key in ['angle_L', 'angle_R']:
194
+ if key in kwargs:
195
+ raise ValueError(f"Cannot use both `angle` and `{key}`!")
196
+ to_assign['angle'] = kwargs.pop('angle')
149
197
  else:
150
- anglerad_L = kwargs.pop('angle_L', 0) / 180. * np.pi
151
- kwargs['sin_zL'] = np.sin(anglerad_L)
152
- kwargs['cos_zL'] = np.cos(anglerad_L)
153
- anglerad_R = kwargs.pop('angle_R', 0) / 180. * np.pi
154
- kwargs['sin_zR'] = np.sin(anglerad_R)
155
- kwargs['cos_zR'] = np.cos(anglerad_R)
198
+ to_assign['angle_L'] = kwargs.pop('angle_L', 0)
199
+ to_assign['angle_R'] = kwargs.pop('angle_R', 0)
200
+
201
+ # Set jaw
202
+ if 'jaw' in kwargs:
203
+ for key in ['jaw_L', 'jaw_R', 'jaw_LU', 'jaw_LD', 'jaw_RU', 'jaw_RD', 'gap', 'gap_L', 'gap_R']:
204
+ if key in kwargs:
205
+ raise ValueError(f"Cannot use both `jaw` and `{key}`!")
206
+ to_assign['jaw'] = kwargs.pop('jaw')
207
+ elif 'jaw_L' in kwargs or 'jaw_R' in kwargs:
208
+ for key in ['jaw_LU', 'jaw_LD', 'jaw_RU', 'jaw_RD', 'gap', 'gap_L', 'gap_R']:
209
+ if key in kwargs:
210
+ raise ValueError(f"Cannot use `jaw_L` or `jaw_R` together with `{key}`!")
211
+ to_assign['jaw_L'] = kwargs.pop('jaw_L', None)
212
+ to_assign['jaw_R'] = kwargs.pop('jaw_R', None)
213
+ elif 'jaw_LU' in kwargs or 'jaw_LD' in kwargs or 'jaw_RU' in kwargs or 'jaw_RD' in kwargs:
214
+ for key in ['tilt', 'tilt_L', 'tilt_R', 'gap', 'gap_L', 'gap_R']:
215
+ if key in kwargs:
216
+ raise ValueError(f"Cannot use both `jaw_LU` etc with `{key}`!")
217
+ to_assign['jaw_LU'] = kwargs.pop('jaw_LU', None)
218
+ to_assign['jaw_RU'] = kwargs.pop('jaw_RU', None)
219
+ to_assign['jaw_LD'] = kwargs.pop('jaw_LD', None)
220
+ to_assign['jaw_RD'] = kwargs.pop('jaw_RD', None)
221
+ kwargs.setdefault('_jaw_LU', OPEN_JAW) # Important that these are initialised, in
222
+ kwargs.setdefault('_jaw_RU', -OPEN_JAW) # order to keep a tilt (given together with
223
+ kwargs.setdefault('_jaw_LD', OPEN_JAW) # a gap) when optics are not known yet.
224
+ kwargs.setdefault('_jaw_RD', -OPEN_JAW)
156
225
 
157
226
  # Set tilt
158
227
  if 'tilt' in kwargs:
159
- if 'tilt_L' in kwargs or 'tilt_R' in kwargs:
160
- raise ValuError("Cannot use both 'tilt' and 'tilt_L/R'!")
161
- kwargs['sin_yL'], kwargs['cos_yL'], kwargs['tan_yL'], \
162
- kwargs['sin_yR'], kwargs['cos_yR'], kwargs['tan_yR'] \
163
- = _angle_setter(kwargs.pop('tilt', 0), rad=True)
228
+ for key in ['tilt_L', 'tilt_R']:
229
+ if key in kwargs:
230
+ raise ValueError(f"Cannot use both `tilt` and `{key}`!")
231
+ to_assign['tilt'] = kwargs.pop('tilt')
164
232
  else:
165
- # anglerad_L = kwargs.pop('tilt_L', 0) / 180. * np.pi
166
- anglerad_L = kwargs.pop('tilt_L', 0)
167
- kwargs['sin_yL'] = np.sin(anglerad_L)
168
- kwargs['cos_yL'] = np.cos(anglerad_L)
169
- kwargs['tan_yL'] = np.cos(anglerad_L)
170
- # anglerad_R = kwargs.pop('tilt_R', 0) / 180. * np.pi
171
- anglerad_R = kwargs.pop('tilt_R', 0)
172
- kwargs['sin_yR'] = np.sin(anglerad_R)
173
- kwargs['cos_yR'] = np.cos(anglerad_R)
174
- kwargs['tan_yR'] = np.cos(anglerad_R)
175
-
176
- # Set lengths
177
- if 'length' in kwargs.keys():
178
- if 'active_length' in kwargs.keys():
179
- if 'inactive_front' in kwargs.keys():
180
- if 'inactive_back' in kwargs.keys():
181
- raise ValueError("Too many length variables used at initialisation!")
182
- kwargs['inactive_back'] = kwargs.pop('length') - kwargs['inactive_front'] - kwargs['active_length']
183
- elif 'inactive_back' in kwargs.keys():
184
- if 'inactive_front' in kwargs.keys():
185
- raise ValueError("Too many length variables used at initialisation!")
186
- kwargs['inactive_front'] = kwargs.pop('length') - kwargs['inactive_back'] - kwargs['active_length']
187
- else:
188
- kwargs['inactive_front'] = (kwargs['length']- kwargs['active_length'])/2
189
- kwargs['inactive_back'] = (kwargs.pop('length') - kwargs['active_length'])/2
190
- elif 'inactive_front' in kwargs.keys():
191
- if 'inactive_back' in kwargs.keys():
192
- kwargs['active_length'] = kwargs.pop('length') - kwargs['inactive_back'] - kwargs['inactive_front']
193
- else:
194
- kwargs['active_length'] = kwargs.pop('length') - kwargs['inactive_front']
195
- elif 'inactive_back' in kwargs.keys():
196
- kwargs['active_length'] = kwargs.pop('length') - kwargs['inactive_back']
197
- else:
198
- kwargs['active_length'] = kwargs.pop('length')
199
- kwargs.setdefault('inactive_front', 0)
200
- kwargs.setdefault('inactive_back', 0)
201
- kwargs.setdefault('active_length', 0)
202
- kwargs.setdefault('active', True)
233
+ to_assign['tilt_L'] = kwargs.pop('tilt_L', 0)
234
+ to_assign['tilt_R'] = kwargs.pop('tilt_R', 0)
235
+
236
+ # Set gap
237
+ if 'gap' in kwargs:
238
+ for key in ['jaw', 'jaw_L', 'jaw_R', 'jaw_LU', 'jaw_LD', 'jaw_RU', 'jaw_RD', 'gap_L', 'gap_R']:
239
+ if key in kwargs:
240
+ raise ValueError(f"Cannot use both `gap` and `{key}`!")
241
+ to_assign['gap'] = kwargs.pop('gap')
242
+ elif 'gap_L' in kwargs or 'gap_R' in kwargs:
243
+ for key in ['jaw', 'jaw_L', 'jaw_R', 'jaw_LU', 'jaw_LD', 'jaw_RU', 'jaw_RD', 'gap']:
244
+ if key in kwargs:
245
+ raise ValueError(f"Cannot use both `gap` and `{key}`!")
246
+ to_assign['gap_L'] = kwargs.pop('gap_L', None)
247
+ to_assign['gap_R'] = kwargs.pop('gap_R', None)
248
+ kwargs.setdefault('_gap_L', OPEN_GAP)
249
+ kwargs.setdefault('_gap_R', -OPEN_GAP)
250
+
251
+ # Set others
252
+ to_assign['align'] = kwargs.pop('align', 'upstream')
253
+ to_assign['emittance'] = kwargs.pop('emittance', None)
203
254
 
204
255
  super().__init__(**kwargs)
256
+ # Careful: non-xofields are not passed correctly between copy's / to_dict. This messes with flags etc..
257
+ # We also have to manually initialise them for xobject generation
258
+ if not hasattr(self, '_optics'):
259
+ self._optics = None
260
+ for key, val in to_assign.items():
261
+ setattr(self, key, val)
262
+ self._verify_consistency()
205
263
 
206
264
 
207
- def enable_scattering(self):
208
- if hasattr(self, '_tracking'):
209
- self._tracking = True
265
+ # Main collimator angle
266
+ # =====================
210
267
 
211
- def disable_scattering(self):
212
- if hasattr(self, '_tracking'):
213
- self._tracking = False
268
+ @property
269
+ def angle(self):
270
+ return self.angle_L if self.angle_L==self.angle_R else [self.angle_L, self.angle_R]
271
+
272
+ @angle.setter
273
+ def angle(self, val):
274
+ if not hasattr(val, '__iter__'):
275
+ self.angle_L = val
276
+ self.angle_R = val
277
+ elif len(val) == 1:
278
+ self.angle_L = val[0]
279
+ self.angle_R = val[0]
280
+ elif len(val) == 2:
281
+ self.angle_L = val[0]
282
+ self.angle_R = val[1]
283
+ else:
284
+ raise ValueError(f"The attribute `angle` should be of the form LR or [L, R] "
285
+ + f"but got {val}.")
214
286
 
215
287
  @property
216
- def jaw(self):
217
- return _get_LR(self, 'jaw', neg=True)
288
+ def angle_L(self):
289
+ return round(np.rad2deg(np.arctan2(self._sin_zL, self._cos_zL)), 10)
218
290
 
219
- @jaw.setter
291
+ @angle_L.setter
292
+ def angle_L(self, angle_L):
293
+ self._sin_zL = np.sin(np.deg2rad(angle_L))
294
+ self._cos_zL = np.cos(np.deg2rad(angle_L))
295
+ if np.isclose(self.angle_R, angle_L):
296
+ self._jaws_parallel = True
297
+ self._sin_zDiff = 0.
298
+ self._cos_zDiff = 1.
299
+ else:
300
+ self._jaws_parallel = False
301
+ self._sin_zDiff = np.sin(np.deg2rad(self.angle_R - angle_L))
302
+ self._cos_zDiff = np.cos(np.deg2rad(self.angle_R - angle_L))
303
+ self._apply_optics()
304
+
305
+ @property
306
+ def angle_R(self):
307
+ return round(np.rad2deg(np.arctan2(self._sin_zR, self._cos_zR)), 10)
308
+
309
+ @angle_R.setter
310
+ def angle_R(self, angle_R):
311
+ self._sin_zR = np.sin(np.deg2rad(angle_R))
312
+ self._cos_zR = np.cos(np.deg2rad(angle_R))
313
+ if np.isclose(self.angle_L, angle_R):
314
+ self._jaws_parallel = True
315
+ self._sin_zDiff = 0.
316
+ self._cos_zDiff = 1.
317
+ else:
318
+ self._jaws_parallel = False
319
+ self._sin_zDiff = np.sin(np.deg2rad(angle_R - self.angle_L))
320
+ self._cos_zDiff = np.cos(np.deg2rad(angle_R - self.angle_L))
321
+ self._apply_optics()
322
+
323
+
324
+ # Jaw attributes
325
+ # ==============
326
+
327
+ @property
328
+ def jaw(self):
329
+ if self.jaw_L is None and self.jaw_R is None:
330
+ return None
331
+ elif (self.side == 'left' and self.tilt_L == 0) \
332
+ or (self.side == 'right' and self.tilt_R == 0) \
333
+ or (self.tilt_L == 0 and self.tilt_R == 0):
334
+ return [self.jaw_L, self.jaw_R]
335
+ else:
336
+ return [[self.jaw_LU, self.jaw_RU], [self.jaw_LD, self.jaw_RD]]
337
+
338
+ @jaw.setter # Keeps the tilts unless all 4 corners are specified
220
339
  def jaw(self, val):
221
- _set_LR(self, 'jaw', val, neg=True)
340
+ if not hasattr(val, '__iter__') or len(val) == 1:
341
+ val = val[0] if hasattr(val, '__iter__') else val
342
+ if self.side == 'left':
343
+ self.jaw_L = val
344
+ elif self.side == 'right':
345
+ # self.jaw_R = -val if val is not None else None
346
+ self.jaw_R = val
347
+ else:
348
+ self.jaw_L = val
349
+ self.jaw_R = -val if val is not None else None
350
+ return
351
+ elif len(val) == 2:
352
+ if hasattr(val[0], '__iter__'):
353
+ if hasattr(val[1], '__iter__') and len(val[0]) == 2 and len(val[1]) == 2:
354
+ self.jaw_LU = val[0][0]
355
+ self.jaw_RU = val[0][1]
356
+ self.jaw_LD = val[1][0]
357
+ self.jaw_RD = val[1][1]
358
+ return
359
+ else:
360
+ self.jaw_L = val[0]
361
+ self.jaw_R = val[1]
362
+ return
363
+ # If we got here, val is incompatible
364
+ raise ValueError(f"The attribute `jaw` should be of the form [L, R] or "
365
+ + f"[[LU, RU], [LD, RD], but got {val}.")
222
366
 
223
367
  @property
224
- def jaw_LU(self):
225
- return self.jaw_L - self.sin_yL*self.active_length/2
368
+ def jaw_L(self):
369
+ jaw_L = (self._jaw_LU + self._jaw_LD) / 2
370
+ if not np.isclose(jaw_L, OPEN_JAW, atol=1.e-10): # open position
371
+ return jaw_L
372
+
373
+ @jaw_L.setter # This moves both jaw_LU and jaw_LD in parallel
374
+ def jaw_L(self, val):
375
+ if self.side == 'right' and val is not None:
376
+ val = None
377
+ print("Warning: Ignored value for jaw_L (right-sided collimator).")
378
+ if val is None:
379
+ val = OPEN_JAW
380
+ self._gap_L = OPEN_GAP
381
+ diff = val - (self._jaw_LU + self._jaw_LD) / 2
382
+ self._jaw_LU += diff
383
+ self._jaw_LD += diff
384
+ self._update_gaps(only_L=True)
226
385
 
227
- @jaw_LU.setter # This assumes you keep jaw_LD fixed, hence both jaw_L and the tilt change
228
- def jaw_LU(self, jaw_LU):
229
- jaw_LD = self.jaw_LD
230
- self.jaw_L = (jaw_LU+jaw_LD)/2
231
- self.sin_yL = (jaw_LD-jaw_LU)/self.active_length
232
- self.cos_yL = np.sqrt(1-self.sin_yL**2)
233
- self.tan_yL = self.sin_yL / self.cos_yL
386
+ @property
387
+ def jaw_R(self):
388
+ jaw_R = (self._jaw_RU + self._jaw_RD) / 2
389
+ if not np.isclose(jaw_R, -OPEN_JAW, atol=1.e-10): # open position
390
+ return jaw_R
391
+
392
+ @jaw_R.setter # This moves both jaw_RU and jaw_RD in parallel
393
+ def jaw_R(self, val):
394
+ if self.side == 'left' and val is not None:
395
+ val = None
396
+ print("Warning: Ignored value for jaw_R (left-sided collimator).")
397
+ if val is None:
398
+ val = -OPEN_JAW
399
+ self._gap_R = -OPEN_GAP
400
+ diff = val - (self._jaw_RU + self._jaw_RD) / 2
401
+ self._jaw_RU += diff
402
+ self._jaw_RD += diff
403
+ self._update_gaps(only_R=True)
234
404
 
235
405
  @property
236
- def jaw_LD(self):
237
- return self.jaw_L + self.sin_yL*self.active_length/2
406
+ def jaw_LU(self):
407
+ if not self.jaw_L is None:
408
+ return self._jaw_LU
409
+
410
+ @jaw_LU.setter # This assumes jaw_LD remains fixed, hence both jaw_L and the tilt change
411
+ def jaw_LU(self, val):
412
+ if self.side == 'right':
413
+ if val is not None:
414
+ print("Warning: Ignored value for jaw_LU (right-sided collimator).")
415
+ return
416
+ if val is None:
417
+ raise ValueError("Cannot set corner to None! Use open_jaws() or set jaw_L to None.")
418
+ self._jaw_LU = val
419
+ self._update_tilts() # Extra, to update tilts which are also in C for efficiency
420
+ self._update_gaps(only_L=True)
238
421
 
239
- @jaw_LD.setter # This assumes you keep jaw_LU fixed, hence both jaw_L and the tilt change
240
- def jaw_LD(self, jaw_LD):
241
- jaw_LU = self.jaw_LU
242
- self.jaw_L = (jaw_LU+jaw_LD)/2
243
- self.sin_yL = (jaw_LD-jaw_LU)/self.active_length
244
- self.cos_yL = np.sqrt(1-self.sin_yL**2)
245
- self.tan_yL = self.sin_yL / self.cos_yL
422
+ @property
423
+ def jaw_LD(self):
424
+ if not self.jaw_L is None:
425
+ return self._jaw_LD
426
+
427
+ @jaw_LD.setter # This assumes jaw_LU remains fixed, hence both jaw_L and the tilt change
428
+ def jaw_LD(self, val):
429
+ if self.side == 'right':
430
+ if val is not None:
431
+ print("Warning: Ignored value for jaw_LD (right-sided collimator).")
432
+ return
433
+ if val is None:
434
+ raise ValueError("Cannot set corner to None! Use open_jaws() or set jaw_L to None.")
435
+ self._jaw_LD = val
436
+ self._update_tilts() # Extra, to update tilts which are also in C for efficiency
437
+ self._update_gaps(only_L=True)
246
438
 
247
439
  @property
248
440
  def jaw_RU(self):
249
- return self.jaw_R - self.sin_yR*self.active_length/2
250
-
251
- @jaw_RU.setter # This assumes you keep jaw_RD fixed, hence both jaw_R and the tilt change
252
- def jaw_RU(self, jaw_RU):
253
- jaw_RD = self.jaw_RD
254
- self.jaw_R = (jaw_RU+jaw_RD)/2
255
- self.sin_yR = (jaw_RD-jaw_RU)/self.active_length
256
- self.cos_yR = np.sqrt(1-self.sin_yR**2)
257
- self.tan_yR = self.sin_yR / self.cos_yR
441
+ if not self.jaw_R is None:
442
+ return self._jaw_RU
443
+
444
+ @jaw_RU.setter # This assumes jaw_RD remains fixed, hence both jaw_R and the tilt change
445
+ def jaw_RU(self, val):
446
+ if self.side == 'left':
447
+ if val is not None:
448
+ print("Warning: Ignored value for jaw_RU (left-sided collimator).")
449
+ return
450
+ if val is None:
451
+ raise ValueError("Cannot set corner to None! Use open_jaws() or set jaw_R to None.")
452
+ self._jaw_RU = val
453
+ self._update_tilts() # Extra, to update tilts which are also in C for efficiency
454
+ self._update_gaps(only_R=True)
258
455
 
259
456
  @property
260
457
  def jaw_RD(self):
261
- return self.jaw_R + self.sin_yR*self.active_length/2
458
+ if not self.jaw_R is None:
459
+ return self._jaw_RD
460
+
461
+ @jaw_RD.setter # This assumes jaw_RU remains fixed, hence both jaw_R and the tilt change
462
+ def jaw_RD(self, val):
463
+ if self.side == 'left':
464
+ if val is not None:
465
+ print("Warning: Ignored value for jaw_RD (left-sided collimator).")
466
+ return
467
+ if val is None:
468
+ raise ValueError("Cannot set corner to None! Use open_jaws() or set jaw_R to None.")
469
+ self._jaw_RD = val
470
+ self._update_tilts() # Extra, to update tilts which are also in C for efficiency
471
+ self._update_gaps(only_R=True)
262
472
 
263
- @jaw_RD.setter # This assumes you keep jaw_RU fixed, hence both jaw_R and the tilt change
264
- def jaw_RD(self, jaw_RD):
265
- jaw_RU = self.jaw_RU
266
- self.jaw_R = (jaw_RU+jaw_RD)/2
267
- self.sin_yR = (jaw_RD-jaw_RU)/self.active_length
268
- self.cos_yR = np.sqrt(1-self.sin_yR**2)
269
- self.tan_yR = self.sin_yR / self.cos_yR
473
+ @property
474
+ def jaw_s_LU(self):
475
+ return self.length/2 * (1 - self._cos_yL)
270
476
 
271
477
  @property
272
- def angle_L(self):
273
- return round(np.arctan2(self.sin_zL, self.cos_zL) * (180.0 / np.pi), 10)
478
+ def jaw_s_LD(self):
479
+ return self.length/2 * (1 + self._cos_yL)
274
480
 
275
- @angle_L.setter
276
- def angle_L(self, angle_L):
277
- anglerad_L = angle_L / 180. * np.pi
278
- self.sin_zL = np.sin(anglerad_L)
279
- self.cos_zL = np.cos(anglerad_L)
481
+ @property
482
+ def jaw_s_RU(self):
483
+ return self.length/2 * (1 - self._cos_yR)
280
484
 
281
485
  @property
282
- def angle_R(self):
283
- return round(np.arctan2(self.sin_zR, self.cos_zR) * (180.0 / np.pi), 10)
486
+ def jaw_s_RD(self):
487
+ return self.length/2 * (1 + self._cos_yR)
488
+
489
+ def open_jaws(self, keep_tilts=False):
490
+ self.jaw_L = None
491
+ self.jaw_R = None
492
+ if not keep_tilts:
493
+ self.tilt = 0
494
+
495
+ def _update_tilts(self):
496
+ if self.side != 'right':
497
+ self._sin_yL = (self.jaw_LD - self.jaw_LU) / self.length
498
+ self._cos_yL = np.sqrt(1 - self._sin_yL**2)
499
+ self._tan_yL = self._sin_yL / self._cos_yL
500
+ if self.side != 'left':
501
+ self._sin_yR = (self.jaw_RD - self.jaw_RU) / self.length
502
+ self._cos_yR = np.sqrt(1 - self._sin_yR**2)
503
+ self._tan_yR = self._sin_yR / self._cos_yR
504
+
505
+ def _update_gaps(self, only_L=False, only_R=False):
506
+ # If we had set a value for the gap manually, this needs to be updated
507
+ # as well after setting the jaw
508
+ if self._gap_L_set_manually() and not only_R:
509
+ self._gap_L = self.gap_L
510
+ if self._gap_R_set_manually() and not only_L:
511
+ self._gap_R = self.gap_R
512
+
513
+
514
+ # Tilt attributes
515
+ # ===============
284
516
 
285
- @angle_R.setter
286
- def angle_R(self, angle_R):
287
- anglerad_R = angle_R / 180. * np.pi
288
- self.sin_zR = np.sin(anglerad_R)
289
- self.cos_zR = np.cos(anglerad_R)
517
+ # TODO: tilts are in rad! Do we want that? It's a bit inconsistent with angle which is in deg...
290
518
 
291
519
  @property
292
- def angle(self):
293
- return self.angle_L if self.angle_L==self.angle_R else [self.angle_L, self.angle_R]
520
+ def tilt(self):
521
+ if self.side == 'left':
522
+ return self.tilt_L
523
+ elif self.side == 'right':
524
+ return self.tilt_R
525
+ else:
526
+ return self.tilt_L if self.tilt_L==self.tilt_R else [self.tilt_L, self.tilt_R]
294
527
 
295
- @angle.setter
296
- def angle(self, angle):
297
- self.sin_zL, self.cos_zL, _, self.sin_zR, self.cos_zR, _ = _angle_setter(angle)
528
+ @tilt.setter
529
+ def tilt(self, val):
530
+ if not hasattr(val, '__iter__') or len(val) == 1:
531
+ val = val[0] if hasattr(val, '__iter__') else val
532
+ if self.side == 'left':
533
+ self.tilt_L = val
534
+ elif self.side == 'right':
535
+ self.tilt_R = val
536
+ else:
537
+ self.tilt_L = val
538
+ self.tilt_R = val
539
+ elif len(val) == 2:
540
+ self.tilt_L = val[0]
541
+ self.tilt_R = val[1]
542
+ else:
543
+ raise ValueError
544
+
545
+ @property
546
+ def tilt_L(self):
547
+ if self.side != 'right':
548
+ return round(np.arctan2(self._sin_yL, self._cos_yL), 10)
549
+
550
+ @tilt_L.setter # This assumes jaw_L remains fixed (hence jaw_LU and jaw_LD change)
551
+ def tilt_L(self, val):
552
+ if self.side == 'right' and val != 0:
553
+ val = 0
554
+ print("Warning: Ignored value for tilt_L (right-sided collimator).")
555
+ if val != 0:
556
+ print("Warning: Setting a tilt does not preserve the hierarchy, as there "
557
+ + "will always be one corner that tightens (the tilt is applied at "
558
+ + "the centre of the jaw).")
559
+ self._sin_yL = np.sin(val)
560
+ self._cos_yL = np.cos(val)
561
+ self._tan_yL = np.tan(val)
562
+ jaw_L = (self._jaw_LU + self._jaw_LD) / 2
563
+ self._jaw_LD = jaw_L + self._sin_yL * self.length / 2.
564
+ self._jaw_LU = jaw_L - self._sin_yL * self.length / 2.
565
+
566
+ @property
567
+ def tilt_R(self):
568
+ if self.side != 'left':
569
+ return round(np.arctan2(self._sin_yR, self._cos_yR), 10)
570
+
571
+ @tilt_R.setter # This assumes jaw_R remains fixed (hence jaw_RU and jaw_RD change)
572
+ def tilt_R(self, val):
573
+ if self.side == 'left' and val != 0:
574
+ val = 0
575
+ print("Warning: Ignored value for tilt_R (left-sided collimator).")
576
+ if val != 0:
577
+ print("Warning: Setting a tilt does not preserve the hierarchy, as there "
578
+ + "will always be one corner that tightens (the tilt is applied at "
579
+ + "the centre of the jaw).")
580
+ self._sin_yR = np.sin(val)
581
+ self._cos_yR = np.cos(val)
582
+ self._tan_yR = np.tan(val)
583
+ jaw_R = (self._jaw_RU + self._jaw_RD) / 2
584
+ self._jaw_RD = jaw_R + self._sin_yR * self.length / 2.
585
+ self._jaw_RU = jaw_R - self._sin_yR * self.length / 2.
586
+
587
+
588
+ # Optics
589
+ # ======
590
+
591
+ @property
592
+ def optics(self):
593
+ return self._optics
594
+
595
+ def optics_ready(self):
596
+ return self.emittance is not None and self.optics is not None
597
+
598
+ def assign_optics(self, *, nemitt_x=None, nemitt_y=None, beta_gamma_rel=None, name=None, twiss=None,
599
+ twiss_upstream=None, twiss_downstream=None):
600
+ from xcoll.beam_elements import _all_collimator_classes
601
+ if not isinstance(self, _all_collimator_classes):
602
+ raise ValueError("Please install collimator before assigning optics.")
603
+ if nemitt_x is None:
604
+ if self.nemitt_x is None:
605
+ raise ValueError("Need to provide `nemitt_x`.")
606
+ else:
607
+ self.nemitt_x = nemitt_x
608
+ if nemitt_y is None:
609
+ if self.nemitt_y is None:
610
+ raise ValueError("Need to provide `nemitt_y`.")
611
+ else:
612
+ self.nemitt_y = nemitt_y
613
+ if beta_gamma_rel is None:
614
+ raise ValueError("Need to provide `beta_gamma_rel`.")
615
+ if twiss is None:
616
+ if twiss_upstream is None or twiss_downstream is None:
617
+ raise ValueError("Use either `twiss` or `twiss_upstream` and `twiss_downstream`.")
618
+ if name is None:
619
+ if len(twiss_upstream.name) > 1 or len(twiss_downstream.name) > 1:
620
+ raise ValueError("Need to provide `name` or twisses that are a single row each.")
621
+ tw_up = twiss_upstream
622
+ tw_down = twiss_downstream
623
+ else:
624
+ tw_up = twiss_upstream.rows[name]
625
+ tw_down = twiss_downstream.rows[name]
626
+ elif twiss_downstream is not None or twiss_downstream is not None:
627
+ raise ValueError("Use either `twiss` or `twiss_upstream` and `twiss_downstream`.")
628
+ elif name is None:
629
+ raise ValueError("When using `twiss`, need to provide the name as well.")
630
+ else:
631
+ tw_up = twiss.rows[name]
632
+ tw_down = twiss.rows[twiss.mask[[name]]+1]
633
+ if not np.isclose(tw_up.s[0] + self.length, tw_down.s[0]):
634
+ raise ValueError(f"Downstream twiss not compatible with length {self.length}m.")
635
+ self._optics = {
636
+ 'upstream': tw_up,
637
+ 'downstream': tw_down,
638
+ 'beta_gamma_rel': beta_gamma_rel
639
+ }
640
+ self._apply_optics()
641
+
642
+ @property
643
+ def nemitt_x(self):
644
+ if self._nemitt_x == 0:
645
+ return None
646
+ return self._nemitt_x
647
+
648
+ @nemitt_x.setter
649
+ def nemitt_x(self, val):
650
+ if val is None:
651
+ val = 0
652
+ elif val <= 0:
653
+ raise ValueError(f"The field `nemitt_x` should be positive, but got {val}.")
654
+ self._nemitt_x = val
655
+ self._apply_optics()
656
+
657
+ @property
658
+ def nemitt_y(self):
659
+ if self._nemitt_y == 0:
660
+ return None
661
+ return self._nemitt_y
662
+
663
+ @nemitt_y.setter
664
+ def nemitt_y(self, val):
665
+ if val is None:
666
+ val = 0
667
+ elif val <= 0:
668
+ raise ValueError(f"The field `nemitt_y` should be positive, but got {val}.")
669
+ self._nemitt_y = val
670
+ self._apply_optics()
671
+
672
+ @property
673
+ def emittance(self):
674
+ if self.nemitt_x is not None and self.nemitt_y is not None:
675
+ if np.isclose(self.nemitt_x, self.nemitt_y):
676
+ return self.nemitt_x
677
+ else:
678
+ return [self.nemitt_x, self.nemitt_y]
679
+
680
+ @emittance.setter
681
+ def emittance(self, val):
682
+ if val is None:
683
+ self._nemitt_x = 0
684
+ self._nemitt_y = 0
685
+ else:
686
+ if not hasattr(val, '__iter__'):
687
+ val = [val]
688
+ if len(val) == 1:
689
+ val = [val[0], val[0]]
690
+ assert len(val) == 2
691
+ if val[0] <= 0 or val[1] <= 0:
692
+ raise ValueError(f"The field `emittance` should be positive, but got {val}.")
693
+ self._nemitt_x = val[0]
694
+ self._nemitt_y = val[1]
695
+ self._apply_optics()
696
+
697
+ @property
698
+ def sigma(self):
699
+ if self.optics_ready():
700
+ betx = self.optics[self.align]['betx'][0]
701
+ bety = self.optics[self.align]['bety'][0]
702
+ sigma_x = np.sqrt(betx*self.nemitt_x/self.optics['beta_gamma_rel'])
703
+ sigma_y = np.sqrt(bety*self.nemitt_y/self.optics['beta_gamma_rel'])
704
+ if hasattr(self, '_cos_zL'):
705
+ sigma_L = np.sqrt((sigma_x*self._cos_zL)**2 + (sigma_y*self._sin_zL)**2)
706
+ sigma_R = np.sqrt((sigma_x*self._cos_zR)**2 + (sigma_y*self._sin_zR)**2)
707
+ return [sigma_L, sigma_R], [sigma_x, sigma_y]
708
+ else: # crystal
709
+ sigma = np.sqrt((sigma_x*self._cos_z)**2 + (sigma_y*self._sin_z)**2)
710
+ return sigma, [sigma_x, sigma_y]
711
+
712
+ @property
713
+ def co(self):
714
+ if self.optics_ready():
715
+ x = self.optics[self.align]['x'][0]
716
+ y = self.optics[self.align]['y'][0]
717
+ if hasattr(self, '_cos_zL'):
718
+ co_L = x*self._cos_zL + y*self._sin_zL
719
+ co_R = x*self._cos_zR + y*self._sin_zR
720
+ return [co_L, co_R], [x, y]
721
+ else: # crystal
722
+ co = x*self._cos_z + y*self._sin_z
723
+ return co, [x, y]
724
+
725
+ @property
726
+ def divergence(self):
727
+ if self.optics_ready():
728
+ alfx = self.optics[self.align]['alfx'][0]
729
+ alfy = self.optics[self.align]['alfy'][0]
730
+ betx = self.optics[self.align]['betx'][0]
731
+ bety = self.optics[self.align]['bety'][0]
732
+ divx = -np.sqrt(self.nemitt_x/self.optics['beta_gamma_rel']/betx)*alfx
733
+ divy = -np.sqrt(self.nemitt_y/self.optics['beta_gamma_rel']/bety)*alfy
734
+ if hasattr(self, '_cos_zL'):
735
+ if self.side != 'right':
736
+ return divx if abs(self.angle_L) < 1e-6 else divy
737
+ else:
738
+ return divx if abs(self.angle_R) < 1e-6 else divy
739
+ else:
740
+ return divx if abs(self.angle) < 1e-6 else divy
741
+
742
+ @property
743
+ def align(self):
744
+ if self._align == 0:
745
+ return 'upstream'
746
+ elif self._align == 1:
747
+ return 'downstream'
748
+ else:
749
+ raise ValueError(f"The attribute `align` can only be 'upstream' or "
750
+ +f"'downstream', but stored as {self._align}.")
751
+
752
+ @align.setter
753
+ def align(self, val):
754
+ if val == 'upstream':
755
+ self._align = 0
756
+ elif val == 'downstream':
757
+ self._align = 1
758
+ else:
759
+ raise ValueError(f"The attribute `align` can only be 'upstream' or "
760
+ +f"'downstream', but got {val}.")
761
+ self._apply_optics()
762
+
763
+
764
+ # Gap attributes
765
+ # ==============
766
+
767
+ @property
768
+ def gap(self):
769
+ if self.gap_L is None and self.gap_R is None:
770
+ return None
771
+ elif self.gap_R is not None and self.gap_L == -self.gap_R:
772
+ return self.gap_L
773
+ else:
774
+ return [self.gap_L, self.gap_R]
775
+
776
+ @gap.setter
777
+ def gap(self, val):
778
+ if not hasattr(val, '__iter__') or len(val) == 1:
779
+ val = val[0] if hasattr(val, '__iter__') else val
780
+ if self.side == 'left':
781
+ self.gap_L = val
782
+ elif self.side == 'right':
783
+ # self.gap_R = -val if val is not None else None
784
+ self.gap_R = val
785
+ else:
786
+ self.gap_L = val
787
+ self.gap_R = -val if val is not None else None
788
+ return
789
+ elif len(val) == 2:
790
+ if not hasattr(val[0], '__iter__') \
791
+ and not hasattr(val[1], '__iter__'):
792
+ if val[0] is not None and val[1] is not None:
793
+ if val[0] <= val[1]:
794
+ raise ValueError(f"The attribute `gap_L` should be larger "
795
+ + f"than `gap_R` but got {val}.")
796
+ self.gap_L = val[0]
797
+ self.gap_R = val[1]
798
+ return
799
+ # If we got here, val is incompatible
800
+ raise ValueError(f"The attribute `gap` should be of the form `gap` or "
801
+ + f"`[gap_L, gap_R]`, but got {val}.")
802
+
803
+ @property
804
+ def gap_L(self):
805
+ if self.side != 'right':
806
+ if self.optics_ready() and self.jaw_L is not None:
807
+ return round((self.jaw_L - self.co[0][0])/self.sigma[0][0], 6)
808
+ elif not self._gap_L_set_manually():
809
+ return None
810
+ else:
811
+ return self._gap_L
812
+
813
+ @gap_L.setter
814
+ def gap_L(self, val):
815
+ if val is None:
816
+ val = OPEN_GAP
817
+ self.jaw_L = None
818
+ elif val <= 0:
819
+ raise ValueError(f"The field `gap_L` should be positive, but got {val}.")
820
+ self._gap_L = val
821
+ self._apply_optics(only_L=True)
298
822
 
299
823
  @property
300
- def reference_center(self):
301
- return [self.ref_x, self.ref_y]
824
+ def gap_R(self):
825
+ if self.side != 'left':
826
+ if self.optics_ready() and self.jaw_R is not None:
827
+ return round((self.jaw_R - self.co[0][1])/self.sigma[0][1], 6)
828
+ elif not self._gap_R_set_manually():
829
+ return None
830
+ else:
831
+ return self._gap_R
832
+
833
+ @gap_R.setter
834
+ def gap_R(self, val):
835
+ if val is None:
836
+ val = -OPEN_GAP
837
+ self.jaw_R = None
838
+ elif val >= 0:
839
+ raise ValueError(f"The field `gap_R` should be negative, but got {val}.")
840
+ self._gap_R = val
841
+ self._apply_optics(only_R=True)
842
+
843
+ @property
844
+ def gap_LU(self):
845
+ if self.gap_L is not None and self.optics_ready():
846
+ return round(self._gap_L - self._sin_yL * self.length / 2. / self.sigma[0][0], 6)
302
847
 
303
- @reference_center.setter
304
- def reference_center(self, ref):
305
- _set_LR(self, 'ref', ref, name_L='_x', name_R='_y')
848
+ @property
849
+ def gap_LD(self):
850
+ if self.gap_L is not None and self.optics_ready():
851
+ return round(self._gap_L + self._sin_yL * self.length / 2. / self.sigma[0][0], 6)
306
852
 
307
853
  @property
308
- def length(self):
309
- return (self.inactive_front + self.active_length + self.inactive_back)
854
+ def gap_RU(self):
855
+ if self.gap_R is not None and self.optics_ready():
856
+ return round(self._gap_R - self._sin_yR * self.length / 2. / self.sigma[0][1], 6)
857
+
858
+ @property
859
+ def gap_RD(self):
860
+ if self.gap_R is not None and self.optics_ready():
861
+ return round(self._gap_R + self._sin_yR * self.length / 2. / self.sigma[0][1], 6)
862
+
863
+ def _gap_L_set_manually(self):
864
+ return not np.isclose(self._gap_L, OPEN_GAP)
310
865
 
311
- @length.setter
312
- def length(self, val):
313
- raise ValueError("The parameter 'length' can only be set at initialisation. "
314
- + "Use 'active_length', 'inactive_front', and/or 'inactive_back'.")
866
+ def _gap_R_set_manually(self):
867
+ return not np.isclose(self._gap_R, -OPEN_GAP)
868
+
869
+ def _apply_optics(self, only_L=False, only_R=False):
870
+ if self.optics_ready():
871
+ # Only if we have set a value for the gap manually, this needs to be updated
872
+ if self._gap_L_set_manually() and not only_R:
873
+ self.jaw_L = self._gap_L * self.sigma[0][0] + self.co[0][0]
874
+ if self._gap_R_set_manually() and not only_L:
875
+ self.jaw_R = self._gap_R * self.sigma[0][1] + self.co[0][1]
876
+
877
+
878
+ # Other attributes
879
+ # ================
315
880
 
316
881
  @property
317
882
  def side(self):
@@ -319,55 +884,94 @@ class BaseCollimator(xt.BeamElement):
319
884
  return 'both'
320
885
  elif self._side == 1:
321
886
  return 'left'
322
- elif self._side == 2:
887
+ elif self._side == -1:
323
888
  return 'right'
324
889
 
325
890
  @side.setter
326
- def side(self, side):
327
- self._side = _side_setter(side)
891
+ def side(self, val):
892
+ if isinstance(val, str):
893
+ if val.lower() == 'both' or val == '+-' or val == '-+':
894
+ self._side = 0
895
+ return
896
+ elif val.lower() == 'left' or val.lower() == 'l' or val == '+':
897
+ self._side = 1
898
+ self.gap_R = None
899
+ return
900
+ elif val.lower() == 'right' or val.lower() == 'r' or val == '-':
901
+ self._side = -1
902
+ self.gap_L = None
903
+ return
904
+ raise ValueError(f"Unkown setting {val} for 'side'! Choose from "
905
+ + f"('left', 'L', '+'), ('right', 'R', '-'), or ('both', '+-').")
328
906
 
329
- # TODO: tilts are in rad! Do we want that? It's a bit inconsistent with angle which is in deg...
330
- # ==============================================================================================
331
907
  @property
332
- def tilt_L(self):
333
- # return round(np.arctan2(self.sin_yL, self.cos_yL) * (180.0 / np.pi), 10)
334
- return round(np.arctan2(self.sin_yL, self.cos_yL), 10)
335
-
336
- @tilt_L.setter
337
- def tilt_L(self, tilt_L):
338
- # anglerad_L = tilt_L / 180. * np.pi
339
- anglerad_L = tilt_L
340
- self.sin_yL = np.sin(anglerad_L)
341
- self.cos_yL = np.cos(anglerad_L)
342
- self.tan_yL = np.tan(anglerad_L)
908
+ def active_length(self):
909
+ raise ValueError("`active_length`is deprecated. Please use `length`.")
343
910
 
344
911
  @property
345
- def tilt_R(self):
346
- # return round(np.arctan2(self.sin_yR, self.cos_yR) * (180.0 / np.pi), 10)
347
- return round(np.arctan2(self.sin_yR, self.cos_yR), 10)
348
-
349
- @tilt_R.setter
350
- def tilt_R(self, tilt_R):
351
- # anglerad_R = tilt_R / 180. * np.pi
352
- anglerad_R = tilt_R
353
- self.sin_yR = np.sin(anglerad_R)
354
- self.cos_yR = np.cos(anglerad_R)
355
- self.tan_yR = np.tan(anglerad_R)
912
+ def inactive_front(self):
913
+ raise ValueError("`inactive_front`is deprecated. Collimators now only "
914
+ + "contain their active length (implemented as `length`).")
356
915
 
357
916
  @property
358
- def tilt(self):
359
- return self.tilt_L if self.tilt_L==self.tilt_R else [self.tilt_L, self.tilt_R]
360
-
361
- @tilt.setter
362
- def tilt(self, tilt):
363
- self.sin_yL, self.cos_yL, self.tan_yL, self.sin_yR, self.cos_yR, self.tan_yR = _angle_setter(tilt, rad=True)
917
+ def inactive_back(self):
918
+ raise ValueError("`inactive_back`is deprecated. Collimators now only "
919
+ + "contain their active length (implemented as `length`).")
920
+
921
+
922
+ # Methods
923
+ # =======
924
+
925
+ def _verify_consistency(self):
926
+ BaseBlock._verify_consistency(self)
927
+ # Verify angles
928
+ if abs(self.angle_L - self.angle_R) >= 90.:
929
+ raise ValueError("Angles of both jaws differ more than 90 degrees!")
930
+ ang = abs(np.arccos(self._cos_zL))
931
+ ang = np.pi - ang if ang > np.pi/2 else ang
932
+ assert np.isclose(ang, abs(np.arcsin(self._sin_zL)))
933
+ ang = abs(np.arccos(self._cos_zR))
934
+ ang = np.pi - ang if ang > np.pi/2 else ang
935
+ assert np.isclose(ang, abs(np.arcsin(self._sin_zR)))
936
+ if np.isclose(self.angle_L, self.angle_R):
937
+ assert self._jaws_parallel == True
938
+ assert np.isclose(self._sin_zL, self._sin_zR)
939
+ assert np.isclose(self._cos_zL, self._cos_zR)
940
+ assert np.isclose(self._sin_zDiff, 0.)
941
+ assert np.isclose(self._cos_zDiff, 1.)
942
+ else:
943
+ assert self._jaws_parallel == False
944
+ assert np.isclose(self._sin_zDiff, self._cos_zL*self._sin_zR - self._sin_zL*self._cos_zR)
945
+ assert np.isclose(self._cos_zDiff, self._cos_zL*self._cos_zR + self._sin_zL*self._sin_zR)
946
+ if self.side == 'both' and abs(self.tilt_L - self.tilt_R) >= 90.:
947
+ raise ValueError("Tilts of both jaws differ more than 90 degrees!")
948
+ if self.side != 'right':
949
+ ang = abs(np.arccos(self._cos_yL))
950
+ ang = np.pi - ang if ang > np.pi/2 else ang
951
+ assert np.isclose(ang, abs(np.arcsin(self._sin_yL)))
952
+ assert np.isclose(self._sin_yL/self._cos_yL, self._tan_yL)
953
+ if self.side != 'left':
954
+ ang = abs(np.arccos(self._cos_yR))
955
+ ang = np.pi - ang if ang > np.pi/2 else ang
956
+ assert np.isclose(ang, abs(np.arcsin(self._sin_yR)))
957
+ assert np.isclose(self._sin_yR/self._cos_yR, self._tan_yR)
958
+
959
+ # Verify bools
960
+ assert self._side in [-1, 1, 0]
961
+ assert isinstance(self._jaws_parallel, bool) or self._jaws_parallel in [0, 1]
364
962
 
365
963
  def jaw_func(self, pos):
366
964
  positions = ['LU', 'RU', 'LD', 'RD']
965
+ if pos[0] == 'L':
966
+ other_pos = 'R'
967
+ else:
968
+ other_pos = 'L'
969
+ point_x = ((getattr(self, 'jaw_' + pos[0]) * getattr(self, 'cos_z' + pos[0])
970
+ + getattr(self, 'jaw_' + other_pos) * getattr(self, 'cos_z' + other_pos))/2)
971
+ point_y = ((getattr(self, 'jaw_' + pos[0]) * getattr(self, 'sin_z' + pos[0])
972
+ + getattr(self, 'jaw_' + other_pos) * getattr(self, 'sin_z' + other_pos))/2)
367
973
  if not pos in positions:
368
974
  raise ValueError(f"Parameter {pos} needs to be one of {positions}!")
369
- point_x = getattr(self, 'ref_x')
370
- point_y = getattr(self, 'ref_y')
371
975
  sinz = getattr(self, 'sin_z' + pos[0])
372
976
  cosz = getattr(self, 'cos_z' + pos[0])
373
977
  # Shift to the jaw, whose location is given as the shortest distance:
@@ -376,32 +980,383 @@ class BaseCollimator(xt.BeamElement):
376
980
  return lambda t: (point_x - t*sinz, point_y + t*cosz)
377
981
 
378
982
 
379
- def _side_setter(val):
380
- if isinstance(val, str):
381
- if val.lower() == 'both' or val == '+-' or val == '-+':
382
- return 0
383
- elif val.lower() == 'left' or val.lower() == 'l' or val == '+':
384
- return 1
385
- elif val.lower() == 'right' or val.lower() == 'r' or val == '-':
386
- return 2
387
- raise ValueError(f"Unkown setting {val} for 'side'! Choose from "
388
- + f"('left', 'L', '+'), ('right', 'R', '-'), or ('both', '+-').")
389
-
390
- def _angle_setter(val, rad=False):
391
- if not hasattr(val, '__iter__'):
392
- val = [val]
393
- conversion = 1 if rad else np.pi / 180.
394
- if isinstance(val, str):
395
- raise ValueError(f"Error in setting angle: not a number!")
396
- elif len(val) == 2:
397
- anglerad_L = val[0] * conversion
398
- anglerad_R = val[1] * conversion
399
- elif len(val) == 1:
400
- anglerad_L = val[0] * conversion
401
- anglerad_R = val[0] * conversion
402
- else:
403
- raise ValueError(f"Error in setting angle: must have one or two (L, R) values!")
404
- return np.sin(anglerad_L), np.cos(anglerad_L), np.tan(anglerad_L), \
405
- np.sin(anglerad_R), np.cos(anglerad_R), np.tan(anglerad_R)
983
+ class BaseCrystal(BaseBlock):
984
+ _xofields = {**BaseBlock._xofields,
985
+ # Collimator angle
986
+ '_sin_z': xo.Float64,
987
+ '_cos_z': xo.Float64,
988
+ # Jaw corners (this is the x-coordinate in the rotated frame)
989
+ '_jaw_U': xo.Float64,
990
+ # Tilts (not superfluous)
991
+ '_sin_y': xo.Float64,
992
+ '_cos_y': xo.Float64,
993
+ '_tan_y': xo.Float64,
994
+ # Other
995
+ '_side': xo.Int8,
996
+ # These are not used in C, but need to be an xofield to get them in the to_dict:
997
+ '_align': xo.Int8,
998
+ '_gap': xo.Float64,
999
+ '_nemitt_x': xo.Float64,
1000
+ '_nemitt_y': xo.Float64,
1001
+ # Crystal specific
1002
+ '_bending_radius': xo.Float64,
1003
+ '_bending_angle': xo.Float64,
1004
+ 'width': xo.Float64,
1005
+ 'height': xo.Float64
1006
+ # 'thick': xo.Float64
1007
+ }
1008
+
1009
+ isthick = BaseBlock.isthick
1010
+ allow_track = BaseBlock.allow_track
1011
+ behaves_like_drift = BaseBlock.behaves_like_drift
1012
+ skip_in_loss_location_refinement = BaseBlock.skip_in_loss_location_refinement
1013
+ allow_double_sided = False
1014
+
1015
+ _skip_in_to_dict = [*BaseBlock._skip_in_to_dict, *[f for f in _xofields if f.startswith('_')]]
1016
+ _store_in_to_dict = [*BaseBlock._store_in_to_dict, 'angle', 'jaw', 'tilt', 'gap', 'side', 'align', 'emittance',
1017
+ 'bending_radius', 'bending_angle']
1018
+
1019
+ _depends_on = [BaseCollimator]
1020
+
1021
+ _internal_record_class = BaseBlock._internal_record_class
1022
+
1023
+ # This is an abstract class and cannot be instantiated
1024
+ def __new__(cls, *args, **kwargs):
1025
+ if cls == BaseCrystal:
1026
+ raise Exception("Abstract class `BaseCrystal` cannot be instantiated!")
1027
+ instance = super().__new__(cls)
1028
+ return instance
1029
+
1030
+ def __init__(self, **kwargs):
1031
+ to_assign = {}
1032
+ if '_xobject' not in kwargs:
1033
+ # Set side
1034
+ to_assign['side'] = kwargs.pop('side', 'left')
1035
+
1036
+ # Set angle
1037
+ to_assign['angle'] = kwargs.pop('angle', 0)
1038
+
1039
+ # Set jaw
1040
+ if 'jaw' in kwargs:
1041
+ for key in ['jaw_U', 'jaw_D', 'gap']:
1042
+ if key in kwargs:
1043
+ raise ValueError(f"Cannot use both `jaw` and `{key}`!")
1044
+ to_assign['jaw'] = kwargs.pop('jaw')
1045
+ elif 'jaw_D' in kwargs:
1046
+ for key in ['tilt', 'gap']:
1047
+ if key in kwargs:
1048
+ raise ValueError(f"Cannot use both `jaw_D` with `{key}`!")
1049
+ if not 'jaw_U' in kwargs:
1050
+ raise ValueError("Need to provide `jaw_U` when setting `jaw_D`!")
1051
+ to_assign['jaw_U'] = kwargs.pop('jaw_U')
1052
+ to_assign['jaw_D'] = kwargs.pop('jaw_D')
1053
+ elif 'jaw_U' in kwargs:
1054
+ if 'gap' in kwargs:
1055
+ raise ValueError(f"Cannot use both `jaw_U` and `gap`!")
1056
+ to_assign['jaw_U'] = kwargs.pop('jaw_U')
1057
+ # TODO: correct sign if right-sided
1058
+ kwargs.setdefault('_jaw_U', OPEN_JAW)
1059
+
1060
+ # Set gap
1061
+ if 'gap' in kwargs:
1062
+ for key in ['jaw', 'jaw_U', 'jaw_D']:
1063
+ if key in kwargs:
1064
+ raise ValueError(f"Cannot use both `gap` and `{key}`!")
1065
+ to_assign['gap'] = kwargs.pop('gap')
1066
+ # TODO: correct sign if right-sided
1067
+ kwargs.setdefault('_gap', OPEN_GAP)
1068
+
1069
+ # Set tilt
1070
+ if 'jaw_D' not in kwargs:
1071
+ to_assign['tilt'] = kwargs.pop('tilt', 0)
1072
+
1073
+ # Set others
1074
+ to_assign['align'] = kwargs.pop('align', 'upstream')
1075
+ to_assign['emittance'] = kwargs.pop('emittance', None)
1076
+ kwargs.setdefault('active', True)
1077
+
1078
+ # Set crystal specific
1079
+ if 'bending_angle' in kwargs:
1080
+ if 'bending_radius' in kwargs:
1081
+ raise ValueError("Need to choose between 'bending_radius' and 'bending_angle'!")
1082
+ to_assign['bending_angle'] = kwargs.pop('bending_angle')
1083
+ else:
1084
+ to_assign['bending_radius'] = kwargs.pop('bending_radius', 1)
1085
+ kwargs.setdefault('width', 0)
1086
+ kwargs.setdefault('height', 0)
1087
+
1088
+ xt.BeamElement.__init__(self, **kwargs)
1089
+ # Careful: non-xofields are not passed correctly between copy's / to_dict. This messes with flags etc..
1090
+ # We also have to manually initialise them for xobject generation
1091
+ if not hasattr(self, '_optics'):
1092
+ self._optics = None
1093
+ for key, val in to_assign.items():
1094
+ setattr(self, key, val)
1095
+ if self.side == 'right':
1096
+ if np.isclose(self._jaw_U, OPEN_JAW):
1097
+ self._jaw_U *= -1
1098
+ if np.isclose(self._gap, OPEN_GAP):
1099
+ self._gap *= -1
1100
+ self._verify_consistency()
1101
+
1102
+
1103
+ # Main crystal angle
1104
+ # ==================
1105
+
1106
+ @property
1107
+ def angle(self):
1108
+ return round(np.rad2deg(np.arctan2(self._sin_z, self._cos_z)), 10)
1109
+
1110
+ @angle.setter
1111
+ def angle(self, val):
1112
+ self._sin_z = np.sin(np.deg2rad(val))
1113
+ self._cos_z = np.cos(np.deg2rad(val))
1114
+ self._apply_optics()
1115
+
406
1116
 
1117
+ # Jaw attributes
1118
+ # ==============
1119
+
1120
+ @property
1121
+ def jaw(self):
1122
+ return self.jaw_U
1123
+
1124
+ @jaw.setter
1125
+ def jaw(self, val):
1126
+ if val is None:
1127
+ val = self._side*OPEN_JAW
1128
+ self.jaw_U = val
1129
+
1130
+ @property
1131
+ def jaw_U(self):
1132
+ if not np.isclose(self._jaw_U, self._side*OPEN_JAW, atol=1.e-10): # open position
1133
+ return self._jaw_U
1134
+
1135
+ @jaw_U.setter # This moves both jaw_LU and jaw_LD in parallel
1136
+ def jaw_U(self, val):
1137
+ if val is None:
1138
+ raise ValueError("Cannot set corner to None! Use open_jaws() or set jaw to None.")
1139
+ self._jaw_U = val
1140
+ self._update_gaps()
1141
+
1142
+ @property
1143
+ def jaw_D(self):
1144
+ if not np.isclose(self._jaw_U, self._side*OPEN_JAW, atol=1.e-10): # open position
1145
+ length = self.length
1146
+ if self._side*self.bending_radius < 0:
1147
+ # Correction for inner corner point
1148
+ length -= self.width*np.sin(abs(self._bending_angle))
1149
+ shift = np.tan(self._bending_angle/2)*self._cos_y + self._sin_y
1150
+ return self._jaw_U + length*shift
1151
+
1152
+ @jaw_D.setter # This moves both jaw_LU and jaw_LD in parallel
1153
+ def jaw_D(self, val):
1154
+ if val is None:
1155
+ self.tilt = 0
1156
+ else:
1157
+ shift = (val - self._jaw_U )/self.length * np.cos(self._bending_angle/2)
1158
+ self._sin_y = shift*np.cos(self._bending_angle/2)
1159
+ self._sin_y -= np.sin(self._bending_angle/2)*np.sqrt(1 - shift**2)
1160
+ self._cos_y = np.sqrt(1 - self._sin_y**2)
1161
+ self._tan_y = self._sin_y / self._cos_y
1162
+ self._update_gaps()
1163
+
1164
+ def open_jaws(self, keep_tilts=False):
1165
+ self.jaw = None
1166
+ if not keep_tilts:
1167
+ self.tilt = 0
1168
+
1169
+ def _update_gaps(self):
1170
+ # If we had set a value for the gap manually, this needs to be updated
1171
+ # as well after setting the jaw
1172
+ if self._gap_set_manually():
1173
+ self._gap = self.gap
1174
+
1175
+
1176
+ # Tilt attributes
1177
+ # ===============
1178
+
1179
+ # TODO: tilts are in rad! Do we want that? It's a bit inconsistent with angle which is in deg...
1180
+
1181
+ @property
1182
+ def tilt(self):
1183
+ return round(np.arctan2(self._sin_y, self._cos_y), 10)
1184
+
1185
+ @tilt.setter # This assumes jaw_U remains fixed (hence jaw_D changes)
1186
+ def tilt(self, val):
1187
+ if self.side == 'left':
1188
+ if val < min(0, self.bending_angle/2):
1189
+ print("Warning: Setting a negative tilt does not preserve the hierarchy, as the "
1190
+ + "crystal tightens towards the beam.")
1191
+ elif self.side == 'right':
1192
+ if val > min(0, -self.bending_angle/2):
1193
+ print("Warning: Setting a positive tilt does not preserve the hierarchy, as the "
1194
+ + "crystal tightens towards the beam.")
1195
+ self._sin_y = np.sin(val)
1196
+ self._cos_y = np.cos(val)
1197
+ self._tan_y = np.tan(val)
1198
+
1199
+
1200
+ # Optics
1201
+ # ======
1202
+
1203
+ @property
1204
+ def optics(self):
1205
+ return self._optics
1206
+
1207
+ def optics_ready(self):
1208
+ return BaseCollimator.optics_ready(self)
1209
+
1210
+ def assign_optics(self, *, nemitt_x=None, nemitt_y=None, beta_gamma_rel=None, name=None, twiss=None,
1211
+ twiss_upstream=None, twiss_downstream=None):
1212
+ return BaseCollimator.assign_optics(self, nemitt_x=nemitt_x, nemitt_y=nemitt_y,
1213
+ beta_gamma_rel=beta_gamma_rel, name=name, twiss=twiss,
1214
+ twiss_upstream=twiss_upstream, twiss_downstream=twiss_downstream)
1215
+
1216
+ @property
1217
+ def nemitt_x(self):
1218
+ return BaseCollimator.nemitt_x.fget(self)
1219
+
1220
+ @nemitt_x.setter
1221
+ def nemitt_x(self, val):
1222
+ BaseCollimator.nemitt_x.fset(self, val)
1223
+
1224
+ @property
1225
+ def nemitt_y(self):
1226
+ return BaseCollimator.nemitt_y.fget(self)
1227
+
1228
+ @nemitt_y.setter
1229
+ def nemitt_y(self, val):
1230
+ BaseCollimator.nemitt_y.fset(self, val)
1231
+
1232
+ @property
1233
+ def emittance(self):
1234
+ return BaseCollimator.emittance.fget(self)
1235
+
1236
+ @emittance.setter
1237
+ def emittance(self, val):
1238
+ BaseCollimator.emittance.fset(self, val)
1239
+
1240
+ @property
1241
+ def sigma(self):
1242
+ return BaseCollimator.sigma.fget(self)
1243
+
1244
+ @property
1245
+ def co(self):
1246
+ return BaseCollimator.co.fget(self)
1247
+
1248
+ @property
1249
+ def divergence(self):
1250
+ return BaseCollimator.divergence.fget(self)
1251
+
1252
+ @property
1253
+ def align(self):
1254
+ return BaseCollimator.align.fget(self)
1255
+
1256
+ @align.setter
1257
+ def align(self, val):
1258
+ if val != 'upstream':
1259
+ raise NotImplementedError("Crystals cannot be aligned to the downstream optics!")
1260
+ BaseCollimator.align.fset(self, val)
1261
+
1262
+ def align_to_beam_divergence(self):
1263
+ if not self.optics_ready():
1264
+ raise ValueError("Optics not assigned! Cannot align to beam divergence.")
1265
+ if self.gap is None:
1266
+ raise ValueError("Need to set `gap` to align to beam divergence.")
1267
+ self.tilt = self.divergence * self.gap
1268
+
1269
+
1270
+ # Gap attributes
1271
+ # ==============
1272
+
1273
+ @property
1274
+ def gap(self):
1275
+ if self.optics_ready() and self.jaw_U is not None:
1276
+ return round((self.jaw_U - self.co[0])/self.sigma[0], 6)
1277
+ elif not self._gap_set_manually():
1278
+ return None
1279
+ else:
1280
+ return self._gap
1281
+
1282
+ # Gap is always positive, irrespective of the side
1283
+ @gap.setter
1284
+ def gap(self, val):
1285
+ if val is None:
1286
+ val = OPEN_GAP
1287
+ self.jaw = None
1288
+ if hasattr(val, '__iter__'):
1289
+ raise ValueError("The attribute `gap` should be a single value, not a list.")
1290
+ if val <= 0:
1291
+ raise ValueError(f"The field `gap` should be positive, but got {val}.")
1292
+ self._gap = val
1293
+ self._apply_optics()
1294
+
1295
+ def _gap_set_manually(self):
1296
+ return not np.isclose(self._gap, OPEN_GAP)
1297
+
1298
+ def _apply_optics(self):
1299
+ if self.optics_ready():
1300
+ # Only if we have set a value for the gap manually, this needs to be updated
1301
+ if self._gap_set_manually():
1302
+ self.jaw_U = self._gap * self.sigma[0] + self.co[0]
1303
+
1304
+
1305
+ # Other attributes
1306
+ # ================
1307
+
1308
+ @property
1309
+ def bending_radius(self):
1310
+ return self._bending_radius
1311
+
1312
+ @bending_radius.setter
1313
+ def bending_radius(self, bending_radius):
1314
+ bending_angle = np.arcsin(self.length/bending_radius)
1315
+ if abs(bending_angle) > np.pi/2:
1316
+ raise ValueError("Bending angle cannot be larger than 90 degrees!")
1317
+ self._bending_radius = bending_radius
1318
+ self._bending_angle = bending_angle
1319
+
1320
+ @property
1321
+ def bending_angle(self):
1322
+ return self._bending_angle
1323
+
1324
+ @bending_angle.setter
1325
+ def bending_angle(self, bending_angle):
1326
+ if abs(bending_angle) > np.pi/2:
1327
+ raise ValueError("Bending angle cannot be larger than 90 degrees!")
1328
+ self._bending_angle = bending_angle
1329
+ self._bending_radius = self.length / np.sin(bending_angle)
1330
+
1331
+ @property
1332
+ def side(self):
1333
+ return BaseCollimator.side.fget(self)
1334
+
1335
+ @side.setter
1336
+ def side(self, val):
1337
+ temp = self._side
1338
+ BaseCollimator.side.fset(self, val)
1339
+ if self._side == 0:
1340
+ self._side = temp
1341
+ raise ValueError("Crystal cannot be two-sided! Please set `side` "
1342
+ + "to 'left' or 'right'.")
1343
+
1344
+
1345
+ # Methods
1346
+ # =======
1347
+
1348
+ def _verify_consistency(self):
1349
+ BaseBlock._verify_consistency(self)
1350
+ # Verify angles
1351
+ ang = abs(np.arccos(self._cos_z))
1352
+ ang = np.pi - ang if ang > np.pi/2 else ang
1353
+ assert np.isclose(ang, abs(np.arcsin(self._sin_z)))
1354
+ ang = abs(np.arccos(self._cos_y))
1355
+ ang = np.pi - ang if ang > np.pi/2 else ang
1356
+ assert np.isclose(ang, abs(np.arcsin(self._sin_y)))
1357
+ assert np.isclose(self._sin_y/self._cos_y, self._tan_y)
1358
+ # Verify bools
1359
+ assert self._side in [-1, 1]
1360
+ # Crystal specific
1361
+ assert np.isclose(self._bending_angle, np.arcsin(self.length/self._bending_radius))
407
1362