unitx 0.17.2__tar.gz → 0.17.4__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 (252) hide show
  1. {unitx-0.17.2 → unitx-0.17.4}/.github/consumers/arrays.py +100 -6
  2. {unitx-0.17.2 → unitx-0.17.4}/.github/consumers/scalars.py +15 -0
  3. {unitx-0.17.2 → unitx-0.17.4}/CHANGELOG.md +50 -0
  4. {unitx-0.17.2 → unitx-0.17.4}/CONTRIBUTING.md +7 -4
  5. {unitx-0.17.2 → unitx-0.17.4}/PKG-INFO +1 -1
  6. {unitx-0.17.2 → unitx-0.17.4}/docs/architecture.md +6 -0
  7. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/calculus.md +39 -4
  8. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/methods.md +12 -2
  9. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy.md +7 -6
  10. {unitx-0.17.2 → unitx-0.17.4}/docs/stdlib.md +33 -6
  11. {unitx-0.17.2 → unitx-0.17.4}/docs/typing.md +122 -14
  12. {unitx-0.17.2 → unitx-0.17.4}/docs/units.md +17 -0
  13. {unitx-0.17.2 → unitx-0.17.4}/pyproject.toml +1 -1
  14. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/points.py +4 -4
  15. unitx-0.17.4/src/unitx/_numerics/methods.py +31 -0
  16. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/numpy.py +36 -17
  17. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/numpy_typing.py +40 -3
  18. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/affinequantity.py +60 -38
  19. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/exceptions.py +11 -1
  20. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/logquantity.py +60 -38
  21. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/math.py +142 -61
  22. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/quantity.py +40 -62
  23. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/quantity.pyi +647 -5
  24. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/unitregistry.py +2 -2
  25. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_array_methods.py +69 -1
  26. unitx-0.17.4/tests/numpy/test_dtype_contracts.py +74 -0
  27. unitx-0.17.4/tests/numpy/test_inference_contracts.py +69 -0
  28. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_math_numpy.py +20 -0
  29. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_scientific.py +1 -1
  30. unitx-0.17.4/tests/numpy/test_point_calculus.py +110 -0
  31. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_array_methods.py +19 -2
  32. unitx-0.17.4/tests/numpy_typing/check_lm_inference.py +109 -0
  33. unitx-0.17.4/tests/numpy_typing/check_math.py +35 -0
  34. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_numpy.py +19 -13
  35. unitx-0.17.4/tests/numpy_typing/check_precision.py +48 -0
  36. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_representation.py +4 -5
  37. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_representation.py +7 -1
  38. {unitx-0.17.2 → unitx-0.17.4}/tests/stdlib/test_math.py +22 -0
  39. unitx-0.17.4/tests/test_exceptions.py +36 -0
  40. {unitx-0.17.2 → unitx-0.17.4}/tests/test_typing.py +1 -1
  41. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_impedance_example.py +3 -3
  42. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_math_time.py +4 -0
  43. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_quantity.py +2 -2
  44. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_representation.py +31 -4
  45. unitx-0.17.2/tests/numpy_typing/check_math.py +0 -14
  46. {unitx-0.17.2 → unitx-0.17.4}/.github/consumers/plots.py +0 -0
  47. {unitx-0.17.2 → unitx-0.17.4}/.github/consumers/pyrightconfig.json +0 -0
  48. {unitx-0.17.2 → unitx-0.17.4}/.github/consumers/scipy_support.py +0 -0
  49. {unitx-0.17.2 → unitx-0.17.4}/.github/workflows/ci.yml +0 -0
  50. {unitx-0.17.2 → unitx-0.17.4}/.github/workflows/pages.yml +0 -0
  51. {unitx-0.17.2 → unitx-0.17.4}/.github/workflows/release.yml +0 -0
  52. {unitx-0.17.2 → unitx-0.17.4}/.gitignore +0 -0
  53. {unitx-0.17.2 → unitx-0.17.4}/.readthedocs.yaml +0 -0
  54. {unitx-0.17.2 → unitx-0.17.4}/LICENSE +0 -0
  55. {unitx-0.17.2 → unitx-0.17.4}/README.md +0 -0
  56. {unitx-0.17.2 → unitx-0.17.4}/benchmarks/README.md +0 -0
  57. {unitx-0.17.2 → unitx-0.17.4}/benchmarks/numpy_writes.py +0 -0
  58. {unitx-0.17.2 → unitx-0.17.4}/benchmarks/scalars.py +0 -0
  59. {unitx-0.17.2 → unitx-0.17.4}/conftest.py +0 -0
  60. {unitx-0.17.2 → unitx-0.17.4}/docs/README.md +0 -0
  61. {unitx-0.17.2 → unitx-0.17.4}/docs/affine.md +0 -0
  62. {unitx-0.17.2 → unitx-0.17.4}/docs/catalogues/README.md +0 -0
  63. {unitx-0.17.2 → unitx-0.17.4}/docs/catalogues/cgs.md +0 -0
  64. {unitx-0.17.2 → unitx-0.17.4}/docs/catalogues/imperial.md +0 -0
  65. {unitx-0.17.2 → unitx-0.17.4}/docs/catalogues/si.md +0 -0
  66. {unitx-0.17.2 → unitx-0.17.4}/docs/custom-units.md +0 -0
  67. {unitx-0.17.2 → unitx-0.17.4}/docs/display.md +0 -0
  68. {unitx-0.17.2 → unitx-0.17.4}/docs/images/blackbody.svg +0 -0
  69. {unitx-0.17.2 → unitx-0.17.4}/docs/javascripts/mermaid.mjs +0 -0
  70. {unitx-0.17.2 → unitx-0.17.4}/docs/logarithmic.md +0 -0
  71. {unitx-0.17.2 → unitx-0.17.4}/docs/matplotlib.md +0 -0
  72. {unitx-0.17.2 → unitx-0.17.4}/docs/migration.md +0 -0
  73. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/compatibility.md +0 -0
  74. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/contractions.md +0 -0
  75. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/creation.md +0 -0
  76. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/fft.md +0 -0
  77. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/histograms.md +0 -0
  78. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/indexing.md +0 -0
  79. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/linalg.md +0 -0
  80. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/selection.md +0 -0
  81. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/statistics.md +0 -0
  82. {unitx-0.17.2 → unitx-0.17.4}/docs/numpy/writes.md +0 -0
  83. {unitx-0.17.2 → unitx-0.17.4}/docs/representation.md +0 -0
  84. {unitx-0.17.2 → unitx-0.17.4}/docs/requirements.txt +0 -0
  85. {unitx-0.17.2 → unitx-0.17.4}/docs/scalars.md +0 -0
  86. {unitx-0.17.2 → unitx-0.17.4}/docs/scipy.md +0 -0
  87. {unitx-0.17.2 → unitx-0.17.4}/docs/serialization.md +0 -0
  88. {unitx-0.17.2 → unitx-0.17.4}/docs/statistics.md +0 -0
  89. {unitx-0.17.2 → unitx-0.17.4}/examples/README.md +0 -0
  90. {unitx-0.17.2 → unitx-0.17.4}/examples/arrhenius_fit.ipynb +0 -0
  91. {unitx-0.17.2 → unitx-0.17.4}/examples/blackbody.ipynb +0 -0
  92. {unitx-0.17.2 → unitx-0.17.4}/examples/damped_oscillator.ipynb +0 -0
  93. {unitx-0.17.2 → unitx-0.17.4}/examples/gaussian_wavepacket.ipynb +0 -0
  94. {unitx-0.17.2 → unitx-0.17.4}/examples/impedance.md +0 -0
  95. {unitx-0.17.2 → unitx-0.17.4}/examples/impedance.py +0 -0
  96. {unitx-0.17.2 → unitx-0.17.4}/examples/optical_attenuation.ipynb +0 -0
  97. {unitx-0.17.2 → unitx-0.17.4}/examples/pendulum_uncertainty.ipynb +0 -0
  98. {unitx-0.17.2 → unitx-0.17.4}/examples/smith_chart.ipynb +0 -0
  99. {unitx-0.17.2 → unitx-0.17.4}/mkdocs.yml +0 -0
  100. {unitx-0.17.2 → unitx-0.17.4}/pyrightconfig.json +0 -0
  101. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/__init__.py +0 -0
  102. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_constnamespace.py +0 -0
  103. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/__init__.py +0 -0
  104. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/affine.py +0 -0
  105. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/definition.py +0 -0
  106. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/logarithmic.py +0 -0
  107. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/logarithms.py +0 -0
  108. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_coordinates/resolve.py +0 -0
  109. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_electromagnetism/__init__.py +0 -0
  110. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_electromagnetism/coefficients.py +0 -0
  111. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_electromagnetism/conversion.py +0 -0
  112. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_formatting/__init__.py +0 -0
  113. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_formatting/render.py +0 -0
  114. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_formatting/unicode.py +0 -0
  115. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_immutable.py +0 -0
  116. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/__init__.py +0 -0
  117. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/conversion.py +0 -0
  118. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/numpy_creation.py +0 -0
  119. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/numpy_histograms.py +0 -0
  120. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/numpy_misc.py +0 -0
  121. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/numpy_writes.py +0 -0
  122. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_numerics/scaling.py +0 -0
  123. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_operands.py +0 -0
  124. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_registry/__init__.py +0 -0
  125. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_registry/builtin_units.py +0 -0
  126. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_registry/prefixes.py +0 -0
  127. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_registry/reconstruction.py +0 -0
  128. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/_representation.py +0 -0
  129. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/affineunit.py +0 -0
  130. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/cgs/__init__.py +0 -0
  131. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/cgs/units.py +0 -0
  132. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/dimension.py +0 -0
  133. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/factor.py +0 -0
  134. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/imperial/__init__.py +0 -0
  135. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/imperial/units.py +0 -0
  136. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/logunit.py +0 -0
  137. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/matplotlib.py +0 -0
  138. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/monomial.py +0 -0
  139. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/py.typed +0 -0
  140. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/scipy.py +0 -0
  141. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/serialization.py +0 -0
  142. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/si/__init__.py +0 -0
  143. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/si/bases.py +0 -0
  144. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/si/constants.py +0 -0
  145. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/si/units.py +0 -0
  146. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/singleunit.py +0 -0
  147. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/statistics.py +0 -0
  148. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/time.py +0 -0
  149. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/unit.py +0 -0
  150. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/unitcatalog.py +0 -0
  151. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/unitdefinition.py +0 -0
  152. {unitx-0.17.2 → unitx-0.17.4}/src/unitx/unitparser.py +0 -0
  153. {unitx-0.17.2 → unitx-0.17.4}/tests/README.md +0 -0
  154. {unitx-0.17.2 → unitx-0.17.4}/tests/__init__.py +0 -0
  155. {unitx-0.17.2 → unitx-0.17.4}/tests/_notebooks.py +0 -0
  156. {unitx-0.17.2 → unitx-0.17.4}/tests/algebra/__init__.py +0 -0
  157. {unitx-0.17.2 → unitx-0.17.4}/tests/algebra/test_dimension.py +0 -0
  158. {unitx-0.17.2 → unitx-0.17.4}/tests/algebra/test_factor.py +0 -0
  159. {unitx-0.17.2 → unitx-0.17.4}/tests/algebra/test_monomial.py +0 -0
  160. {unitx-0.17.2 → unitx-0.17.4}/tests/algebra/test_numeric.py +0 -0
  161. {unitx-0.17.2 → unitx-0.17.4}/tests/catalogues/__init__.py +0 -0
  162. {unitx-0.17.2 → unitx-0.17.4}/tests/catalogues/test_cgs.py +0 -0
  163. {unitx-0.17.2 → unitx-0.17.4}/tests/catalogues/test_cgs_systems.py +0 -0
  164. {unitx-0.17.2 → unitx-0.17.4}/tests/catalogues/test_imperial.py +0 -0
  165. {unitx-0.17.2 → unitx-0.17.4}/tests/catalogues/test_si.py +0 -0
  166. {unitx-0.17.2 → unitx-0.17.4}/tests/catalogues/test_unicode_units.py +0 -0
  167. {unitx-0.17.2 → unitx-0.17.4}/tests/display/__init__.py +0 -0
  168. {unitx-0.17.2 → unitx-0.17.4}/tests/display/test_display.py +0 -0
  169. {unitx-0.17.2 → unitx-0.17.4}/tests/display/test_siunitx_display.py +0 -0
  170. {unitx-0.17.2 → unitx-0.17.4}/tests/display/test_unicode.py +0 -0
  171. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/__init__.py +0 -0
  172. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/conftest.py +0 -0
  173. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_cgs_numpy.py +0 -0
  174. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy.py +0 -0
  175. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_affine.py +0 -0
  176. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_array_manipulation.py +0 -0
  177. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_compatibility.py +0 -0
  178. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_contractions.py +0 -0
  179. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_covariance.py +0 -0
  180. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_creation.py +0 -0
  181. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_differentiation.py +0 -0
  182. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_factorizations.py +0 -0
  183. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_fft.py +0 -0
  184. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_histograms.py +0 -0
  185. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_indexing.py +0 -0
  186. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_integration.py +0 -0
  187. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_interpolation.py +0 -0
  188. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_joining.py +0 -0
  189. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_linalg.py +0 -0
  190. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_math.py +0 -0
  191. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_multi_outputs.py +0 -0
  192. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_mutation.py +0 -0
  193. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_nan_reductions.py +0 -0
  194. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_nan_statistics.py +0 -0
  195. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_norm.py +0 -0
  196. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_ordering.py +0 -0
  197. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_outputs.py +0 -0
  198. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_products.py +0 -0
  199. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_selection.py +0 -0
  200. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_shapes.py +0 -0
  201. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_statistics.py +0 -0
  202. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_ufunc_methods.py +0 -0
  203. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_weighted_statistics.py +0 -0
  204. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_workflows.py +0 -0
  205. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy/test_numpy_write_functions.py +0 -0
  206. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_asarray.py +0 -0
  207. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_cgs.py +0 -0
  208. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_components.py +0 -0
  209. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_indexing.py +0 -0
  210. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_logarithmic.py +0 -0
  211. {unitx-0.17.2 → unitx-0.17.4}/tests/numpy_typing/check_scipy.py +0 -0
  212. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/__init__.py +0 -0
  213. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_affine.py +0 -0
  214. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_affinequantity.py +0 -0
  215. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_decibel_call.py +0 -0
  216. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_float_decimal.py +0 -0
  217. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_linear_boundaries.py +0 -0
  218. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_logarithmic_model.py +0 -0
  219. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_quantity.py +0 -0
  220. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_quantity_astype.py +0 -0
  221. {unitx-0.17.2 → unitx-0.17.4}/tests/quantities/test_scalar_protocols.py +0 -0
  222. {unitx-0.17.2 → unitx-0.17.4}/tests/stdlib/__init__.py +0 -0
  223. {unitx-0.17.2 → unitx-0.17.4}/tests/stdlib/test_scipy.py +0 -0
  224. {unitx-0.17.2 → unitx-0.17.4}/tests/stdlib/test_statistics.py +0 -0
  225. {unitx-0.17.2 → unitx-0.17.4}/tests/stdlib/test_time.py +0 -0
  226. {unitx-0.17.2 → unitx-0.17.4}/tests/test_examples.py +0 -0
  227. {unitx-0.17.2 → unitx-0.17.4}/tests/test_immutable.py +0 -0
  228. {unitx-0.17.2 → unitx-0.17.4}/tests/test_impedance_example.py +0 -0
  229. {unitx-0.17.2 → unitx-0.17.4}/tests/test_import.py +0 -0
  230. {unitx-0.17.2 → unitx-0.17.4}/tests/test_matplotlib.py +0 -0
  231. {unitx-0.17.2 → unitx-0.17.4}/tests/test_serialization.py +0 -0
  232. {unitx-0.17.2 → unitx-0.17.4}/tests/test_warnings.py +0 -0
  233. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_affine.py +0 -0
  234. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_cgs.py +0 -0
  235. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_float.py +0 -0
  236. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_imperial.py +0 -0
  237. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_logarithmic.py +0 -0
  238. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_registry.py +0 -0
  239. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_serialization.py +0 -0
  240. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_si.py +0 -0
  241. {unitx-0.17.2 → unitx-0.17.4}/tests/typing/check_statistics.py +0 -0
  242. {unitx-0.17.2 → unitx-0.17.4}/tests/units/__init__.py +0 -0
  243. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_builtin_definitions.py +0 -0
  244. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_catalog_collection.py +0 -0
  245. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_definition_scope.py +0 -0
  246. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_registry.py +0 -0
  247. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_registry_scope.py +0 -0
  248. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_singleunit.py +0 -0
  249. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_unit.py +0 -0
  250. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_unit_definition.py +0 -0
  251. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_unitparser.py +0 -0
  252. {unitx-0.17.2 → unitx-0.17.4}/tests/units/test_unitregistry.py +0 -0
@@ -6,10 +6,11 @@ import numpy as np
6
6
  from numpy.typing import NDArray
7
7
 
8
8
  from unitx import AffineQuantity, LogQuantity, LogUnit, Quantity, Unit
9
+ from unitx import math as unitx_math
9
10
  from unitx._numerics import Array
10
11
  from unitx._numerics.numpy_typing import NumpyValue
11
12
  from unitx.cgs import emu, esu, gaussian, hl
12
- from unitx.exceptions import DimensionError
13
+ from unitx.exceptions import DimensionError, ImplicitConversionError, UnitOperationError
13
14
  from unitx.si import si
14
15
 
15
16
  type ArrayQuantity = Quantity[Array] | AffineQuantity[Array] | LogQuantity[Array]
@@ -29,13 +30,13 @@ def check_components(signal: Quantity[NDArray[np.complex128]]) -> None:
29
30
 
30
31
  def check_arrays(values: NDArray[np.float64]) -> None:
31
32
  ratio = Quantity(values, si.percent)
32
- assert_type(ratio.n, NumpyValue)
33
+ assert_type(ratio.n, NDArray[np.float64] | np.float64)
33
34
  np.testing.assert_allclose(ratio.n, values / 100)
34
35
  assert_type(ratio.as_pair(), tuple[NDArray[np.float64], Unit])
35
36
  assert ratio.as_pair()[0] is values
36
- assert_type(ratio.to_base(), Quantity[NumpyValue])
37
- assert_type(ratio.simplify(), Quantity[NumpyValue])
38
- assert_type(Quantity(values, si.m).compact(), Quantity[NumpyValue])
37
+ assert_type(ratio.to_base(), Quantity[NDArray[np.float64] | np.float64])
38
+ assert_type(ratio.simplify(), Quantity[NDArray[np.float64] | np.float64])
39
+ assert_type(Quantity(values, si.m).compact(), Quantity[NDArray[np.float64] | np.float64])
39
40
  assert Quantity(values, si.cm * si.m).simplify().unit == si.m**2
40
41
  signal = Quantity(np.asarray([3 + 4j], dtype=np.complex128), si.V)
41
42
  check_components(signal)
@@ -555,7 +556,7 @@ def check_array_methods(target: NDArray[np.float64]) -> None:
555
556
  assert_type(columns, Quantity[NDArray[np.float32]])
556
557
  assert_type(columns.ravel(), Quantity[NDArray[np.float32]])
557
558
  assert_type(columns.copy(), Quantity[NDArray[np.float32]])
558
- assert_type(columns.mean(axis=0), Quantity[NumpyValue])
559
+ assert_type(columns.mean(axis=0), Quantity[NDArray[np.float32] | np.float32])
559
560
  assert np.shares_memory(columns.value, values)
560
561
  assert not np.shares_memory(columns.copy().value, values)
561
562
  out = Quantity(target, si.cm)
@@ -578,3 +579,96 @@ def check_array_methods(target: NDArray[np.float64]) -> None:
578
579
 
579
580
 
580
581
  check_array_methods(np.zeros(2, dtype=np.float64))
582
+
583
+
584
+ def check_calculus_types(values: NDArray[np.float64]) -> None:
585
+ coordinate = Quantity(values, si.s)
586
+ signal = 2 * Quantity(values, si.A)
587
+ assert_type(coordinate.to(si.ms), Quantity[NDArray[np.float64] | np.float64])
588
+ assert_type(signal * coordinate, Quantity[NDArray[np.float64] | np.float64])
589
+ weights = cast(Quantity[Array], np.gradient(coordinate, axis=0))
590
+ weights[[0, -1]] /= 2
591
+ integral = cast(Quantity[Array], np.trapezoid(signal, coordinate))
592
+ assert np.sum(signal * weights).isclose(integral)
593
+ points = AffineQuantity(values, si.degC)
594
+ increments = cast(Quantity[Array], np.diff(points))
595
+ assert increments.unit == si.degC.delta_unit
596
+ assert np.diff(points, n=0) is points
597
+ rate = cast(Quantity[Array], np.gradient(points, coordinate, axis=0))
598
+ np.testing.assert_allclose(rate.value, 1)
599
+ levels = LogQuantity(values, si.dBm)
600
+ differences = cast(Quantity[Array], np.diff(levels))
601
+ assert differences.unit == si.dBm.delta_unit
602
+ try:
603
+ np.asarray(coordinate)
604
+ except ImplicitConversionError as error:
605
+ assert isinstance(error, TypeError)
606
+ else:
607
+ error_message = 'Array coercion must preserve the explicit numerical boundary.'
608
+ raise AssertionError(error_message)
609
+ try:
610
+ np.add(points, points)
611
+ except UnitOperationError as error:
612
+ assert isinstance(error, TypeError)
613
+ else:
614
+ error_message = 'Adding two points must fail.'
615
+ raise AssertionError(error_message)
616
+
617
+
618
+ check_calculus_types(np.array([0.0, 1, 3, 6]))
619
+
620
+
621
+ def check_scientific_inference(values: NDArray[np.float64]) -> None:
622
+ spectrum = Quantity(values.reshape(2, 2, 2), si.J)
623
+ mask = np.array([True, False])
624
+ selected = spectrum[mask, 0, 0]
625
+ assert_type(selected, Quantity[NDArray[np.float64]])
626
+ assert_type(spectrum[:, 0, 0], Quantity[NDArray[np.float64]])
627
+ phase = 1j * selected
628
+ assert_type(phase, Quantity[NDArray[np.complex128] | np.complex128])
629
+ assert_type(spectrum.sum(), Quantity[np.float64])
630
+ assert_type(spectrum.sum().value_in(si.J), np.float64)
631
+ assert_type(selected.astype(float), Quantity[NDArray[np.float64]])
632
+ assert_type(Quantity(np.float32(2), si.percent).n, np.float32)
633
+ np.testing.assert_array_equal(selected.value, [values[0]])
634
+ np.testing.assert_array_equal(phase.value, 1j * selected.value)
635
+ assert spectrum.sum().value_in(si.J) == np.sum(values)
636
+
637
+
638
+ check_scientific_inference(np.arange(8, dtype=np.float64))
639
+
640
+
641
+ def check_complex_field(values: NDArray[np.float64]) -> None:
642
+ z = values * si.m
643
+ gouy = unitx_math.atan(z / (0.5 * si.m))
644
+ geometry = unitx_math.hypot(1, z / (0.5 * si.m))
645
+ field = unitx_math.conj(unitx_math.exp(1j * gouy) / geometry)
646
+ assert_type(field, Quantity[NDArray[np.complex128] | np.complex128])
647
+ for _ in range(3):
648
+ field *= unitx_math.exp(-1j * gouy)
649
+ assert_type(field, Quantity[NDArray[np.complex128] | np.complex128])
650
+ angle = np.arctan(np.linspace(-1, 1, 5) / 0.5)
651
+ expected = np.exp(-4j * angle) / np.hypot(1, np.linspace(-1, 1, 5) / 0.5)
652
+ np.testing.assert_allclose(field.n, expected, rtol=1e-14)
653
+
654
+
655
+ check_complex_field(np.linspace(-1, 1, 5))
656
+
657
+
658
+ def check_shape_axes(values: NDArray[np.float64]) -> None:
659
+ q = Quantity(values.reshape(2, 1, 4), si.m)
660
+ permuted = q.transpose(2, 0, 1).squeeze(-1)
661
+ assert_type(permuted, Quantity[NDArray[np.float64]])
662
+ assert np.shares_memory(permuted.value, values)
663
+ np.testing.assert_array_equal(permuted.value, values.reshape(2, 4).T)
664
+ assert_type(Quantity(np.float32(2), si.m).squeeze(), Quantity[np.float32])
665
+ for point in (
666
+ AffineQuantity(values.reshape(2, 1, 4), si.degC),
667
+ LogQuantity(values.reshape(2, 1, 4), si.dBm),
668
+ ):
669
+ transformed = point.transpose((2, 0, 1)).squeeze(axis=-1)
670
+ assert type(transformed) is type(point) and transformed.unit is point.unit
671
+ np.testing.assert_array_equal(transformed.value, values.reshape(2, 4).T)
672
+
673
+
674
+ check_shape_axes(np.arange(8.0))
@@ -26,6 +26,7 @@ from unitx import (
26
26
  time,
27
27
  )
28
28
  from unitx.cgs import cgs, emu, esu, gaussian, hl
29
+ from unitx.exceptions import ImplicitConversionError, UnitOperationError, UnitxError
29
30
  from unitx.factor import Factor
30
31
  from unitx.imperial import imperial
31
32
  from unitx.quantity import quantity as decimal_quantity
@@ -120,6 +121,9 @@ assert_type(root, Quantity[float])
120
121
  assert root == 3 * si.m
121
122
  assert math.sin(90 * si.deg).value == 1.0
122
123
  assert math.hypot(3 * si.m, 400 * si.cm) == 5 * si.m
124
+ assert_type(math.exp(1j), Quantity[complex])
125
+ assert_type(math.conj(2 + 3j), Quantity[complex])
126
+ assert math.conj(Quantity(2 + 3j, si.m)).value == 2 - 3j
123
127
 
124
128
  stamp = time.time()
125
129
  start = time.perf_counter()
@@ -185,3 +189,14 @@ assert 'matplotlib' not in sys.modules
185
189
  # The sibling stub must retain the module-level Decimal factory.
186
190
  assert_type(decimal_quantity(1.25, si.m), Quantity[Decimal])
187
191
  assert decimal_quantity(1.25, si.m).value == Decimal('1.25')
192
+
193
+ assert issubclass(UnitOperationError, TypeError)
194
+ assert issubclass(UnitOperationError, UnitxError)
195
+ assert issubclass(ImplicitConversionError, UnitOperationError)
196
+ try:
197
+ _ = (20 @ si.degC) * 2
198
+ except UnitOperationError as error:
199
+ assert isinstance(error, TypeError)
200
+ else:
201
+ error_message = 'Multiplying a coordinate point must fail.'
202
+ raise AssertionError(error_message)
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.4] - 2026-10-28
4
+
5
+ ### Added
6
+
7
+ - Add typed `transpose` and `squeeze` methods for Quantity, AffineQuantity and
8
+ LogQuantity, preserving full units, coordinate families and NumPy views.
9
+ - Add `unitx.math.conj` and complex inputs to `unitx.math.exp` for typed complex
10
+ phases, preserving complete units and the existing NumPy dispatch rules.
11
+
12
+ ### Changed
13
+
14
+ - Expose `n` as an ordinary read-only property and retain float inference for
15
+ scientific scalar formulas, with exact runtime conversion rules unchanged.
16
+ - Preserve known NumPy dtypes through the supported `unitx.math` phase functions,
17
+ mixed real/complex algebra, absolute values and same-dtype augmented products.
18
+ - Retain ndarray results and numeric dtypes for slices, boolean indices,
19
+ ellipses, new axes and tuples starting with one of these indices.
20
+ - Infer complex64/complex128 for float32/float64 multiplication and division
21
+ with Python complex scalars, including reflected operations.
22
+ - Infer concrete NumPy dtype and container types for explicit `astype` calls,
23
+ and NumPy scalar types for complete sum/mean reductions and their numerical
24
+ exits, reducing the need for consumer casts and numerical conversions.
25
+
26
+ ## [0.17.3] - 2026-10-27
27
+
28
+ ### Added
29
+
30
+ - Support `diff` and `gradient` on affine/logarithmic point arrays, returning
31
+ differences and using coordinate difference units for derivatives. Align
32
+ point boundaries explicitly; retain the original object for `diff(n=0)`.
33
+ - Document reusable trapezoidal weights using `np.gradient` and an endpoint
34
+ correction.
35
+ - Add `UnitOperationError` for point/difference semantic violations and its
36
+ `ImplicitConversionError` subclass for silent array unit removal. Both remain
37
+ catchable as TypeError and UnitxError; native numerical errors stay native.
38
+
39
+ ### Changed
40
+
41
+ - Delegate direct array methods through a private lazy decorator, retaining
42
+ explicit signatures, method defaults and the existing NumPy unit/output rules.
43
+ - Retain known floating/complex NumPy dtypes in linear conversions, supported
44
+ arithmetic and default sum/mean results, including possible NumPy scalars.
45
+ Preserve numeric dtypes in indexing/iteration; narrow exact Fraction
46
+ arithmetic and complex scalar result domains.
47
+
48
+ ### Fixed
49
+
50
+ - Update the generator annotation on registry definition scopes to avoid the
51
+ deprecated Iterator overload of `contextmanager` in current type stubs.
52
+
3
53
  ## [0.17.2] - 2026-10-27
4
54
 
5
55
  ### Added
@@ -112,10 +112,13 @@ python -m build
112
112
  python -m twine check --strict dist/*
113
113
  ```
114
114
 
115
- Check wheel/sdist contents, version metadata, `py.typed`, and an installed
116
- consumer outside the checkout. Source-tree success alone does not verify
117
- packaging. Use a clean export of the intended commit when separating several
118
- changes into independently reviewable commits.
115
+ Build release artifacts from a clean checkout or Git export of the intended
116
+ commit. Git's local `.git/info/exclude` is not a distribution manifest:
117
+ building a shared working tree can include locally ignored notes in an sdist.
118
+ Check wheel/sdist contents, version metadata, `py.typed`, and installed
119
+ consumers outside the checkout. Source-tree success alone does not verify
120
+ packaging. When separating changes, also validate the actual committed
121
+ snapshots rather than only the final working tree.
119
122
 
120
123
  The [release workflow](.github/workflows/release.yml) validates distributions
121
124
  and uploads them to PyPI after a published GitHub Release. The tag must match
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unitx
3
- Version: 0.17.2
3
+ Version: 0.17.4
4
4
  Summary: Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.
5
5
  Project-URL: Homepage, https://github.com/DaitTHU/unitx
6
6
  Project-URL: Documentation, https://daitthu.github.io/unitx/
@@ -165,6 +165,12 @@ enumerate their own reference/difference dependencies. Display, NumPy and
165
165
  serialization remain explicit integration boundaries. Numeric caches, parser
166
166
  state and reconstruction indexes remain separate from mathematical identity.
167
167
 
168
+ Direct array methods declare their public signatures explicitly. A private
169
+ decorator in `_numerics/methods.py` binds their defaults and lazily delegates to
170
+ the same NumPy dispatch used by function calls. It adds no dynamic method lookup
171
+ or separate unit rules; coordinate and linear result construction stays in the
172
+ existing handlers. Reshape keeps its explicit positional-shape normalization.
173
+
168
174
  The point marker's type parameter describes the result of adding a difference,
169
175
  not the original magnitude type. This prevents the open scalar protocol from
170
176
  claiming point operands and preserves concrete result types without naming the
@@ -69,13 +69,18 @@ No coordinate sorting, interpolation, dtype option, or output buffer is added.
69
69
 
70
70
  ## Differences and gradients
71
71
 
72
- `np.diff(q, n=1, axis=-1)` computes differences in q's stored coordinates and
73
- retains its unit for every order n. `prepend` and `append` may be numeric
72
+ `np.diff(q, n=1, axis=-1)` computes differences in q's stored coordinates.
73
+ Linear quantities retain their unit; AffineQuantity and LogQuantity points
74
+ return ordinary Quantities in their complete difference unit for every positive
75
+ order n. Logarithmic differences describe changes in readings, without an
76
+ implicit conversion to physical power or amplitude. `prepend` and `append` may be numeric
74
77
  scalars/arrays or Quantities; their dimensions must match the input, and their
75
78
  values convert to the input unit before differencing. Bare boundaries are
76
79
  dimensionless, including bare zero. A bare input with a Quantity boundary is
77
80
  also dimensionless. Scaled dimensionless boundaries therefore normalize to the
78
81
  input's coordinates. Explicit list inputs/boundaries remain unsupported.
82
+ For points, boundaries must be compatible points and convert to the input
83
+ coordinate first; a difference or bare zero cannot stand in for a point.
79
84
 
80
85
  With `n=0`, diff returns its input object unchanged and ignores axis and
81
86
  boundaries. Other orders follow [NumPy diff](https://numpy.org/doc/stable/reference/generated/numpy.diff.html)
@@ -83,12 +88,15 @@ for shapes, scalar-boundary expansion, dtype promotion, unsigned wraparound,
83
88
  and empty results. A difference is not a derivative: its unit is not divided
84
89
  by a coordinate unit or raised to n.
85
90
 
86
- `np.gradient(f, *spacing, axis=None, edge_order=1)` accepts numeric or Quantity
87
- input and spacings. Each spacing is a scalar step or one-dimensional coordinate
91
+ `np.gradient(f, *spacing, axis=None, edge_order=1)` accepts numeric values,
92
+ Quantities and coordinate points. Each spacing is a scalar difference step or one-dimensional coordinate
88
93
  array; bare inputs and spacings are dimensionless. A single scalar step applies
89
94
  to every selected axis. Otherwise supply one spacing per selected axis, in
90
95
  the order given by axis. Each component has unit `f.unit / spacing.unit`,
91
96
  including exact Unit factors. No spacing means dimensionless unit steps.
97
+ For point-valued f, use its difference unit in the numerator. A point coordinate
98
+ array contributes its difference unit in the denominator; a scalar point is
99
+ not a step. Changing a coordinate origin does not change the derivative.
92
100
  The kernels use stored numerical coordinates; no implicit SI conversion is
93
101
  needed because each result retains the corresponding unit ratio.
94
102
 
@@ -112,4 +120,31 @@ np.testing.assert_allclose(velocity.value, 2)
112
120
  assert np.trapezoid(velocity, time).isclose(distance[-1] - distance[0])
113
121
  ```
114
122
 
123
+ ## Reusable trapezoidal weights
124
+
125
+ For repeated weighted sums or contractions on a real one-dimensional grid of
126
+ at least two samples, compute weights with gradient and halve the endpoints:
127
+
128
+ ```python
129
+ time = Quantity(np.array([0.0, 1, 3, 6]), 's')
130
+ current = Quantity(np.array([0.0, 2, 6, 12]), 'A')
131
+ weights = np.gradient(time, axis=0)
132
+ weights[[0, -1]] /= 2
133
+ np.testing.assert_allclose(weights.value, [0.5, 1.5, 2.5, 1.5])
134
+ assert np.sum(current * weights).isclose(np.trapezoid(current, time))
135
+ ```
136
+
137
+ With no spacing argument and the default `edge_order=1`, interior gradient
138
+ entries are `(x[i+1] - x[i-1]) / 2`, half the sum of adjacent intervals.
139
+ Endpoints are full one-sided intervals, so their halving is essential.
140
+ This is mathematically equivalent to trapezoidal weights; floating-point
141
+ rounding order can differ from interval-by-interval integration.
142
+ Decreasing and nonmonotonic coordinates retain their interval signs.
143
+ Gradient needs at least two samples; handle empty/singleton grids locally
144
+ only when the calculation requires them. Point coordinates return difference
145
+ weights; logarithmic readings are not implicitly mapped to physical values.
146
+
147
+ For a single integral, use `np.trapezoid`, following the linear integration
148
+ contract above. NumPy function stubs retain the [documented typing limits](../typing.md#numpy-magnitude-types).
149
+
115
150
  [NumPy overview and support matrix](../numpy.md) · [Documentation index](../README.md)
@@ -1,7 +1,7 @@
1
1
  # Direct array methods
2
2
 
3
- For NumPy magnitudes, Quantity provides `reshape`, `ravel`, `copy`, `sum`,
4
- `mean` and `std`. AffineQuantity and LogQuantity provide the same methods
3
+ For NumPy magnitudes, Quantity provides `reshape`, `ravel`, `copy`, `transpose`,
4
+ `squeeze`, `sum`, `mean` and `std`. AffineQuantity and LogQuantity provide the same methods
5
5
  except `sum`. Mean preserves a point's coordinate; standard deviation returns
6
6
  its difference unit. Logarithmic statistics operate on readings; convert to
7
7
  physical values explicitly before averaging power or amplitude.
@@ -15,6 +15,14 @@ ndarray storage, including for a NumPy scalar. Its C default follows
15
15
  can remain a NumPy scalar; ndarray input remains ndarray with the same dtype.
16
16
  Python scalar magnitudes require the scalar APIs or explicit array construction.
17
17
 
18
+ `transpose()` reverses the axes; `transpose((2, 0, 1))` and
19
+ `transpose(2, 0, 1)` select a permutation. Axis inputs are positional.
20
+ `squeeze(axis=None)` removes all length-one axes, or only the selected integer
21
+ or tuple of axes. Both retain the complete unit, coordinate family and dtype,
22
+ and share storage for ndarray inputs. A fully squeezed ndarray remains a
23
+ zero-dimensional ndarray; a NumPy scalar remains a NumPy scalar. Invalid
24
+ axes raise native NumPy errors without changing the input.
25
+
18
26
  Reductions accept `axis`, `dtype`, `out`, `keepdims` and `where`. Sum also
19
27
  accepts a compatible `initial` (bare zero is the additive identity); std
20
28
  supports `ddof`, `correction` and a compatible precomputed `mean`. These use
@@ -34,6 +42,8 @@ np.testing.assert_allclose(samples.mean(axis=1).value, [1, 4])
34
42
  assert samples.sum(initial=Quantity(100, 'cm')) == Quantity(16, 'm')
35
43
  assert np.shares_memory(samples.ravel().value, samples.value)
36
44
  assert not np.shares_memory(samples.copy().value, samples.value)
45
+ channels = samples.reshape(2, 1, 3).transpose(2, 0, 1).squeeze(-1)
46
+ np.testing.assert_array_equal(channels.value, samples.value.T)
37
47
  assert np.trapezoid(np.array([1.0, 2, 3]), dx=Quantity(2, 's')) == Quantity(8, 's')
38
48
  ```
39
49
 
@@ -33,7 +33,8 @@ differences, and point sums/products are rejected.
33
33
  ## Values, storage, and precision
34
34
 
35
35
  `.value`, `.value_in(unit)`, and `.to(unit).value` are explicit numerical exits.
36
- `np.asarray(q)` and `np.array(q)` raise TypeError even for dimensionless q;
36
+ `np.asarray(q)` and `np.array(q)` raise `ImplicitConversionError` (a TypeError)
37
+ even for dimensionless q;
37
38
  use `np.asarray(q.value_in(target_unit))`.
38
39
 
39
40
  Construction, unit-only multiplication/division, and an identity conversion
@@ -71,7 +72,7 @@ produce unhashable Quantities. `bool(q)` delegates to `bool(q.value)`.
71
72
 
72
73
  ## Direct array methods
73
74
 
74
- Quantity provides `reshape`, `ravel`, `copy`, `sum`, `mean` and `std` for NumPy
75
+ Quantity provides `reshape`, `ravel`, `copy`, `transpose`, `squeeze`, `sum`, `mean` and `std` for NumPy
75
76
  magnitudes. Two coordinate families provide the same methods except `sum`;
76
77
  mean preserves a point and std returns a difference. These methods preserve
77
78
  the wrapper in static types. See [array methods](numpy/methods.md) for shape,
@@ -212,8 +213,8 @@ functions, including exp/log. Python scalar arithmetic follows the separate [sca
212
213
  | argmin/nanargmin, argmax/nanargmax | Bare indices | axis, keepdims, out |
213
214
  | interp | Align x to xp's unit; retain fp's unit | finite strictly increasing 1-D xp; scalar left/right, period=None; no out |
214
215
  | trapezoid | Multiply ordinate and coordinate units | x, scalar dx, axis; no out |
215
- | diff | Preserve input unit | n, axis, prepend, append; no out |
216
- | gradient | Divide input unit by each coordinate unit | scalar spacings or coordinate arrays, axis, edge_order; no out |
216
+ | diff | Preserve linear unit; point inputs return differences for n > 0 | n, axis, compatible point/value prepend and append; no out |
217
+ | gradient | Divide input difference unit by each coordinate difference unit | scalar difference spacings or coordinate arrays, axis, edge_order; no out |
217
218
  | isclose, allclose | Bare mask / Python bool | rtol, atol, equal_nan |
218
219
 
219
220
  Supply the first magnitude argument positionally; concatenate/stack take a
@@ -235,8 +236,8 @@ behavior for augmented arithmetic.
235
236
  `squeeze`, `expand_dims`, `moveaxis`, `swapaxes`, `rollaxis`, and `ravel`
236
237
  operate on magnitudes without conversion. Like reshape and transpose, they
237
238
  retain the complete unit or point coordinate and return the same family:
238
- Quantity, AffineQuantity, or LogQuantity. Only the NumPy function forms are
239
- supported; these functions have no `out` option.
239
+ Quantity, AffineQuantity, or LogQuantity. Ravel also has a direct method;
240
+ these functions have no `out` option.
240
241
 
241
242
  Axis rules and errors follow NumPy, including negative and tuple axes where
242
243
  supported. Squeezing an ndarray whose axes all have length one produces a
@@ -1,6 +1,6 @@
1
1
  # Standard-library enhancements
2
2
 
3
- `unitx.math` provides a finite set of unit-aware real functions. `unitx.time`
3
+ `unitx.math` provides a finite set of unit-aware functions. `unitx.time`
4
4
  provides integer nanosecond clock readings and explicit duration adapters.
5
5
  `unitx.statistics` provides scalar means, dispersion and paired statistics.
6
6
  These modules work without NumPy installed.
@@ -9,7 +9,7 @@ These modules work without NumPy installed.
9
9
 
10
10
  | Module | Use |
11
11
  | --- | --- |
12
- | `unitx.math` | Unit-aware real mathematical functions with scalar and optional NumPy backends |
12
+ | `unitx.math` | Unit-aware mathematical functions with scalar and optional NumPy backends |
13
13
  | `unitx.time` | Integer ns clocks, scalar sleep, timedelta and aware datetime conversion |
14
14
  | `unitx.statistics` | [Scalar means, dispersion, covariance and correlation](statistics.md) |
15
15
 
@@ -46,6 +46,7 @@ identities or arbitrary-precision evaluation.
46
46
  | `sin(x)`, `cos(x)`, `tan(x)` | Dimensionless input, including deg/rad; coherent dimensionless result |
47
47
  | `asin(x)`, `acos(x)`, `atan(x)` | Dimensionless input; result in rad |
48
48
  | `exp(x)`, `expm1(x)` | Dimensionless input; coherent dimensionless result |
49
+ | `conj(x)` | Complex conjugate in the complete input unit |
49
50
  | `log(x)`, `log1p(x)`, `log2(x)`, `log10(x)` | Dimensionless input; coherent dimensionless result |
50
51
  | `hypot(*coordinates)` | Compatible input dimensions; first coordinate's unit |
51
52
  | `atan2(y, x)` | Compatible input dimensions; result in rad |
@@ -58,11 +59,15 @@ Use `log(x) / log(base)` for a different base. `hypot()` returns dimensionless
58
59
  zero; `hypot(x)` returns the absolute value in x's unit. For a bare first
59
60
  coordinate, the result unit is coherent dimensionless.
60
61
 
61
- Python int, float, Fraction and Decimal inputs use the standard-library math
62
+ Real transcendental calls on Python int, float, Fraction and Decimal use the standard-library math
62
63
  backend, returning `Quantity[float]`. Decimal/Fraction input does not imply
63
64
  arbitrary-precision output. Standard math domain, overflow, NaN and infinity
64
65
  behavior applies. In particular, negative `sqrt` raises ValueError; negative
65
- `cbrt` returns a real cube root. Complex inputs, bools, custom scalar backends,
66
+ `cbrt` returns a real cube root. `exp` also accepts complex inputs: Python
67
+ complex values use `cmath.exp`, and NumPy complex magnitudes use `np.exp`.
68
+ `conj` preserves the native scalar type or delegates to NumPy conjugation;
69
+ it preserves the complete input unit. Other functions retain their real domain.
70
+ Bools, custom scalar backends,
66
71
  implicit list/tuple arrays, affine points and logarithmic units are unsupported.
67
72
  Explicitly convert a logarithmic quantity to a physical ratio first.
68
73
 
@@ -74,13 +79,35 @@ may differ from the standard library's multi-coordinate implementation.
74
79
  The wrappers accept positional inputs only, without `out` or `where`.
75
80
  Use the supported NumPy ufuncs directly for those options.
76
81
 
77
- Array overloads return `Quantity[Array]`, using the NumPy-independent magnitude
78
- protocol so strict scalar users have complete annotations without NumPy.
82
+ The `sqrt`, `sin`, `cos`, `atan` and `exp` overloads retain known NumPy floating
83
+ dtypes; `exp` and `conj` also retain known complex dtypes. Two-coordinate
84
+ float64 `hypot` calls with float64 or Python real inputs retain float64.
85
+ The result includes a possible NumPy scalar, since zero-dimensional array
86
+ operations can remove the array container. Other array overloads return
87
+ `Quantity[Array]`, using the NumPy-independent magnitude protocol.
88
+ Strict scalar users have complete annotations without NumPy.
79
89
  At runtime, magnitudes are native NumPy arrays/scalars. The protocol exposes
80
90
  shape/ndim/size/dtype and explicit array conversion, without promising a
81
91
  particular result shape or dtype. For additional native array methods in
82
92
  static analysis, use `np.asarray(result.value)`.
83
93
 
94
+ For a typed complex phase, use this existing mathematical entry point directly:
95
+
96
+ ```python
97
+ import numpy as np
98
+ from unitx import math
99
+ from unitx.si import si
100
+
101
+ z = np.linspace(-1, 1, 5) * si.m
102
+ gouy = math.atan(z / (0.5 * si.m))
103
+ field = math.conj(math.exp(1j * gouy) / math.hypot(1, z / (0.5 * si.m)))
104
+ field *= math.exp(-1j * gouy)
105
+ ```
106
+
107
+ The phase formula needs no dtype argument or cast. Ordinary `np.exp(q)` and
108
+ `np.conj(q)` still work at runtime, but their NumPy function signatures may
109
+ infer a bare ndarray or an incomplete dtype. unitx does not alter those stubs.
110
+
84
111
  ## Clock readings
85
112
 
86
113
  ```python