sunnbear 0.2.2__tar.gz → 0.2.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 (148) hide show
  1. {sunnbear-0.2.2 → sunnbear-0.2.3}/.gitignore +0 -3
  2. {sunnbear-0.2.2 → sunnbear-0.2.3}/PKG-INFO +22 -12
  3. {sunnbear-0.2.2 → sunnbear-0.2.3}/pyproject.toml +33 -26
  4. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/protocol/correctness.py +7 -4
  5. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/anderson_bjorck/state.py +3 -5
  6. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/brent/solver.py +6 -10
  7. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/illinois/state.py +3 -2
  8. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/itp/solver.py +7 -5
  9. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/modab/solver.py +10 -10
  10. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/pegasus/state.py +3 -5
  11. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/toms748/solver.py +5 -9
  12. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/interval.py +3 -3
  13. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/solver.py +5 -5
  14. sunnbear-0.2.3/src/sunnbear/_core/utils/floats.py +7 -0
  15. {sunnbear-0.2.2 → sunnbear-0.2.3}/LICENSE +0 -0
  16. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/__init__.py +0 -0
  17. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/__init__.py +0 -0
  18. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/__init__.py +0 -0
  19. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/archiver.py +0 -0
  20. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/builtin/mc_tuples/manifest.json +0 -0
  21. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/builtin/mc_tuples/mc_tuples.csv +0 -0
  22. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/data_release_client.py +0 -0
  23. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/declaration.py +0 -0
  24. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/exceptions.py +0 -0
  25. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/listing.py +0 -0
  26. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/manifest.py +0 -0
  27. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/registry.py +0 -0
  28. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/source.py +0 -0
  29. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/artifacts/store.py +0 -0
  30. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/__init__.py +0 -0
  31. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/aggregation/__init__.py +0 -0
  32. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/aggregation/derived_results.py +0 -0
  33. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/aggregation/summary.py +0 -0
  34. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/__init__.py +0 -0
  35. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/artifact.py +0 -0
  36. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/construction.py +0 -0
  37. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/construction_allocation.py +0 -0
  38. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/construction_correction.py +0 -0
  39. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/construction_population.py +0 -0
  40. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/construction_result.py +0 -0
  41. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/construction_solve.py +0 -0
  42. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/exceptions.py +0 -0
  43. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/sizes.py +0 -0
  44. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/mc_tuples/tuples.py +0 -0
  45. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/protocol/__init__.py +0 -0
  46. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/protocol/seeds.py +0 -0
  47. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/protocol/tolerances.py +0 -0
  48. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/__init__.py +0 -0
  49. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/exceptions.py +0 -0
  50. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/results_schema.py +0 -0
  51. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/run_dir.py +0 -0
  52. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/run_info.py +0 -0
  53. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/run_settings.py +0 -0
  54. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/runner.py +0 -0
  55. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/task.py +0 -0
  56. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/benchmark/runner/worker_pool.py +0 -0
  57. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/exceptions/__init__.py +0 -0
  58. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/exceptions/base.py +0 -0
  59. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/__init__.py +0 -0
  60. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/__init__.py +0 -0
  61. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c01_existing_benchmark_suites/__init__.py +0 -0
  62. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c02_standard_function_families/__init__.py +0 -0
  63. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c02_standard_function_families/c01_polynomials/__init__.py +0 -0
  64. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c02_standard_function_families/c01_polynomials/f01_cubic.py +0 -0
  65. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c02_standard_function_families/c01_polynomials/f02_odd_power.py +0 -0
  66. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c03_purpose_built_functions/__init__.py +0 -0
  67. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/catalog/c99_user_defined_functions/__init__.py +0 -0
  68. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/__init__.py +0 -0
  69. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/category.py +0 -0
  70. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/exceptions.py +0 -0
  71. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/formula.py +0 -0
  72. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/identity.py +0 -0
  73. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/param_values.py +0 -0
  74. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/recipes.py +0 -0
  75. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/registry.py +0 -0
  76. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/taxonomy.py +0 -0
  77. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/test_cases.py +0 -0
  78. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/test_function.py +0 -0
  79. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/functions/core/types.py +0 -0
  80. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/__init__.py +0 -0
  81. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/__init__.py +0 -0
  82. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/anderson_bjorck/__init__.py +0 -0
  83. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/anderson_bjorck/configs.py +0 -0
  84. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/anderson_bjorck/solver.py +0 -0
  85. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/bisection/__init__.py +0 -0
  86. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/bisection/configs.py +0 -0
  87. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/bisection/solver.py +0 -0
  88. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/brent/__init__.py +0 -0
  89. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/brent/configs.py +0 -0
  90. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/carf/__init__.py +0 -0
  91. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/carf/configs.py +0 -0
  92. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/carf/solver.py +0 -0
  93. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/chandrupatla/__init__.py +0 -0
  94. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/chandrupatla/configs.py +0 -0
  95. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/chandrupatla/solver.py +0 -0
  96. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/illinois/__init__.py +0 -0
  97. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/illinois/configs.py +0 -0
  98. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/illinois/solver.py +0 -0
  99. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/itp/__init__.py +0 -0
  100. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/itp/configs.py +0 -0
  101. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/itp/state.py +0 -0
  102. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/itp/variant.py +0 -0
  103. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/modab/__init__.py +0 -0
  104. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/modab/configs.py +0 -0
  105. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/pegasus/__init__.py +0 -0
  106. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/pegasus/configs.py +0 -0
  107. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/pegasus/solver.py +0 -0
  108. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/regula_falsi/__init__.py +0 -0
  109. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/regula_falsi/configs.py +0 -0
  110. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/regula_falsi/solver.py +0 -0
  111. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/ridders/__init__.py +0 -0
  112. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/ridders/configs.py +0 -0
  113. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/ridders/solver.py +0 -0
  114. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/ridders/variant.py +0 -0
  115. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/steffen_brent/__init__.py +0 -0
  116. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/steffen_brent/configs.py +0 -0
  117. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/steffen_brent/solver.py +0 -0
  118. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/toms748/__init__.py +0 -0
  119. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/bracketing/toms748/configs.py +0 -0
  120. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/__init__.py +0 -0
  121. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/config.py +0 -0
  122. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/exceptions.py +0 -0
  123. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/registry.py +0 -0
  124. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/result.py +0 -0
  125. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/role.py +0 -0
  126. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/state.py +0 -0
  127. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/core/wrapped_function.py +0 -0
  128. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/open/__init__.py +0 -0
  129. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/open/secant/__init__.py +0 -0
  130. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/open/secant/configs.py +0 -0
  131. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/solvers/open/secant/solver.py +0 -0
  132. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/stats/__init__.py +0 -0
  133. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/stats/pairwise_l1.py +0 -0
  134. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/stats/pseudo_quantiles/__init__.py +0 -0
  135. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/stats/pseudo_quantiles/geometric_pseudo_quantile.py +0 -0
  136. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/stats/pseudo_quantiles/ordered_weighted_geomean.py +0 -0
  137. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/utils/__init__.py +0 -0
  138. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/utils/class_origin.py +0 -0
  139. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/utils/grouping.py +0 -0
  140. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/utils/mapping_comparison.py +0 -0
  141. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/_core/utils/slugify.py +0 -0
  142. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/benchmark.py +0 -0
  143. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/data.py +0 -0
  144. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/exceptions.py +0 -0
  145. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/functions.py +0 -0
  146. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/py.typed +0 -0
  147. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/solvers.py +0 -0
  148. {sunnbear-0.2.2 → sunnbear-0.2.3}/src/sunnbear/stats.py +0 -0
@@ -12,9 +12,6 @@ __pycache__
12
12
  .coverage.*
13
13
  htmlcov
14
14
 
15
- # Jupyter Notebook checkpoints
16
- .ipynb_checkpoints
17
-
18
15
  # Virtual environment directories
19
16
  venv
20
17
  .env
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sunnbear
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Under construction.
5
5
  Project-URL: Source, https://github.com/bertpl/sunnbear
6
6
  Project-URL: ChangeLog, https://github.com/bertpl/sunnbear/blob/main/CHANGELOG.md
@@ -16,34 +16,44 @@ Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
19
20
  Classifier: Topic :: Scientific/Engineering :: Mathematics
20
21
  Classifier: Typing :: Typed
21
22
  Requires-Python: >=3.12
22
23
  Requires-Dist: backports-zstd>=1.0.0; python_version < '3.14'
23
- Requires-Dist: counted-float>=1.2.1
24
+ Requires-Dist: counted-float>=1.2.1; python_version < '3.15'
25
+ Requires-Dist: counted-float>=2.6.0; python_version >= '3.15'
24
26
  Requires-Dist: matplotlib>=3.10.7; python_version == '3.12'
25
27
  Requires-Dist: matplotlib>=3.10.7; python_version == '3.13'
26
- Requires-Dist: matplotlib>=3.10.7; python_version >= '3.14'
27
- Requires-Dist: max-div>=0.21.1
28
- Requires-Dist: mpl-composite>=0.1.2
28
+ Requires-Dist: matplotlib>=3.10.7; python_version == '3.14'
29
+ Requires-Dist: matplotlib>=3.11.2; python_version >= '3.15'
30
+ Requires-Dist: max-div>=0.21.1; python_version < '3.15'
31
+ Requires-Dist: max-div>=0.21.3; python_version >= '3.15'
32
+ Requires-Dist: mpl-composite>=0.1.2; python_version < '3.15'
33
+ Requires-Dist: mpl-composite>=0.1.4; python_version >= '3.15'
29
34
  Requires-Dist: numba>=0.60.0; python_version == '3.12'
30
35
  Requires-Dist: numba>=0.66.0; python_version == '3.13'
31
- Requires-Dist: numba>=0.66.0; python_version >= '3.14'
36
+ Requires-Dist: numba>=0.66.0; python_version == '3.14'
37
+ Requires-Dist: numba>=0.68.0; python_version >= '3.15'
32
38
  Requires-Dist: numpy>=2.0.0; python_version == '3.12'
33
39
  Requires-Dist: numpy>=2.1.0; python_version == '3.13'
34
- Requires-Dist: numpy>=2.3.2; python_version >= '3.14'
40
+ Requires-Dist: numpy>=2.3.2; python_version == '3.14'
41
+ Requires-Dist: numpy>=2.5.2; python_version >= '3.15'
35
42
  Requires-Dist: platformdirs>=4.0
36
- Requires-Dist: polars>=1.0.0
37
- Requires-Dist: pydantic>=2.12; python_version >= '3.14'
43
+ Requires-Dist: polars>=1.0.0; python_version < '3.15'
44
+ Requires-Dist: polars>=2.0.0; python_version >= '3.15'
45
+ Requires-Dist: pydantic>=2.12; python_version == '3.14'
46
+ Requires-Dist: pydantic>=2.14; python_version >= '3.15'
38
47
  Requires-Dist: pydantic>=2.7; python_version == '3.12'
39
48
  Requires-Dist: pydantic>=2.9; python_version == '3.13'
40
- Requires-Dist: snuffled>=0.1.6
49
+ Requires-Dist: snuffled>=0.1.6; python_version < '3.15'
50
+ Requires-Dist: snuffled>=0.1.9; python_version >= '3.15'
41
51
  Requires-Dist: typing-extensions>=4.4
42
52
  Description-Content-Type: text/markdown
43
53
 
44
54
  [![CI](https://img.shields.io/github/actions/workflow/status/bertpl/sunnbear/push_to_main.yml?branch=main&label=CI)](https://github.com/bertpl/sunnbear/actions/workflows/push_to_main.yml)
45
55
  [![Coverage](https://img.shields.io/badge/coverage-100.00%25-brightgreen)](https://github.com/bertpl/sunnbear/actions/workflows/push_to_main.yml)
46
- [![Tests](https://img.shields.io/badge/tests-1439-blue)](https://github.com/bertpl/sunnbear/actions/workflows/push_to_main.yml)
56
+ [![Tests](https://img.shields.io/badge/tests-1451-blue)](https://github.com/bertpl/sunnbear/actions/workflows/push_to_main.yml)
47
57
  [![PyPI](https://img.shields.io/pypi/v/sunnbear.svg)](https://pypi.org/project/sunnbear/)
48
58
  [![Python](https://img.shields.io/pypi/pyversions/sunnbear.svg)](https://pypi.org/project/sunnbear/)
49
59
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](https://github.com/bertpl/sunnbear/blob/main/LICENSE)
@@ -54,4 +64,4 @@ Description-Content-Type: text/markdown
54
64
 
55
65
  Under construction.
56
66
 
57
- ![sunnbear splash](https://raw.githubusercontent.com/bertpl/sunnbear/v0.2.2/images/splash_with_version.webp)
67
+ ![sunnbear splash](https://raw.githubusercontent.com/bertpl/sunnbear/v0.2.3/images/splash_with_version.webp)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sunnbear"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Under construction."
5
5
  # readme is dynamic: hatch-fancy-pypi-readme builds the PyPI long-description from README.md,
6
6
  # rewriting the relative splash link to a tag-pinned absolute URL (config below).
@@ -15,31 +15,45 @@ dependencies = [
15
15
  # Per-Python minimal versions: each is the earliest version shipping a wheel for that
16
16
  # CPython (and, for numpy, admitted by the numba minimal version on the same interpreter),
17
17
  # so `lowest-direct` installs (not source-builds) across the whole matrix.
18
+ #
19
+ # A pure-Python package ships 1 wheel that installs on every Python version, so the rule above does
20
+ # not apply to it: where its minimal version does not declare 3.15 support, a separate dependency line
21
+ # for 3.15 raises its minimal version to the earliest release that does.
18
22
  "numpy>=2.0.0; python_version == '3.12'",
19
23
  "numpy>=2.1.0; python_version == '3.13'",
20
- "numpy>=2.3.2; python_version >= '3.14'",
24
+ "numpy>=2.3.2; python_version == '3.14'",
25
+ "numpy>=2.5.2; python_version >= '3.15'",
21
26
  "numba>=0.60.0; python_version == '3.12'",
22
27
  "numba>=0.66.0; python_version == '3.13'",
23
- "numba>=0.66.0; python_version >= '3.14'",
28
+ "numba>=0.66.0; python_version == '3.14'",
29
+ "numba>=0.68.0; python_version >= '3.15'",
24
30
  "matplotlib>=3.10.7; python_version == '3.12'",
25
31
  "matplotlib>=3.10.7; python_version == '3.13'",
26
- "matplotlib>=3.10.7; python_version >= '3.14'",
27
- "counted-float>=1.2.1",
32
+ "matplotlib>=3.10.7; python_version == '3.14'",
33
+ "matplotlib>=3.11.2; python_version >= '3.15'",
34
+ "counted-float>=1.2.1; python_version < '3.15'",
35
+ "counted-float>=2.6.0; python_version >= '3.15'",
28
36
  # These pydantic minimal versions equal counted-float's own pydantic minimal versions; counted-float
29
37
  # depends on pydantic, so every install already satisfies them.
30
38
  "pydantic>=2.7; python_version == '3.12'",
31
39
  "pydantic>=2.9; python_version == '3.13'",
32
- "pydantic>=2.12; python_version >= '3.14'",
40
+ "pydantic>=2.12; python_version == '3.14'",
41
+ "pydantic>=2.14; python_version >= '3.15'",
33
42
  "typing-extensions>=4.4", # TypeVar defaults (PEP 696) on Python < 3.13
34
43
  "platformdirs>=4.0", # platformdirs finds the user's cache directory for downloaded data artifacts
35
44
  # backports.zstd provides zstd for the archives of downloaded data artifacts; the standard library has it from 3.14.
36
45
  "backports.zstd>=1.0.0; python_version < '3.14'",
37
- "snuffled>=0.1.6",
38
- # max-div 0.21.1 adds the factory method DiversityMetric.min_separation(), which the construction of the Monte Carlo (MC) tuple set calls to create its diversity metric.
39
- "max-div>=0.21.1",
40
- "mpl-composite>=0.1.2",
41
- # polars holds the benchmark results; its wheels use the stable CPython ABI, so 1 minimal version serves every supported Python version.
42
- "polars>=1.0.0",
46
+ "snuffled>=0.1.6; python_version < '3.15'",
47
+ "snuffled>=0.1.9; python_version >= '3.15'",
48
+ # max-div 0.21.1 adds DiversityMetric.gpq_separation(q), the diversity metric that sunnbear maximizes when it builds the Monte Carlo (MC) tuple set.
49
+ "max-div>=0.21.1; python_version < '3.15'",
50
+ "max-div>=0.21.3; python_version >= '3.15'",
51
+ "mpl-composite>=0.1.2; python_version < '3.15'",
52
+ "mpl-composite>=0.1.4; python_version >= '3.15'",
53
+ # polars holds the benchmark results. Its wheels use the stable CPython ABI, so they install on every Python
54
+ # version, but on 3.15 the benchmark runner fails with polars 1.x.
55
+ "polars>=1.0.0; python_version < '3.15'",
56
+ "polars>=2.0.0; python_version >= '3.15'",
43
57
  ]
44
58
  classifiers = [
45
59
  "Development Status :: 2 - Pre-Alpha",
@@ -50,6 +64,7 @@ classifiers = [
50
64
  "Programming Language :: Python :: 3.12",
51
65
  "Programming Language :: Python :: 3.13",
52
66
  "Programming Language :: Python :: 3.14",
67
+ "Programming Language :: Python :: 3.15",
53
68
  "Topic :: Scientific/Engineering :: Mathematics",
54
69
  "Typing :: Typed",
55
70
  ]
@@ -65,24 +80,18 @@ dev = [
65
80
  # exact tool versions are pinned by the tracked uv.lock (used by pre-commit,
66
81
  # `make lint`, and CI); these ranges just set the floor.
67
82
  "ruff>=0.15.11",
68
- "scipy>=1.16.2", # its root finders are what the solver tests compare against; never a runtime dependency
83
+ # The solver tests compare against scipy's root finders; scipy is never a runtime dependency. On 3.15,
84
+ # its minimal version is its first release with a 3.15 wheel.
85
+ "scipy>=1.16.2; python_version < '3.15'",
86
+ "scipy>=1.18.1; python_version >= '3.15'",
69
87
  "ty==0.0.56", # pre-1.0 type checker — pin exactly; its ruleset is still evolving
70
88
  ]
71
- # maintainer scratch tooling — installed by a plain `uv sync` (default-groups below)
72
- # but NOT by CI's `uv sync --group dev` / `uv run --group dev`, keeping matrix jobs lean
73
- notebooks = [
74
- "jupyterlab>=4.4.6",
75
- "notebook>=7.4.5",
76
- ]
77
- # packaging-integrity tooling for the _package_check.yml CI gate; opt-in via
78
- # `--group package`, kept out of the default dev/notebooks sync.
89
+ # This group holds the packaging-integrity tooling for the _package_check.yml CI gate, which installs it with
90
+ # `--group package`.
79
91
  package = [
80
92
  "check-wheel-contents>=0.6.2",
81
93
  ]
82
94
 
83
- [tool.uv]
84
- default-groups = ["dev", "notebooks"]
85
-
86
95
  [project.urls]
87
96
  Source = "https://github.com/bertpl/sunnbear"
88
97
  ChangeLog = "https://github.com/bertpl/sunnbear/blob/main/CHANGELOG.md"
@@ -189,8 +198,6 @@ invalid-method-override = "ignore"
189
198
  [tool.codespell]
190
199
  # "intoto": the in-toto attestation format (provenance.intoto.jsonl).
191
200
  ignore-words-list = "intoto"
192
- # Notebooks embed base64 image outputs that codespell misreads as typos.
193
- skip = "*.ipynb"
194
201
 
195
202
  [tool.pytest.ini_options]
196
203
  addopts = "--strict-markers"
@@ -11,6 +11,8 @@ from collections.abc import Callable, Iterator, Sequence
11
11
 
12
12
  import numpy as np
13
13
 
14
+ from sunnbear._core.utils.floats import FLOAT64_EPS
15
+
14
16
  # The largest number of function evaluations that 1 check spends before it judges the answer incorrect.
15
17
  CORRECTNESS_CHECK_MAX_FEVALS = 1000
16
18
 
@@ -85,11 +87,12 @@ def _random_x_probes(x_found: float, xtol: float, seed: int) -> Iterator[float]:
85
87
  likely, and uniform.
86
88
  """
87
89
  rng = np.random.default_rng(seed)
88
- # Adding a distance below 1 ulp (unit in the last place) of `x_found` returns `x_found` unchanged. When
89
- # `x_found` is 0, 1 ulp is about 5e-324, so log-uniform distances would spread over hundreds of orders of
90
+ # Adding a distance below 1 ulp (unit in the last place) of `x_found` returns `x_found` unchanged.
91
+ #
92
+ # When `x_found` is 0, 1 ulp is about 5e-324, so log-uniform distances would spread over hundreds of orders of
90
93
  # magnitude and almost all be negligible; the smallest log-uniform distance is therefore also held at or
91
- # above `xtol · 2^-52`, the relative precision of a double.
92
- log_distance_min = math.log(min(xtol, max(math.ulp(x_found), math.ldexp(xtol, -52))))
94
+ # above `xtol · FLOAT64_EPS`.
95
+ log_distance_min = math.log(min(xtol, max(math.ulp(x_found), xtol * FLOAT64_EPS)))
93
96
  log_distance_max = math.log(xtol)
94
97
  while True:
95
98
  for is_log_uniform in (True, False):
@@ -20,14 +20,12 @@ class AndersonBjorckState(SolveState):
20
20
  scaling factor of every iteration that kept the bound again.
21
21
  """
22
22
 
23
- newest_bound: IntervalBound = IntervalBound.UPPER
23
+ newest_bound: IntervalBound = field(init=False)
24
24
  newest_f: float = field(init=False)
25
25
  scaled_retained_f: float = field(init=False)
26
26
 
27
27
  def __post_init__(self) -> None:
28
- """Set the starting values from the interval: `newest_f` is ``fb`` and `scaled_retained_f` is ``fa``.
29
-
30
- These values hold only for the default `newest_bound`.
31
- """
28
+ """Start with the upper bound as the newest bound: `newest_f` is ``fb`` and `scaled_retained_f` is ``fa``."""
29
+ self.newest_bound = IntervalBound.UPPER
32
30
  self.newest_f = self.interval.fb
33
31
  self.scaled_retained_f = self.interval.fa
@@ -1,11 +1,7 @@
1
1
  """`Brent` implements Brent's method: interpolation steps, with bisection when they converge too slowly."""
2
2
 
3
- import sys
4
-
5
3
  from sunnbear._core.solvers.core import Solver, SolveState
6
-
7
- # ``_MACHEPS`` is Brent's ``macheps``, the relative machine precision: 2^-52 for float64.
8
- _MACHEPS = sys.float_info.epsilon
4
+ from sunnbear._core.utils.floats import FLOAT64_EPS
9
5
 
10
6
 
11
7
  class Brent(Solver):
@@ -30,9 +26,9 @@ class Brent(Solver):
30
26
 
31
27
  - ``b`` and ``c`` are the interval bounds, ``b`` the one with the smaller ``|f|``, which is the best estimate;
32
28
  - ``m`` is half the signed width of the interval, from ``b`` toward ``c``;
33
- - ``tol = 2 * macheps * |b| + t`` is the tolerance, with ``macheps`` the relative machine precision and ``t`` an
34
- absolute tolerance derived from ``xtol`` (below); the solve ends once ``|m| <= tol`` or ``f(b) = 0``, and
35
- returns ``b``.
29
+ - ``tol = 2 * macheps * |b| + t`` is the tolerance, with ``macheps`` the relative machine precision
30
+ (`FLOAT64_EPS`) and ``t`` an absolute tolerance derived from ``xtol`` (below); the solve ends once
31
+ ``|m| <= tol`` or ``f(b) = 0``, and returns ``b``.
36
32
 
37
33
  Brent's procedure returns a ``b`` within ``6 * macheps * |x| + 2 * t`` of a root ``x``. The solver sets
38
34
  ``t = (xtol - 6 * macheps * max(|a0|, |b0|)) / 2``, with ``a0`` and ``b0`` the bounds of the initial interval,
@@ -77,7 +73,7 @@ class Brent(Solver):
77
73
  """
78
74
  interval = state.interval
79
75
  a, fa, b, fb = interval.a, interval.fa, interval.b, interval.fb
80
- t = 0.5 * (state.xtol - 6.0 * _MACHEPS * max(abs(a), abs(b)))
76
+ t = 0.5 * (state.xtol - 6.0 * FLOAT64_EPS * max(abs(a), abs(b)))
81
77
  # The Algol label "int": c becomes a, the bound on the other side of the root from b.
82
78
  c, fc = a, fa
83
79
  d = e = b - a
@@ -86,7 +82,7 @@ class Brent(Solver):
86
82
  if abs(fc) < abs(fb):
87
83
  a, b, c = b, c, b
88
84
  fa, fb, fc = fb, fc, fb
89
- tol = 2.0 * _MACHEPS * abs(b) + t
85
+ tol = 2.0 * FLOAT64_EPS * abs(b) + t
90
86
  m = 0.5 * (c - b)
91
87
  if abs(m) <= tol or fb == 0.0:
92
88
  return b
@@ -17,9 +17,10 @@ class IllinoisState(SolveState):
17
17
  iteration that kept the bound again.
18
18
  """
19
19
 
20
- newest_bound: IntervalBound = IntervalBound.UPPER
20
+ newest_bound: IntervalBound = field(init=False)
21
21
  scaled_retained_f: float = field(init=False)
22
22
 
23
23
  def __post_init__(self) -> None:
24
- """Start with the lower bound as the retained bound, at its own function value."""
24
+ """Start with the upper bound as the newest bound: `scaled_retained_f` is ``fa``."""
25
+ self.newest_bound = IntervalBound.UPPER
25
26
  self.scaled_retained_f = self.interval.fa
@@ -7,6 +7,11 @@ from sunnbear._core.solvers.core import BracketingSolver, Interval
7
7
  from .state import ITPState
8
8
  from .variant import ITPVariant
9
9
 
10
+ # ``kappa_1`` and ``kappa_2`` are the truncation constants that the `ITP` docstring gives. ``kappa_1`` depends on
11
+ # the initial interval, so `ITP._solve` sets it per solve from `_KAPPA_1_TIMES_INITIAL_WIDTH`.
12
+ _KAPPA_1_TIMES_INITIAL_WIDTH = 0.2
13
+ _KAPPA_2 = 2
14
+
10
15
 
11
16
  class ITP(BracketingSolver[ITPState]):
12
17
  """`ITP` implements the ITP method, in the variant of its paper's pseudocode or in that of its paper's experiments.
@@ -70,9 +75,6 @@ class ITP(BracketingSolver[ITPState]):
70
75
  version = 1
71
76
  state_cls = ITPState
72
77
 
73
- # `_KAPPA_2` is the paper's kappa_2; kappa_1 depends on the initial interval, so `_solve` sets it per solve.
74
- _KAPPA_2 = 2
75
-
76
78
  def __init__(self, *, n_slack: int, variant: ITPVariant) -> None:
77
79
  """Configure the method.
78
80
 
@@ -99,7 +101,7 @@ class ITP(BracketingSolver[ITPState]):
99
101
  """
100
102
  width = state.interval.width
101
103
  n_bisection = math.ceil(math.log2(width / (2.0 * state.xtol)))
102
- state.kappa_1 = 0.2 / width
104
+ state.kappa_1 = _KAPPA_1_TIMES_INITIAL_WIDTH / width
103
105
  state.max_next_width = state.xtol * 2.0 ** (n_bisection + self.n_slack)
104
106
  return super()._solve(state)
105
107
 
@@ -121,7 +123,7 @@ class ITP(BracketingSolver[ITPState]):
121
123
  sigma = -1.0
122
124
  else:
123
125
  sigma = 0.0
124
- delta = state.kappa_1 * interval.width**self._KAPPA_2
126
+ delta = state.kappa_1 * interval.width**_KAPPA_2
125
127
  if delta <= abs(x_half_minus_x_f):
126
128
  x_t = x_f + sigma * delta
127
129
  else:
@@ -22,7 +22,7 @@ class ModAB(Solver):
22
22
 
23
23
  ``x3`` replaces the bound whose function value has the sign of ``y3``.
24
24
 
25
- In Anderson-Björck mode, when the same bound stays fixed in 2 iterations in a row, its stored function value is
25
+ In Anderson-Björck mode, when the same bound is retained in 2 iterations in a row, its stored function value is
26
26
  multiplied by the factor of `AndersonBjorck`, ``m = 1 - y3 / y_moved``, where ``y_moved`` is the function value at
27
27
  the replaced bound; where ``m`` is not positive, the factor is 0.5.
28
28
 
@@ -42,7 +42,7 @@ class ModAB(Solver):
42
42
  - **Clamping:** Algorithm 1 evaluates the chord's zero and then clamps it into the interval. The C# code clamps
43
43
  first: a chord's zero that rounding puts on or outside a bound is replaced by that bound and its stored function
44
44
  value, so nothing is evaluated. In Anderson-Björck mode, such an iteration only halves the stored function value
45
- at the other bound, or marks the other bound as fixed.
45
+ at the other bound, or marks the other bound as retained.
46
46
  - **The first threshold:** only the C# code shows the threshold's starting value at the switch.
47
47
  - **Scaled function values:** Anderson-Björck mode's scaled function values stay stored after a fallback to
48
48
  bisection, and the next bisection steps read them when they test whether the function looks close to a straight
@@ -81,7 +81,7 @@ class ModAB(Solver):
81
81
 
82
82
  - ``x_new`` and ``y_new`` are the point that replaces a bound: ``x3`` and ``y3``, or, when ``x3`` is clamped
83
83
  onto a bound, that bound with its stored function value;
84
- - ``fixed_bound`` is the bound that stayed fixed in the last Anderson-Björck step, ``None`` before the first;
84
+ - ``retained_bound`` is the bound that was retained in the last Anderson-Björck step, ``None`` before the first;
85
85
  the C# code stores it as the integer ``side``, although the comment on ``side`` calls it the side that moved
86
86
  last;
87
87
  - ``is_bisecting`` is the mode, ``bisection`` in the C# code.
@@ -91,13 +91,13 @@ class ModAB(Solver):
91
91
  - computes ``x3``, and ends the solve if the interval is narrow enough;
92
92
  - evaluates ``x3``, unless it is clamped, and in bisection mode tests whether to switch;
93
93
  - ends the solve if ``y_new = 0``;
94
- - replaces a bound by ``(x_new, y_new)``, scaling the fixed bound's function value in Anderson-Björck mode;
94
+ - replaces a bound by ``(x_new, y_new)``, scaling the retained bound's function value in Anderson-Björck mode;
95
95
  - falls back to bisection if the interval is wider than the threshold.
96
96
  """
97
97
  interval = state.interval
98
98
  x1, y1, x2, y2 = interval.a, interval.fa, interval.b, interval.fb
99
99
  is_bisecting = True
100
- fixed_bound: IntervalBound | None = None
100
+ retained_bound: IntervalBound | None = None
101
101
  threshold = x2 - x1
102
102
  while True:
103
103
  # --- the new x-value x3 -------------
@@ -137,23 +137,23 @@ class ModAB(Solver):
137
137
  # The C# code compares Math.Sign(y_new) with Math.Sign(y1); the test below gives the same result because
138
138
  # y_new is not 0 here.
139
139
  if (y_new > 0.0 and y1 > 0.0) or (y_new < 0.0 and y1 < 0.0):
140
- if fixed_bound is IntervalBound.UPPER:
140
+ if retained_bound is IntervalBound.UPPER:
141
141
  m = 1.0 - y_new / y1
142
142
  y2 = y2 * (0.5 if m <= 0.0 else m)
143
143
  elif not is_bisecting:
144
- fixed_bound = IntervalBound.UPPER
144
+ retained_bound = IntervalBound.UPPER
145
145
  x1, y1 = x_new, y_new
146
146
  else:
147
- if fixed_bound is IntervalBound.LOWER:
147
+ if retained_bound is IntervalBound.LOWER:
148
148
  m = 1.0 - y_new / y2
149
149
  y1 = y1 * (0.5 if m <= 0.0 else m)
150
150
  elif not is_bisecting:
151
- fixed_bound = IntervalBound.LOWER
151
+ retained_bound = IntervalBound.LOWER
152
152
  x2, y2 = x_new, y_new
153
153
  if x2 - x1 > threshold:
154
154
  # The Anderson-Björck steps shrink the interval too slowly: fall back to bisection.
155
155
  is_bisecting = True
156
- fixed_bound = None
156
+ retained_bound = None
157
157
 
158
158
  # --------------------------------------------------------------------------
159
159
  # Helpers
@@ -19,14 +19,12 @@ class PegasusState(SolveState):
19
19
  scaling factor of every iteration that kept the bound again.
20
20
  """
21
21
 
22
- newest_bound: IntervalBound = IntervalBound.UPPER
22
+ newest_bound: IntervalBound = field(init=False)
23
23
  newest_f: float = field(init=False)
24
24
  scaled_retained_f: float = field(init=False)
25
25
 
26
26
  def __post_init__(self) -> None:
27
- """Set the starting values from the interval: `newest_f` is ``fb`` and `scaled_retained_f` is ``fa``.
28
-
29
- These values hold only for the default `newest_bound`.
30
- """
27
+ """Start with the upper bound as the newest bound: `newest_f` is ``fb`` and `scaled_retained_f` is ``fa``."""
28
+ self.newest_bound = IntervalBound.UPPER
31
29
  self.newest_f = self.interval.fb
32
30
  self.scaled_retained_f = self.interval.fa
@@ -1,11 +1,7 @@
1
1
  """`TOMS748` implements Algorithm 748: interpolation steps, a double-size secant step, and bisection if needed."""
2
2
 
3
- import sys
4
-
5
3
  from sunnbear._core.solvers.core import Interval, IntervalBound, Solver, SolveState
6
-
7
- # ``_MACHEPS`` is the paper's ``macheps``, the relative machine precision: 2^-52 for float64.
8
- _MACHEPS = sys.float_info.epsilon
4
+ from sunnbear._core.utils.floats import FLOAT64_EPS
9
5
 
10
6
  # ``_MU`` is the paper's ``mu``: an iteration that leaves the interval wider than ``_MU`` times its width at the start
11
7
  # of the iteration ends with a bisection.
@@ -40,8 +36,8 @@ class TOMS748(Solver):
40
36
  The paper states the 2 algorithms without a stopping criterion; its experiments, and the authors' code, add the
41
37
  stopping criterion of Brent's method:
42
38
 
43
- - ``stop_width = 2 * (2 * macheps * |u| + tol)``, with ``macheps`` the relative machine precision and ``tol`` an
44
- absolute tolerance that the solver derives from ``xtol``;
39
+ - ``stop_width = 2 * (2 * macheps * |u| + tol)``, with ``macheps`` the relative machine precision
40
+ (`FLOAT64_EPS`) and ``tol`` an absolute tolerance that the solver derives from ``xtol``;
45
41
  - the solve ends once the interval is at most ``stop_width`` wide, or once an evaluation returns exactly 0, and
46
42
  returns the lower bound ``a``, or the point that returned 0.
47
43
 
@@ -162,7 +158,7 @@ class TOMS748(Solver):
162
158
  @staticmethod
163
159
  def _get_tol(xtol: float, a0: float, b0: float) -> float:
164
160
  """Return the ``tol`` that keeps ``stop_width`` at most ``xtol``, or 0 where that ``tol`` would be negative."""
165
- return max(0.5 * xtol - 2.0 * _MACHEPS * max(abs(a0), abs(b0)), 0.0)
161
+ return max(0.5 * xtol - 2.0 * FLOAT64_EPS * max(abs(a0), abs(b0)), 0.0)
166
162
 
167
163
  @staticmethod
168
164
  def _get_u(interval: Interval) -> tuple[float, float]:
@@ -176,7 +172,7 @@ class TOMS748(Solver):
176
172
  def _get_stop_width(interval: Interval, tol: float) -> float:
177
173
  """Return ``stop_width = 2 * (2 * macheps * |u| + tol)`` for ``interval``, the authors' subroutine ``TOLE``."""
178
174
  u, _ = TOMS748._get_u(interval)
179
- return 2.0 * (tol + 2.0 * abs(u) * _MACHEPS)
175
+ return 2.0 * (tol + 2.0 * abs(u) * FLOAT64_EPS)
180
176
 
181
177
  def _evaluate_and_split_at(
182
178
  self, state: SolveState, interval: Interval, c: float, stop_width: float, tol: float
@@ -115,13 +115,13 @@ class Interval(ABC):
115
115
  else:
116
116
  return type(self)(self.a, x, self.fa, fx, IntervalBound.UPPER)
117
117
 
118
- def is_converged(self, doubled_xtol: float) -> bool:
119
- """Return whether a stopping criterion holds: the width is at most ``doubled_xtol`` or a bound value is zero.
118
+ def is_converged(self, xtol_doubled: float) -> bool:
119
+ """Return whether the width is at most ``xtol_doubled`` or the function value at a bound is zero.
120
120
 
121
121
  The parameter is the doubled tolerance, so the caller computes it once
122
122
  per solve, not once per iteration.
123
123
  """
124
- return self.width <= doubled_xtol or self.is_fa_zero or self.is_fb_zero
124
+ return self.width <= xtol_doubled or self.is_fa_zero or self.is_fb_zero
125
125
 
126
126
  def root(self) -> float:
127
127
  """Return the best root estimate without another step: a bound with a zero value if any, else the midpoint."""
@@ -95,15 +95,15 @@ class Solver(ABC, Generic[StateT]):
95
95
  ValueError: If ``a >= b``, or ``f(a)`` and ``f(b)`` have the same sign and neither is
96
96
  zero — a caller error, not a solve outcome.
97
97
  """
98
- # --- uncounted validation -------------------
98
+ # --- uncounted validation ---------------
99
99
  if not a < b:
100
100
  raise ValueError(f"Interval must satisfy a < b (got a={a}, b={b}).")
101
101
  wrapped_f = WrappedFunction(f, max_fevals=max_fevals, history_enabled=history_enabled)
102
102
 
103
- # --- counted: main algorithm ----------------
103
+ # --- counted: main algorithm ------------
104
104
  x_failed: float | None = None # Where f failed, if it did; decides FUNCTION_ERROR versus DIVERGED below.
105
105
  with FlopCountingContext() as flop_ctx:
106
- # --- prep and early exits ---------------
106
+ # --- prep and early exits -----------
107
107
  a_counted, b_counted, xtol_counted = CountedFloat(a), CountedFloat(b), CountedFloat(xtol)
108
108
  try:
109
109
  fa, fb = wrapped_f(a_counted), wrapped_f(b_counted)
@@ -115,7 +115,7 @@ class Solver(ABC, Generic[StateT]):
115
115
  elif fb == 0.0: # Early exit when b is a root.
116
116
  x, status = b_counted, SolveStatus.CONVERGED
117
117
  else:
118
- # --- actual solve -----------------------
118
+ # --- actual solve -----------
119
119
  interval = Interval.from_interval_bounds(a_counted, b_counted, fa, fb)
120
120
  state = self.state_cls(f=wrapped_f, interval=interval, xtol=xtol_counted, x_best=interval.midpoint)
121
121
  try:
@@ -124,7 +124,7 @@ class Solver(ABC, Generic[StateT]):
124
124
  except Exception as exc: # noqa: BLE001 — every ending becomes a recorded status, by design
125
125
  x, status, x_failed = _ending_of(exc, x_best=state.x_best)
126
126
 
127
- # --- return results -------------------------
127
+ # --- return results ---------------------
128
128
  # Divergence is the framework's judgment, not solver work, so these checks are uncounted.
129
129
  x_plain = float(x)
130
130
  if x_failed is not None and not a <= x_failed <= b:
@@ -0,0 +1,7 @@
1
+ """This module holds the float64 constants that several packages share."""
2
+
3
+ import sys
4
+
5
+ # `FLOAT64_EPS` is 2^-52, the distance from 1.0 to the next larger float64 value; papers on root finding call it
6
+ # ``macheps``, the relative machine precision.
7
+ FLOAT64_EPS = sys.float_info.epsilon
File without changes
File without changes
File without changes
File without changes