spacecore 0.4.2__tar.gz → 0.4.3__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 (376) hide show
  1. {spacecore-0.4.2 → spacecore-0.4.3}/CHANGELOG.md +196 -0
  2. {spacecore-0.4.2 → spacecore-0.4.3}/PKG-INFO +21 -12
  3. {spacecore-0.4.2 → spacecore-0.4.3}/README.md +20 -11
  4. spacecore-0.4.3/docs/source/api/context.rst +61 -0
  5. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/functionals.rst +86 -4
  6. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/checking_policy.rst +30 -22
  7. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/release_notes.rst +91 -0
  8. {spacecore-0.4.2 → spacecore-0.4.3}/pyproject.toml +7 -0
  9. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/__init__.py +47 -24
  10. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/_batching.py +8 -2
  11. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/_checks.py +39 -0
  12. spacecore-0.4.3/spacecore/_errors.py +29 -0
  13. spacecore-0.4.3/spacecore/_lazy_algebra.py +181 -0
  14. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/_version.py +1 -1
  15. spacecore-0.4.3/spacecore/backend/__init__.py +42 -0
  16. spacecore-0.4.3/spacecore/backend/_container.py +215 -0
  17. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/_ops.py +247 -43
  18. spacecore-0.4.3/spacecore/backend/_optional.py +215 -0
  19. spacecore-0.4.3/spacecore/backend/_registry.py +193 -0
  20. spacecore-0.4.3/spacecore/backend/jax/__init__.py +19 -0
  21. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/jax/_ops.py +15 -0
  22. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/torch/_ops.py +51 -2
  23. {spacecore-0.4.2/spacecore/_contextual → spacecore-0.4.3/spacecore/contextual}/__init__.py +21 -1
  24. {spacecore-0.4.2/spacecore/_contextual → spacecore-0.4.3/spacecore/contextual}/_bound.py +75 -34
  25. {spacecore-0.4.2/spacecore/backend → spacecore-0.4.3/spacecore/contextual}/_context.py +89 -49
  26. spacecore-0.4.3/spacecore/contextual/_contextual.py +260 -0
  27. spacecore-0.4.3/spacecore/contextual/_state.py +263 -0
  28. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/__init__.py +17 -2
  29. spacecore-0.4.3/spacecore/functional/_algebra.py +762 -0
  30. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/_base.py +58 -31
  31. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/_composed.py +69 -6
  32. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/_linear.py +29 -19
  33. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/_quadratic.py +15 -11
  34. spacecore-0.4.3/spacecore/functional/_realified.py +240 -0
  35. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/__init__.py +9 -2
  36. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/_coordinate.py +15 -0
  37. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/_entropy.py +40 -9
  38. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/_huber.py +33 -6
  39. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/_norms.py +32 -11
  40. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/_proximal.py +49 -4
  41. spacecore-0.4.3/spacecore/functional/tools/_spectral.py +292 -0
  42. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/algebra.py +7 -12
  43. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/functional.py +15 -2
  44. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/_dispatch.py +12 -3
  45. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/_power.py +1 -1
  46. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/_algebra.py +233 -133
  47. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/_base.py +58 -18
  48. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/_dense.py +42 -6
  49. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/_diagonal.py +11 -5
  50. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/_metric.py +13 -1
  51. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/_sparse.py +11 -5
  52. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/tree/_base.py +14 -5
  53. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/tree/_block.py +27 -16
  54. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/tree/_from_single.py +10 -5
  55. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/tree/_to_single.py +10 -5
  56. spacecore-0.4.3/spacecore/opfamily.py +329 -0
  57. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/optimize/_optax.py +2 -2
  58. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/_coordinate.py +24 -5
  59. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/_inner_product.py +3 -0
  60. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/_jordan.py +6 -0
  61. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/_space.py +63 -7
  62. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/_star.py +3 -0
  63. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/_vector.py +3 -0
  64. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/concrete/_dense_coordinate.py +9 -2
  65. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/concrete/_dense_vector.py +29 -9
  66. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/concrete/_hermitian.py +38 -2
  67. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/concrete/_stacked.py +36 -24
  68. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/concrete/_tree_space.py +18 -21
  69. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore.egg-info/PKG-INFO +21 -12
  70. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore.egg-info/SOURCES.txt +20 -7
  71. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/_references.py +187 -0
  72. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_context.py +33 -48
  73. spacecore-0.4.3/tests/backend/test_optional_guard.py +188 -0
  74. spacecore-0.4.3/tests/backend/test_pytree_registry.py +429 -0
  75. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_bench_smoke.py +9 -7
  76. spacecore-0.4.3/tests/conftest.py +25 -0
  77. spacecore-0.4.3/tests/context/_contracts.py +101 -0
  78. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/conftest.py +5 -0
  79. spacecore-0.4.3/tests/context/test_ambient_scoping.py +216 -0
  80. spacecore-0.4.3/tests/context/test_check_policy.py +204 -0
  81. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/test_check_policy_helpers.py +8 -0
  82. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/test_checked_method.py +11 -3
  83. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/test_compatibility.py +47 -68
  84. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/test_context_bound.py +57 -19
  85. spacecore-0.4.3/tests/context/test_context_contracts.py +133 -0
  86. spacecore-0.4.3/tests/context/test_context_resolution_policy.py +102 -0
  87. spacecore-0.4.3/tests/context/test_enable_checks.py +164 -0
  88. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/test_policies_errors.py +24 -12
  89. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/test_state_free_functions.py +38 -34
  90. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_algebra.py +184 -5
  91. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_composed_functional.py +167 -1
  92. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_functional_base.py +103 -2
  93. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_generated_functionals.py +32 -23
  94. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_inner_product_functional.py +1 -1
  95. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_linear_functional.py +1 -1
  96. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_linop_quadratic_form.py +7 -6
  97. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_matrix_free_linear_functional.py +10 -8
  98. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_metric_gradient.py +75 -67
  99. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/test_quadratic_form.py +1 -1
  100. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/tools/test_proximal.py +139 -0
  101. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/tools/test_spectral.py +24 -14
  102. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_arrays.py +1 -1
  103. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_contexts.py +7 -10
  104. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_hermitian.py +1 -1
  105. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_metrics.py +1 -1
  106. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_trees.py +1 -1
  107. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/functionals.py +114 -22
  108. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/linops.py +20 -16
  109. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/spaces.py +8 -4
  110. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/test_registry_completeness.py +1 -1
  111. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/test_imports.py +1 -1
  112. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/test_public_api.py +8 -4
  113. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/test_smoke_torch.py +24 -21
  114. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/test_kernel_dispatch.py +49 -1
  115. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/_helpers.py +10 -2
  116. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_core_resolution.py +2 -1
  117. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_generated_solver_matrix.py +2 -1
  118. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_solver_contracts.py +2 -1
  119. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_utils.py +14 -8
  120. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_algebra_factories.py +82 -33
  121. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_algebra_linops.py +48 -29
  122. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_block_diagonal_linop.py +8 -7
  123. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_block_matrix_linop.py +1 -2
  124. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_dense_linop.py +6 -5
  125. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_fused_algebra_overhead.py +8 -5
  126. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_linop_jit.py +8 -1
  127. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_metric_helpers.py +3 -3
  128. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_sparse_linop.py +9 -7
  129. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_stacked_linop.py +8 -6
  130. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_sum_to_single_linop.py +6 -4
  131. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_tree_helpers.py +6 -4
  132. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optim/test_cached_member_checks.py +16 -12
  133. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_check_batched.py +4 -8
  134. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_dense_coordinate_space.py +1 -1
  135. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_elementwise_jordan_spaces.py +3 -3
  136. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_generated_dense_coordinate.py +2 -2
  137. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_hermitian_space.py +65 -6
  138. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_jordan_algebra_spaces.py +4 -4
  139. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_jordan_invariants.py +6 -6
  140. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_space_base.py +9 -9
  141. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_space_checks.py +6 -4
  142. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_stacked_space.py +3 -2
  143. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_tree_space.py +14 -10
  144. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_tree_spectral_decomposition.py +12 -11
  145. spacecore-0.4.3/tests/test_equality.py +310 -0
  146. spacecore-0.4.3/tests/test_opfamily.py +289 -0
  147. {spacecore-0.4.2 → spacecore-0.4.3}/tests/test_repr.py +1 -1
  148. {spacecore-0.4.2 → spacecore-0.4.3}/tests/test_weighted_tikhonov.py +9 -8
  149. spacecore-0.4.2/docs/source/api/context.rst +0 -45
  150. spacecore-0.4.2/spacecore/_contextual/_policies.py +0 -17
  151. spacecore-0.4.2/spacecore/_contextual/_state.py +0 -446
  152. spacecore-0.4.2/spacecore/backend/__init__.py +0 -41
  153. spacecore-0.4.2/spacecore/backend/jax/__init__.py +0 -14
  154. spacecore-0.4.2/spacecore/backend/jax/_pytree.py +0 -32
  155. spacecore-0.4.2/spacecore/functional/_algebra.py +0 -440
  156. spacecore-0.4.2/spacecore/functional/tools/_spectral.py +0 -126
  157. spacecore-0.4.2/tests/backend/test_jax_pytree_class.py +0 -165
  158. spacecore-0.4.2/tests/conftest.py +0 -7
  159. spacecore-0.4.2/tests/context/test_check_policy.py +0 -187
  160. spacecore-0.4.2/tests/context/test_enable_checks.py +0 -128
  161. {spacecore-0.4.2 → spacecore-0.4.3}/CONTRIBUTING.md +0 -0
  162. {spacecore-0.4.2 → spacecore-0.4.3}/LICENSE +0 -0
  163. {spacecore-0.4.2 → spacecore-0.4.3}/MANIFEST.in +0 -0
  164. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/0.3.1-docs-inventory.md +0 -0
  165. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/0.3.1-docstring-audit.md +0 -0
  166. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/0.4.1-bench-surface.md +0 -0
  167. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/001_backend_layer.md +0 -0
  168. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/002_context_and_conversion.md +0 -0
  169. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/003_space_hierarchy.md +0 -0
  170. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/004_inner_product_and_geometry.md +0 -0
  171. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/005_space_subclasses_and_capabilities.md +0 -0
  172. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/006_current_batching_model.md +0 -0
  173. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/007_linop_contract.md +0 -0
  174. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/008_linop_subclasses.md +0 -0
  175. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/009_metric_adjoint.md +0 -0
  176. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/010_functional_contract.md +0 -0
  177. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/011_linalg_contract.md +0 -0
  178. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/012_jordan_spectrum.md +0 -0
  179. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/013_tree_structured_spaces.md +0 -0
  180. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/014_check_policy.md +0 -0
  181. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/015_dtype_default_vs_scalar_field.md +0 -0
  182. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/016_kernel_layers.md +0 -0
  183. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/017_tensor_product_spaces.md +0 -0
  184. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/018_external_optimizer_adapters.md +0 -0
  185. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/019_everyday_toolbox.md +0 -0
  186. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/020_sets_and_projection.md +0 -0
  187. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/021_lazy_operator_algebra_and_simplification.md +0 -0
  188. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/022_caching.md +0 -0
  189. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/023_benchmark_framework.md +0 -0
  190. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/README.md +0 -0
  191. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/adr/template.md +0 -0
  192. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/contributing/architecture.md +0 -0
  193. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/contributing/labels.md +0 -0
  194. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/contributing/linop_generators.md +0 -0
  195. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/contributing/prerequisites.md +0 -0
  196. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/contributing/process.md +0 -0
  197. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/contributing/setup.md +0 -0
  198. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/current.md +0 -0
  199. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/docstring_style.md +0 -0
  200. {spacecore-0.4.2 → spacecore-0.4.3}/docs/dev/vision.md +0 -0
  201. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/_static/custom.css +0 -0
  202. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/backend.rst +0 -0
  203. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/index.rst +0 -0
  204. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/linalg.rst +0 -0
  205. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/linops.rst +0 -0
  206. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/optimize.rst +0 -0
  207. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/api/spaces.rst +0 -0
  208. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/conf.py +0 -0
  209. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/backend_conformance.rst +0 -0
  210. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/backend_deviations.rst +0 -0
  211. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/backend_ops_array_api.rst +0 -0
  212. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/batching.rst +0 -0
  213. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/batching_test_policy.rst +0 -0
  214. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/capability_dispatch.rst +0 -0
  215. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/context_ownership.rst +0 -0
  216. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/conversion_policy.rst +0 -0
  217. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/dtype_policy.rst +0 -0
  218. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/geometry.rst +0 -0
  219. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/index.rst +0 -0
  220. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/jax_integration.rst +0 -0
  221. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/kernels_policy.rst +0 -0
  222. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/performance.rst +0 -0
  223. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/design/tree_spaces.rst +0 -0
  224. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/dev/adr.rst +0 -0
  225. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/dev/contributing.rst +0 -0
  226. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/dev/index.rst +0 -0
  227. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/dev/vision.rst +0 -0
  228. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/index.rst +0 -0
  229. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/01_backend_and_context.rst +0 -0
  230. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/02_linear_algebra.rst +0 -0
  231. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/03_functionals.rst +0 -0
  232. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/04_tree_spaces.rst +0 -0
  233. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/05_weighted_tikhonov.rst +0 -0
  234. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/06_optimal_transport.rst +0 -0
  235. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/07_manifold_descent.rst +0 -0
  236. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/08_pdhg_conic_program.rst +0 -0
  237. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/09_kernels_and_fusion.rst +0 -0
  238. {spacecore-0.4.2 → spacecore-0.4.3}/docs/source/tutorials/index.rst +0 -0
  239. {spacecore-0.4.2 → spacecore-0.4.3}/setup.cfg +0 -0
  240. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/_check_policy.py +0 -0
  241. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/_repr.py +0 -0
  242. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/_eager.py +0 -0
  243. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/_family.py +0 -0
  244. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/cupy/__init__.py +0 -0
  245. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/cupy/_ops.py +0 -0
  246. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/numpy/__init__.py +0 -0
  247. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/numpy/_ops.py +0 -0
  248. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/backend/torch/__init__.py +0 -0
  249. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/functional/tools/_least_squares.py +0 -0
  250. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/__init__.py +0 -0
  251. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/__init__.py +0 -0
  252. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/_rules.py +0 -0
  253. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/dense.py +0 -0
  254. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/diagonal.py +0 -0
  255. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/core/sparse.py +0 -0
  256. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/__init__.py +0 -0
  257. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/_batched.py +0 -0
  258. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/_policy.py +0 -0
  259. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/_registry.py +0 -0
  260. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/block_batched.py +0 -0
  261. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/block_diagonal.py +0 -0
  262. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/composed.py +0 -0
  263. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/composed_simplify.py +0 -0
  264. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/kernels/specs/stacked_batched.py +0 -0
  265. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/__init__.py +0 -0
  266. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/_cg.py +0 -0
  267. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/_expm.py +0 -0
  268. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/_lanczos.py +0 -0
  269. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/_lsqr.py +0 -0
  270. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linalg/_utils.py +0 -0
  271. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/__init__.py +0 -0
  272. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/linop/tree/__init__.py +0 -0
  273. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/optimize/__init__.py +0 -0
  274. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/optimize/_common.py +0 -0
  275. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/optimize/_scipy.py +0 -0
  276. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/__init__.py +0 -0
  277. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/base/__init__.py +0 -0
  278. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/checks/__init__.py +0 -0
  279. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/checks/_base.py +0 -0
  280. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/checks/_coordinate.py +0 -0
  281. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/space/concrete/__init__.py +0 -0
  282. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/types/__init__.py +0 -0
  283. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/types/_array.py +0 -0
  284. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/types/_dtype.py +0 -0
  285. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore/types/_misc.py +0 -0
  286. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore.egg-info/dependency_links.txt +0 -0
  287. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore.egg-info/requires.txt +0 -0
  288. {spacecore-0.4.2 → spacecore-0.4.3}/spacecore.egg-info/top_level.txt +0 -0
  289. {spacecore-0.4.2 → spacecore-0.4.3}/tests/__init__.py +0 -0
  290. {spacecore-0.4.2 → spacecore-0.4.3}/tests/_conformance.py +0 -0
  291. {spacecore-0.4.2 → spacecore-0.4.3}/tests/_helpers.py +0 -0
  292. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/__init__.py +0 -0
  293. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/_conformance.py +0 -0
  294. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/conftest.py +0 -0
  295. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_backend_family.py +0 -0
  296. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_cupy_ops.py +0 -0
  297. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_jax_ops.py +0 -0
  298. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_lazy_namespace.py +0 -0
  299. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_numpy_ops.py +0 -0
  300. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_operations.py +0 -0
  301. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_torch_ops.py +0 -0
  302. {spacecore-0.4.2 → spacecore-0.4.3}/tests/backend/test_types_protocols.py +0 -0
  303. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/__init__.py +0 -0
  304. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_bare_baseline.py +0 -0
  305. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_macro_aggregate.py +0 -0
  306. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_macro_dashboard.py +0 -0
  307. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_macro_runner_smoke.py +0 -0
  308. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_macro_schema.py +0 -0
  309. {spacecore-0.4.2 → spacecore-0.4.3}/tests/bench/test_regimes.py +0 -0
  310. {spacecore-0.4.2 → spacecore-0.4.3}/tests/context/__init__.py +0 -0
  311. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/conftest.py +0 -0
  312. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/tools/test_battery_functionals.py +0 -0
  313. {spacecore-0.4.2 → spacecore-0.4.3}/tests/functional/tools/test_least_squares.py +0 -0
  314. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/__init__.py +0 -0
  315. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_params.py +0 -0
  316. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_protocol.py +0 -0
  317. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/_seed.py +0 -0
  318. {spacecore-0.4.2 → spacecore-0.4.3}/tests/generators/test_smoke.py +0 -0
  319. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/__init__.py +0 -0
  320. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/test_github_labels.py +0 -0
  321. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/test_smoke_jax.py +0 -0
  322. {spacecore-0.4.2 → spacecore-0.4.3}/tests/integration/test_smoke_numpy.py +0 -0
  323. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/__init__.py +0 -0
  324. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/conftest.py +0 -0
  325. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/test_core_kernel_dispatch.py +0 -0
  326. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/test_kernel_registry.py +0 -0
  327. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/test_kernel_spec.py +0 -0
  328. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/test_kernels_match_generic.py +0 -0
  329. {spacecore-0.4.2 → spacecore-0.4.3}/tests/kernels/test_materialized_cache.py +0 -0
  330. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/__init__.py +0 -0
  331. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/conftest.py +0 -0
  332. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_cg.py +0 -0
  333. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_expm_multiply.py +0 -0
  334. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_lanczos.py +0 -0
  335. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_lsqr.py +0 -0
  336. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_power_iteration.py +0 -0
  337. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linalg/test_result_types.py +0 -0
  338. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/__init__.py +0 -0
  339. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/conftest.py +0 -0
  340. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_diagonal_linop.py +0 -0
  341. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_fuse.py +0 -0
  342. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_generated_linop_laws.py +0 -0
  343. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_linop_base.py +0 -0
  344. {spacecore-0.4.2 → spacecore-0.4.3}/tests/linops/test_tree_linop_base.py +0 -0
  345. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optim/__init__.py +0 -0
  346. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/__init__.py +0 -0
  347. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/_helpers.py +0 -0
  348. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/test_contracts.py +0 -0
  349. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/test_line_search_scipy.py +0 -0
  350. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/test_metric_handoff.py +0 -0
  351. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/test_minimize_optax.py +0 -0
  352. {spacecore-0.4.2 → spacecore-0.4.3}/tests/optimize/test_minimize_scipy.py +0 -0
  353. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/__init__.py +0 -0
  354. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/_generated_helpers.py +0 -0
  355. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/conftest.py +0 -0
  356. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_coordinate_space_base.py +0 -0
  357. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_dense_vector_space.py +0 -0
  358. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_generated_dense_vector.py +0 -0
  359. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_generated_inner_product.py +0 -0
  360. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_generated_jordan_space.py +0 -0
  361. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_generated_space_laws.py +0 -0
  362. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_generated_tree_space.py +0 -0
  363. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_inner_product.py +0 -0
  364. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_inner_product_space_base.py +0 -0
  365. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_star_space_base.py +0 -0
  366. {spacecore-0.4.2 → spacecore-0.4.3}/tests/spaces/test_vector_space_base.py +0 -0
  367. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/01_backend_and_context.ipynb +0 -0
  368. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/02_linear_algebra.ipynb +0 -0
  369. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/03_functionals.ipynb +0 -0
  370. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/04_tree_spaces.ipynb +0 -0
  371. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/05_weighted_tikhonov.ipynb +0 -0
  372. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/06_optimal_transport.ipynb +0 -0
  373. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/07_manifold_descent.ipynb +0 -0
  374. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/08_pdhg_conic_program.ipynb +0 -0
  375. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/09_kernels_and_fusion.ipynb +0 -0
  376. {spacecore-0.4.2 → spacecore-0.4.3}/tutorials/README.md +0 -0
@@ -7,6 +7,202 @@ and the project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.3] — 2026-09-12
11
+
12
+ ### Removed
13
+
14
+ - **`jax_pytree_class` is removed from the public API** (breaking). Pytree
15
+ registration is no longer a JAX-named decorator applied by hand, but a
16
+ backend-neutral registry: containers inherit `PyTreeNode` and each backend
17
+ installs its own tree protocol (see *Added*). No replacement decorator is
18
+ exported — a class becomes a pytree node by being a concrete `LinOp`,
19
+ `Functional`, or one of the three pytree `Space` types. Code that decorated its
20
+ own class should inherit `spacecore.backend.PyTreeNode` instead.
21
+ - **`SpectralLpNormFunctional` is removed** (breaking). A per-formula spectral
22
+ class duplicates the value, the gradient, the pytree methods and the validation
23
+ of its coordinate twin — it re-checked `p >= 1` that `LpNormFunctional` already
24
+ enforces. The Schatten `p`-norm is now the spectral *lift* of the coordinate
25
+ `p`-norm:
26
+
27
+ ```python
28
+ # before
29
+ f = sc.SpectralLpNormFunctional(X, p)
30
+ # after
31
+ f = sc.spectralize(X, lambda s: sc.LpNormFunctional(s, p))
32
+ ```
33
+
34
+ `NuclearNormFunctional(X)` is unchanged as a named constructor and now returns a
35
+ `SpectralFunctional` whose `base` is `LpNormFunctional(s, 1.0)`; code reading
36
+ `f.p` should read `f.base.p`.
37
+
38
+ ### Added
39
+
40
+ - **Backend-neutral pytree registration.** `PyTreeNode` (the capability mixin
41
+ owning the `tree_flatten` / `tree_unflatten` contract), `PyTreeRegistry`, and
42
+ `BackendOps.install_pytree_protocol()` — a plugin hook, no-op by default, that a
43
+ backend implements to teach its transform machinery to see inside SpaceCore
44
+ containers. The registry wires the class × backend cross-product with
45
+ back-fill in both directions, so registration no longer depends on import order.
46
+ `LinOp` and `Functional` now carry the capability on their bases, so every
47
+ concrete subclass — including user-defined ones — is registered automatically.
48
+ - **Torch is now a transform-capable backend.** `TorchOps.install_pytree_protocol`
49
+ registers containers with `torch.utils._pytree`, so a Torch-backed operator can
50
+ flow through `torch.compile` and `torch.func` as a traced argument rather than an
51
+ opaque leaf. One registration also covers `torch.utils._cxx_pytree`.
52
+ - **`SpectralFunctional`, `spectralize`, `eigenvalue_space`** — lift **any**
53
+ symmetric coordinate functional onto a Jordan spectrum (Lewis's theorem), instead
54
+ of hand-writing a class per formula. `spectralize(X, NegativeEntropyFunctional)`
55
+ is the von Neumann entropy; `SquaredL2NormFunctional` lifts to the squared
56
+ Frobenius norm, `HuberFunctional` to its spectral analogue. The base functional
57
+ must be symmetric — eigenvalues have no canonical order, so a non-symmetric `f`
58
+ makes `f(lambda(X))` ill-defined rather than merely mis-differentiated.
59
+ - **`RealifiedFunctional` and `realify`** — view a complex-domain functional over
60
+ stacked real coordinates `(Re v, Im v)`, for optimizers that assume a real vector
61
+ space. The real gradient is `(Re g_v, Im g_v)` with `g_v` the *coordinate*
62
+ gradient, so the metric correction stays where ADR-010 put it. `realify` is a
63
+ no-op on an already-real domain.
64
+ - **`OpsRegistry`** — the backend registry extracted out of `Contextual`, which now
65
+ holds ambient policy only. Instantiable, so registration is testable without the
66
+ process-wide singleton; `register_ops` still raises `ContextConflictError` on a
67
+ duplicate family.
68
+ - **`BackendOps.complex_dtype`** — the inverse of `real_dtype`. Deriving it at the
69
+ call site is not portable: NumPy promotes `float32` against a Python complex to
70
+ `complex128`, while JAX and Torch give `complex64`.
71
+
72
+ - **`ProductFunctional` and `make_functional_product`** — the functional algebra
73
+ becomes multiplicative. `F * G` is the pointwise product `F(x) * G(x)` on a
74
+ shared domain, with the product-rule Riesz gradient
75
+
76
+ ```
77
+ grad(F·G)(x) = conj(G(x))·grad F(x) + conj(F(x))·grad G(x)
78
+ ```
79
+
80
+ combined through the domain's own `scale`/`add` (a domain element may be a
81
+ pytree), and a `value_and_grad` that evaluates each factor once. Deliberately
82
+ **binary**: the product rule is a two-factor law, and `(F*G)*H` expresses the
83
+ n-ary case at the same cost. The conjugations are identities for the usual
84
+ real-valued factors.
85
+ - **`ConstantFunctional` and `make_constant_functional`** — the constant map
86
+ `x -> c`, with zero gradient. This is the embedding of a scalar into the
87
+ functional algebra, previously unrepresentable: the algebra had a zero element
88
+ and an affine shift but no constant node. `make_constant_functional` collapses
89
+ `c = 0` to `ZeroFunctional` so the additive identity keeps one representation.
90
+ - **`spacecore.opfamily`: `OperatorFamily`, `FunctionalScaledOperator`,
91
+ `make_functional_scaled_operator`** — `F * A` for a `Functional` and a `LinOp`
92
+ is the functional-weighted map `m(x) = F(x) A x`. That map is **not linear**
93
+ (both the scale and the direction move with `x`), so it is deliberately *not* a
94
+ `LinOp`; it is a point-indexed *family* `x -> A_x`, of which a `LinOp` is the
95
+ constant case. Freezing the point recovers linearity, and each point carries
96
+ two different operators:
97
+
98
+ - `m.at(x)` — the frozen member `F(x) · A`, an ordinary `ScaledLinOp` that
99
+ composes, sums and has an adjoint;
100
+ - `m.linearize_at(x)` — the derivative `Dm(x)[h] = <grad F(x), h>·Ax + F(x)·A h`,
101
+ with metric adjoint `<Ax, w>_Y·grad F(x) + conj(F(x))·A^# w`, for Newton-type
102
+ steps.
103
+
104
+ They differ by exactly a rank-one term and coincide only when `F` is constant.
105
+ A `ConstantFunctional` weight collapses to a plain `ScaledLinOp`, so the linear
106
+ case is never forced through the non-linear type. The module is top-level
107
+ because it depends on both `linop` and `functional`, and neither depends on it.
108
+ - **`checked_method(out_scalar=True)` / `out_batched_scalar=True`** — the codomain
109
+ check for a `Functional`. `out_space=` names an attribute holding a `Space`, and
110
+ a functional's codomain is the scalar *field*, reported only as a string, so the
111
+ decorator that guards every `LinOp` output had nothing to bind to and the check
112
+ was hand-written per subclass. `out_scalar` asserts `shape == ()`;
113
+ `out_batched_scalar` asserts `(N,)` with `N` read from the input named by
114
+ `in_space`, which it therefore requires.
115
+ - **`Space.scalar_field`, `Space.declared_scalar_field`, `Space.check_scalar`** —
116
+ the field of scalars a space is closed under, *declared* rather than inferred
117
+ from the dtype. It defaults to `field`; `HermitianSpace` declares `"real"`,
118
+ because complex Hermitian matrices have complex entries but form a **real**
119
+ vector space (`i·H` is anti-Hermitian).
120
+
121
+ ### Changed
122
+
123
+ - **`check_level` is a property of the bound object, not of `Context`** (breaking).
124
+ `Context(ops, dtype=..., check_level=...)` and the deprecated `enable_checks=`
125
+ argument are gone; `Context` is now exactly `(ops, dtype)`. Pass `check_level=`
126
+ to the space / operator / functional constructor, or set it ambiently with
127
+ `set_check_level` / `use_check_level`. Two contexts differing only in strictness
128
+ are now correctly the same context.
129
+ - **`spacecore._contextual` is now `spacecore.contextual`** (breaking), and
130
+ `Context` moved out of `spacecore.backend`. The top-level `spacecore.Context`
131
+ re-export is unchanged.
132
+ - **Ambient context and check level are scoped with `contextvars`.**
133
+ `use_context` / `use_check_level` install a `ContextVar` override unwound by
134
+ `Token`, so nesting is exact and concurrent threads or async tasks cannot clobber
135
+ one another. `set_context` / `set_check_level` still write the process-wide
136
+ baseline. The backend registry deliberately stays global — scoping it would make
137
+ a backend registered inside a `with` block vanish on exit.
138
+ - **`available_ops()` is memoized and returns a tuple** rather than a list.
139
+ Discovery attempts a real import per optional backend and scans entry-point
140
+ metadata; it ran three times per `import spacecore`. A tuple because the cached
141
+ value is shared.
142
+
143
+ - **`Functional.__mul__` / `__rmul__` now dispatch on the operand type.** A scalar
144
+ still gives `ScaledFunctional`; a `Functional` now gives the pointwise product
145
+ and a `LinOp` the functional-weighted family. Previously both returned
146
+ `NotImplemented`, so `F * G` and `F * A` raised `TypeError`. Operands that are
147
+ neither scalar-like nor `Functional` nor `LinOp` still defer to the reflected
148
+ operation.
149
+ - **Functional outputs are checked as scalars at `standard` and above.** 17
150
+ `value` and 3 `vvalue` implementations carry the new decorator flags, replacing
151
+ four hand-written `_checks_at_least("standard")` / `_check_scalar_shape` bodies
152
+ with one implementation. The level matches what those call sites already used —
153
+ `cheap` deliberately does not run it. Cost is ~0.5 µs per `value` call, and
154
+ nothing at `check_level="none"`, which still short-circuits before any check.
155
+ `MatrixFreeLinearFunctional.vvalue` keeps its own check: it permits several
156
+ leading batch axes, which is broader than the single-axis contract `vvalue`
157
+ documents. `RealifiedFunctional.value` is checked on its output only — its input
158
+ is validated against the *complex* domain by the inner functional.
159
+ - **`_check_scalar_shape` distinguishes single from batched output** in its error
160
+ message ("Expected scalar output" vs "Expected scalar batch output"). It said
161
+ "batch" unconditionally, which was harmless while it guarded four batched paths
162
+ and misleading now that it guards ~20 mostly single-element ones.
163
+ - **`HermitianSpace.scale` / `scale_batch` reject a non-real multiplier**
164
+ (breaking, at `standard` and above). `scale(1j, H)` returned a skew-Hermitian
165
+ array still typed as an element of `Herm(n)`; the error surfaced at some later
166
+ membership check, or never at `check_level="none"`. Only a *provably* non-real
167
+ multiplier is refused, so a traced scalar under `jax.jit` still passes. `field`
168
+ remains dtype-derived and still drives equality and repr: a real-dtype and a
169
+ complex-dtype `Herm(n)` are genuinely different spaces.
170
+
171
+ ### Fixed
172
+
173
+ - **A present-but-broken optional backend no longer aborts `import spacecore`.**
174
+ `spacecore.backend` eagerly imported the JAX subpackage to reach
175
+ `jax_pytree_class`, so an installed-but-unimportable backend raising anything
176
+ other than `ModuleNotFoundError` — a shadowed `cupy`, a partially-installed
177
+ `jax` — propagated out of the import. Discovery is now the only path into a
178
+ backend package, and it warns and skips instead.
179
+ - **One broken backend now warns once, not once per discovery call.**
180
+
181
+ - **`ComposedFunctional` now has a gradient.** `F.compose(A).grad(x)` raised
182
+ `NotImplementedError`: the node implemented `value` but not the chain rule,
183
+ though `LinOp.rapply` already provides the metric adjoint it needs. Added
184
+ `grad`, a fused `value_and_grad`, and a batched `vgrad`, with cores registered
185
+ in the `composed-functional` kernel set:
186
+
187
+ ```
188
+ grad(F o A)(x) = A^#(grad F(A x))
189
+ ```
190
+
191
+ `rapply` **is** `A^#` (ADR-009), so no Riesz map is applied on top of it —
192
+ that would count the geometry twice — and no explicit conjugation appears,
193
+ because the adjoint identity absorbs it. `value_and_grad` applies `A` once and
194
+ shares the image, where the inherited default applied it twice. The typed
195
+ specializations in `make_functional_composed` (`InnerProductFunctional`,
196
+ `LinOpQuadraticForm`) were unaffected, and are used as a cross-check on the
197
+ generic node.
198
+ - **`scalar_eq` no longer swallows every exception.** It wrapped its comparison in
199
+ a bare `except Exception: return False`, so a raising `__eq__` was silently
200
+ reported as inequality. Narrowed to `TypeError` — the base class of JAX's
201
+ `TracerBoolConversionError` and of any "cannot reduce to a concrete bool"
202
+ failure — which keeps the intended verdict for an abstract scalar (undecidable,
203
+ so canonicalization is skipped and the expression tree stays unfolded but
204
+ correct) while letting a genuinely broken `__eq__` propagate.
205
+
10
206
  ## [0.4.2] — 2026-07-01
11
207
 
12
208
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spacecore
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Backend-agnostic vector spaces and linear operators.
5
5
  Author: Pavlo Pelikh
6
6
  License-Expression: Apache-2.0
@@ -226,7 +226,8 @@ level) adds named constructors over that machinery, with no new core types:
226
226
  `least_squares` for `½‖Ax−b‖²`, coordinate norms (`SquaredL2NormFunctional`,
227
227
  `LpNormFunctional`, `L1NormFunctional`), `NegativeEntropyFunctional`,
228
228
  `KLDivergenceFunctional`, `HuberFunctional`, the spectral
229
- `SpectralLpNormFunctional`/`NuclearNormFunctional`, and the metric-aware
229
+ `NuclearNormFunctional` (and `spectralize` / `SpectralFunctional` to lift any
230
+ coordinate functional to a spectral one), and the metric-aware
230
231
  proximal primitive `generalized_shrinkage` with the wrappers `prox_l1`,
231
232
  `prox_l2sq`, and `project_nonneg`. Each objective's gradient is the metric
232
233
  (Riesz) gradient under the domain geometry, and the proximal step is taken in
@@ -260,21 +261,29 @@ and [deviation catalog](https://pavlo3p.github.io/SpaceCore/design/backend_devia
260
261
 
261
262
  ## Validation Policy
262
263
 
263
- A `Context` carries a `check_level` that determines how aggressively spaces,
264
- operators, functionals, and solver preconditions validate their inputs. The
265
- ordered levels are `CHECK_LEVELS = ("none", "cheap", "standard", "strict")`:
266
- `cheap` covers shape/dtype/backend/tree-structure, `standard` adds membership
267
- and Hermitian checks, and `strict` adds bounded expensive probes. Checks are
268
- opt-in per context, so hot paths can run unvalidated while development and tests
269
- run strict.
264
+ Every context-bound object — space, operator, functional — carries a
265
+ `check_level` that determines how aggressively it validates its inputs, together
266
+ with solver preconditions. The ordered levels are
267
+ `CHECK_LEVELS = ("none", "cheap", "standard", "strict")`: `cheap` covers
268
+ shape/dtype/backend/tree-structure, `standard` adds membership and Hermitian
269
+ checks, and `strict` adds bounded expensive probes. Checks are opt-in per
270
+ object, so hot paths can run unvalidated while development and tests run strict.
271
+
272
+ The level is a property of the bound object rather than of the `Context`: a
273
+ context fixes backend ops and dtype, while two objects sharing that backend and
274
+ dtype may legitimately validate at different strictness. Pass `check_level=` to
275
+ a constructor, or move the ambient default with `sc.set_check_level(...)` /
276
+ `sc.use_check_level(...)`.
270
277
 
271
278
  ```python
272
279
  import numpy as np
273
280
  import spacecore as sc
274
281
 
275
- ctx = sc.Context(sc.NumpyOps(), dtype=np.float64, check_level="standard")
276
- X = sc.DenseCoordinateSpace((2,), ctx)
277
- A = sc.DenseLinOp(ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx)
282
+ ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
283
+ X = sc.DenseCoordinateSpace((2,), ctx, check_level="standard")
284
+ A = sc.DenseLinOp(
285
+ ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx, check_level="standard"
286
+ )
278
287
 
279
288
  try:
280
289
  A.apply(ctx.asarray([1.0, 2.0, 3.0])) # wrong shape
@@ -180,7 +180,8 @@ level) adds named constructors over that machinery, with no new core types:
180
180
  `least_squares` for `½‖Ax−b‖²`, coordinate norms (`SquaredL2NormFunctional`,
181
181
  `LpNormFunctional`, `L1NormFunctional`), `NegativeEntropyFunctional`,
182
182
  `KLDivergenceFunctional`, `HuberFunctional`, the spectral
183
- `SpectralLpNormFunctional`/`NuclearNormFunctional`, and the metric-aware
183
+ `NuclearNormFunctional` (and `spectralize` / `SpectralFunctional` to lift any
184
+ coordinate functional to a spectral one), and the metric-aware
184
185
  proximal primitive `generalized_shrinkage` with the wrappers `prox_l1`,
185
186
  `prox_l2sq`, and `project_nonneg`. Each objective's gradient is the metric
186
187
  (Riesz) gradient under the domain geometry, and the proximal step is taken in
@@ -214,21 +215,29 @@ and [deviation catalog](https://pavlo3p.github.io/SpaceCore/design/backend_devia
214
215
 
215
216
  ## Validation Policy
216
217
 
217
- A `Context` carries a `check_level` that determines how aggressively spaces,
218
- operators, functionals, and solver preconditions validate their inputs. The
219
- ordered levels are `CHECK_LEVELS = ("none", "cheap", "standard", "strict")`:
220
- `cheap` covers shape/dtype/backend/tree-structure, `standard` adds membership
221
- and Hermitian checks, and `strict` adds bounded expensive probes. Checks are
222
- opt-in per context, so hot paths can run unvalidated while development and tests
223
- run strict.
218
+ Every context-bound object — space, operator, functional — carries a
219
+ `check_level` that determines how aggressively it validates its inputs, together
220
+ with solver preconditions. The ordered levels are
221
+ `CHECK_LEVELS = ("none", "cheap", "standard", "strict")`: `cheap` covers
222
+ shape/dtype/backend/tree-structure, `standard` adds membership and Hermitian
223
+ checks, and `strict` adds bounded expensive probes. Checks are opt-in per
224
+ object, so hot paths can run unvalidated while development and tests run strict.
225
+
226
+ The level is a property of the bound object rather than of the `Context`: a
227
+ context fixes backend ops and dtype, while two objects sharing that backend and
228
+ dtype may legitimately validate at different strictness. Pass `check_level=` to
229
+ a constructor, or move the ambient default with `sc.set_check_level(...)` /
230
+ `sc.use_check_level(...)`.
224
231
 
225
232
  ```python
226
233
  import numpy as np
227
234
  import spacecore as sc
228
235
 
229
- ctx = sc.Context(sc.NumpyOps(), dtype=np.float64, check_level="standard")
230
- X = sc.DenseCoordinateSpace((2,), ctx)
231
- A = sc.DenseLinOp(ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx)
236
+ ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
237
+ X = sc.DenseCoordinateSpace((2,), ctx, check_level="standard")
238
+ A = sc.DenseLinOp(
239
+ ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx, check_level="standard"
240
+ )
232
241
 
233
242
  try:
234
243
  A.apply(ctx.asarray([1.0, 2.0, 3.0])) # wrong shape
@@ -0,0 +1,61 @@
1
+ Context API
2
+ ===========
3
+
4
+ ``Context`` packages backend operations and the default dtype. Spaces,
5
+ operators, and functionals store a normalized context and use it for array
6
+ construction and conversion.
7
+
8
+ Validation policy is *not* part of a context: ``check_level`` belongs to the
9
+ bound object, so two objects on the same backend and dtype may validate at
10
+ different strictness. Use ``check_level="none"``, ``"cheap"``, ``"standard"``,
11
+ or ``"strict"`` on the object, or move the ambient default with
12
+ ``set_check_level`` / ``use_check_level``. The exported ``spacecore.CheckLevel``
13
+ literal is available for annotations. See :doc:`../design/checking_policy`.
14
+
15
+ Context
16
+ -------
17
+
18
+ .. autosummary::
19
+ :nosignatures:
20
+
21
+ spacecore.Context
22
+
23
+ .. autoclass:: spacecore.Context
24
+ :members:
25
+
26
+ Context helpers
27
+ ---------------
28
+
29
+ .. autosummary::
30
+ :nosignatures:
31
+
32
+ spacecore.get_context
33
+ spacecore.set_context
34
+ spacecore.use_context
35
+ spacecore.get_check_level
36
+ spacecore.set_check_level
37
+ spacecore.use_check_level
38
+ spacecore.normalize_context
39
+ spacecore.normalize_ops
40
+ spacecore.resolve_context_priority
41
+ spacecore.register_ops
42
+
43
+ * ``get_context`` and ``set_context`` manage the global default context, and
44
+ ``use_context`` overrides it for a block, scoped to the current thread or
45
+ async task.
46
+ * ``get_check_level`` / ``set_check_level`` / ``use_check_level`` do the same for
47
+ the ambient validation level applied to newly constructed bound objects.
48
+ * ``normalize_context`` turns backend names, families, concrete contexts, or ``None`` into a context.
49
+ * ``resolve_context_priority`` chooses a common context for constructors.
50
+ * ``register_ops`` adds a custom backend implementation.
51
+
52
+ .. autofunction:: spacecore.get_context
53
+ .. autofunction:: spacecore.set_context
54
+ .. autofunction:: spacecore.use_context
55
+ .. autofunction:: spacecore.get_check_level
56
+ .. autofunction:: spacecore.set_check_level
57
+ .. autofunction:: spacecore.use_check_level
58
+ .. autofunction:: spacecore.normalize_context
59
+ .. autofunction:: spacecore.normalize_ops
60
+ .. autofunction:: spacecore.resolve_context_priority
61
+ .. autofunction:: spacecore.register_ops
@@ -20,6 +20,69 @@ Base and composition
20
20
  * ``ComposedFunctional`` represents pullback ``f o A`` for a linear operator ``A``.
21
21
  * ``make_functional_composed`` constructs the same pullback with simplifications.
22
22
 
23
+ Algebra
24
+ -------
25
+
26
+ Lazy nodes for combining functionals. The operator overloads on ``Functional``
27
+ (``a * F``, ``F + G``, ``F - G``, ``-F``, ``F * G``) delegate to the ``make_*``
28
+ factories, which apply local, *structural* canonicalization — they read node
29
+ types, never values.
30
+
31
+ .. autosummary::
32
+ :nosignatures:
33
+
34
+ spacecore.functional.ScaledFunctional
35
+ spacecore.functional.SumFunctional
36
+ spacecore.functional.ShiftedFunctional
37
+ spacecore.functional.ZeroFunctional
38
+ spacecore.functional.ConstantFunctional
39
+ spacecore.functional.ProductFunctional
40
+ spacecore.functional.make_scaled_functional
41
+ spacecore.functional.make_functional_sum
42
+ spacecore.functional.make_shifted_functional
43
+ spacecore.functional.make_constant_functional
44
+ spacecore.functional.make_functional_product
45
+
46
+ * ``ScaledFunctional`` is ``a * F``; the Riesz gradient scales by ``conj(a)``.
47
+ * ``SumFunctional`` is ``F_1 + ... + F_n`` on a shared domain.
48
+ * ``ShiftedFunctional`` is the affine shift ``F + c`` (gradient unchanged).
49
+ * ``ZeroFunctional`` is the additive identity, recognized by the canonicalizers.
50
+ * ``ConstantFunctional`` is ``x -> c``, the embedding of a scalar into the
51
+ algebra; ``make_constant_functional`` collapses ``c = 0`` to ``ZeroFunctional``.
52
+ * ``ProductFunctional`` is the pointwise product ``F(x) * G(x)``, with the
53
+ product-rule Riesz gradient
54
+ :math:`\overline{G(x)}\, \nabla F(x) + \overline{F(x)}\, \nabla G(x)`. Because
55
+ functionals are scalar-valued, scaling is the constant-factor case of a
56
+ product: ``make_functional_product`` folds a ``ConstantFunctional`` factor back
57
+ into a ``ScaledFunctional``, and a ``ZeroFunctional`` factor to zero.
58
+
59
+ Operator families (``F · A``)
60
+ -----------------------------
61
+
62
+ .. autosummary::
63
+ :nosignatures:
64
+
65
+ spacecore.OperatorFamily
66
+ spacecore.FunctionalScaledOperator
67
+ spacecore.make_functional_scaled_operator
68
+
69
+ ``F * A`` for a ``Functional`` and a ``LinOp`` is the functional-weighted map
70
+ :math:`m(x) = F(x)\,Ax`. This is **not** linear — both the scale and the
71
+ direction move with ``x`` — so it is *not* a ``LinOp``; it is an
72
+ ``OperatorFamily``, a point-indexed family :math:`x \mapsto A_x`.
73
+
74
+ Each point carries two different linear operators, and they are not the same:
75
+
76
+ * ``m.at(x)`` — the **frozen member** ``F(x) · A``, an ordinary ``LinOp`` that
77
+ composes, sums, and has an adjoint. Freezing the point is what recovers
78
+ linearity.
79
+ * ``m.linearize_at(x)`` — the **derivative** :math:`Dm(x)`, for Newton-type
80
+ steps. They coincide only when ``F`` is constant; otherwise they differ by a
81
+ rank-one term.
82
+
83
+ A ``ConstantFunctional`` weight collapses to an ordinary ``ScaledLinOp``, so the
84
+ linear case is never forced through the non-linear type.
85
+
23
86
  Linear functionals
24
87
  ------------------
25
88
 
@@ -59,8 +122,12 @@ metric (Riesz) gradients under the domain geometry.
59
122
  spacecore.functional.SquaredL2NormFunctional
60
123
  spacecore.functional.LpNormFunctional
61
124
  spacecore.functional.L1NormFunctional
62
- spacecore.functional.SpectralLpNormFunctional
125
+ spacecore.functional.SpectralFunctional
126
+ spacecore.functional.spectralize
127
+ spacecore.functional.eigenvalue_space
63
128
  spacecore.functional.NuclearNormFunctional
129
+ spacecore.functional.RealifiedFunctional
130
+ spacecore.functional.realify
64
131
  spacecore.functional.NegativeEntropyFunctional
65
132
  spacecore.functional.KLDivergenceFunctional
66
133
  spacecore.functional.HuberFunctional
@@ -68,8 +135,14 @@ metric (Riesz) gradients under the domain geometry.
68
135
  * ``least_squares`` builds the ``scale ||A x - b||^2`` objective as a ``LinOpQuadraticForm``.
69
136
  * ``SquaredL2NormFunctional`` is ``1/2 ||x||_X^2`` (gradient ``x``, clean shrinkage prox).
70
137
  * ``LpNormFunctional`` / ``L1NormFunctional`` are coordinate ``p``-norms.
71
- * ``SpectralLpNormFunctional`` / ``NuclearNormFunctional`` are the Schatten ``p``-norm
72
- and nuclear norm of a Jordan spectrum (e.g. Hermitian eigenvalues).
138
+ * ``SpectralFunctional`` / ``spectralize`` lift **any** symmetric coordinate
139
+ functional onto a Jordan spectrum (Lewis): the Schatten ``p``-norm is
140
+ ``spectralize(X, lambda s: LpNormFunctional(s, p))``, the von Neumann entropy
141
+ is ``spectralize(X, NegativeEntropyFunctional)``. ``eigenvalue_space`` builds
142
+ the real space the spectrum lives in.
143
+ * ``NuclearNormFunctional`` is the named Schatten-1 case.
144
+ * ``RealifiedFunctional`` / ``realify`` present a complex-domain functional over
145
+ stacked real coordinates, for real-only optimizers.
73
146
  * ``NegativeEntropyFunctional`` and ``KLDivergenceFunctional`` are the entropy objectives.
74
147
  * ``HuberFunctional`` is the separable Huber loss.
75
148
 
@@ -137,11 +210,20 @@ Autodoc
137
210
 
138
211
  .. autofunction:: spacecore.functional.L1NormFunctional
139
212
 
140
- .. autoclass:: spacecore.functional.SpectralLpNormFunctional
213
+ .. autoclass:: spacecore.functional.SpectralFunctional
141
214
  :members:
142
215
 
216
+ .. autofunction:: spacecore.functional.spectralize
217
+
218
+ .. autofunction:: spacecore.functional.eigenvalue_space
219
+
143
220
  .. autofunction:: spacecore.functional.NuclearNormFunctional
144
221
 
222
+ .. autoclass:: spacecore.functional.RealifiedFunctional
223
+ :members:
224
+
225
+ .. autofunction:: spacecore.functional.realify
226
+
145
227
  .. autoclass:: spacecore.functional.NegativeEntropyFunctional
146
228
  :members:
147
229
 
@@ -1,18 +1,23 @@
1
1
  Checking policy
2
2
  ===============
3
3
 
4
- SpaceCore uses ``Context.check_level`` as its public runtime-validation policy.
5
- The public type is ``spacecore.CheckLevel``, a literal type with four ordered
6
- values: ``"none"``, ``"cheap"``, ``"standard"``, and ``"strict"``. A literal
7
- keeps context construction simple and makes invalid spellings visible to static
4
+ SpaceCore uses ``ContextBound.check_level`` as its public runtime-validation
5
+ policy. The public type is ``spacecore.CheckLevel``, a literal type with four
6
+ ordered values: ``"none"``, ``"cheap"``, ``"standard"``, and ``"strict"``. A
7
+ literal keeps construction simple and makes invalid spellings visible to static
8
8
  type checkers without introducing a separate policy object.
9
9
 
10
+ The level belongs to the *bound object* — space, operator, functional — and not
11
+ to the :class:`~spacecore.Context`. A context fixes backend ops and dtype; two
12
+ objects sharing both may still need different strictness, so the policy is set
13
+ per object, with an ambient default for objects that do not name one.
14
+
10
15
  .. code-block:: python
11
16
 
12
17
  import spacecore as sc
13
18
 
14
- ctx = sc.Context(sc.NumpyOps(), dtype="float64", check_level="standard")
15
- X = sc.DenseCoordinateSpace((3,), ctx=ctx)
19
+ ctx = sc.Context(sc.NumpyOps(), dtype="float64")
20
+ X = sc.DenseCoordinateSpace((3,), ctx=ctx, check_level="standard")
16
21
 
17
22
  x = X.ctx.asarray([1.0, 2.0, 3.0])
18
23
  X.check_member(x)
@@ -53,9 +58,11 @@ Choosing a level
53
58
  * Performance-sensitive trusted code: use ``"cheap"`` or ``"none"``.
54
59
  * User-facing libraries: usually use ``"standard"``.
55
60
 
56
- The process-wide default context remains ``"none"`` for compatibility. A
57
- direct ``Context(...)`` defaults to ``"standard"``, matching the previous
58
- direct-constructor default.
61
+ An object constructed without an explicit ``check_level`` takes the ambient
62
+ default, read with :func:`spacecore.get_check_level`. Move that default
63
+ process-wide with :func:`spacecore.set_check_level`, or for a block with
64
+ :func:`spacecore.use_check_level`, which is scoped to the current thread or
65
+ async task.
59
66
 
60
67
  Where checks run
61
68
  ----------------
@@ -71,25 +78,26 @@ When a context is inferred from several source objects, SpaceCore selects the
71
78
  least expensive source level. For example, combining ``"strict"`` and
72
79
  ``"cheap"`` contexts produces a ``"cheap"`` inferred policy.
73
80
 
74
- Migration from ``enable_checks``
75
- --------------------------------
76
-
77
- ``enable_checks`` remains as a deprecated compatibility keyword:
81
+ Migration from context-carried levels
82
+ -------------------------------------
78
83
 
79
- * ``enable_checks=True`` maps to ``check_level="standard"``;
80
- * ``enable_checks=False`` maps to ``check_level="none"``;
81
- * passing both keywords raises ``TypeError``.
84
+ Before 0.4.3 the level rode on the ``Context``, and the long-deprecated
85
+ ``enable_checks=`` Boolean was still accepted there. Both are gone: ``Context``
86
+ is now exactly ``(ops, dtype)``, and passing either keyword to it raises
87
+ ``TypeError``.
82
88
 
83
89
  .. code-block:: python
84
90
 
85
- # New spelling
86
- ctx = sc.Context(sc.NumpyOps(), check_level="standard")
91
+ # 0.4.3 and later: the level is set on the object
92
+ ctx = sc.Context(sc.NumpyOps())
93
+ X = sc.DenseCoordinateSpace((3,), ctx=ctx, check_level="standard")
87
94
 
88
- # Deprecated equivalent
89
- legacy_ctx = sc.Context(sc.NumpyOps(), enable_checks=True)
95
+ # ...or moved for everything constructed in a block
96
+ with sc.use_check_level("strict"):
97
+ Y = sc.DenseCoordinateSpace((3,), ctx=ctx)
90
98
 
91
- ``ctx.enable_checks`` remains a deprecated Boolean view and is true for
92
- ``cheap``, ``standard``, and ``strict`` contexts.
99
+ The level is always one of the four literals; the Boolean spelling is not
100
+ accepted in its place. Read an object's level back from ``obj.check_level``.
93
101
 
94
102
  Implementation convention
95
103
  -------------------------