axis-synome 0.1.0.dev69__tar.gz → 0.1.0.dev70__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 (203) hide show
  1. {axis_synome-0.1.0.dev69/src/axis_synome.egg-info → axis_synome-0.1.0.dev70}/PKG-INFO +15 -1
  2. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/README.md +14 -0
  3. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/WRITING_SPECS.md +28 -0
  4. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/pyproject.toml +3 -0
  5. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/_version.py +2 -2
  6. axis_synome-0.1.0.dev70/src/axis_synome/parser/__main__.py +6 -0
  7. axis_synome-0.1.0.dev70/src/axis_synome/parser/ast_normalizer.py +34 -0
  8. axis_synome-0.1.0.dev70/src/axis_synome/parser/ast_utils.py +37 -0
  9. axis_synome-0.1.0.dev70/src/axis_synome/parser/checks/numeric_fidelity.py +313 -0
  10. axis_synome-0.1.0.dev70/src/axis_synome/parser/converters.py +640 -0
  11. axis_synome-0.1.0.dev70/src/axis_synome/parser/entity_parser.py +697 -0
  12. axis_synome-0.1.0.dev70/src/axis_synome/parser/formula_extraction.py +1001 -0
  13. axis_synome-0.1.0.dev70/src/axis_synome/parser/helpers.py +212 -0
  14. axis_synome-0.1.0.dev70/src/axis_synome/parser/identity.py +415 -0
  15. axis_synome-0.1.0.dev70/src/axis_synome/parser/latex/__init__.py +19 -0
  16. axis_synome-0.1.0.dev70/src/axis_synome/parser/latex/converter.py +100 -0
  17. axis_synome-0.1.0.dev70/src/axis_synome/parser/latex/formatter.py +310 -0
  18. axis_synome-0.1.0.dev70/src/axis_synome/parser/latex/render.py +212 -0
  19. axis_synome-0.1.0.dev70/src/axis_synome/parser/parse_expressions.py +144 -0
  20. axis_synome-0.1.0.dev70/src/axis_synome/parser/statement_handler.py +410 -0
  21. axis_synome-0.1.0.dev70/src/axis_synome/parser/validation.py +493 -0
  22. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_validator/__init__.py +1 -1
  23. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_validator/flake8_plugin.py +1 -1
  24. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70/src/axis_synome.egg-info}/PKG-INFO +15 -1
  25. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome.egg-info/SOURCES.txt +30 -0
  26. axis_synome-0.1.0.dev70/tests/axis_synome/parser/fixture_latex_chain.py +38 -0
  27. axis_synome-0.1.0.dev70/tests/axis_synome/parser/fixture_mixed_module.py +15 -0
  28. axis_synome-0.1.0.dev70/tests/axis_synome/parser/fixture_set_membership.py +38 -0
  29. axis_synome-0.1.0.dev70/tests/axis_synome/parser/graph_validator.py +82 -0
  30. axis_synome-0.1.0.dev70/tests/axis_synome/parser/latex/__init__.py +1 -0
  31. axis_synome-0.1.0.dev70/tests/axis_synome/parser/latex/test_formatter.py +93 -0
  32. axis_synome-0.1.0.dev70/tests/axis_synome/parser/latex/test_render.py +208 -0
  33. axis_synome-0.1.0.dev70/tests/axis_synome/parser/test_entity_parser.py +159 -0
  34. axis_synome-0.1.0.dev70/tests/axis_synome/parser/test_formula_extraction.py +183 -0
  35. axis_synome-0.1.0.dev70/tests/axis_synome/parser/test_helpers.py +270 -0
  36. axis_synome-0.1.0.dev70/tests/axis_synome/parser/test_parser.py +419 -0
  37. axis_synome-0.1.0.dev70/tests/axis_synome/parser/test_set_membership.py +208 -0
  38. axis_synome-0.1.0.dev70/tests/axis_synome/suraf/formulas/__init__.py +0 -0
  39. axis_synome-0.1.0.dev70/tests/axis_synome/suraf/suraf_client/__init__.py +0 -0
  40. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/uv.lock +48 -0
  41. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/.flake8 +0 -0
  42. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/AGENTS.md +0 -0
  43. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/CLAUDE.md +0 -0
  44. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/examples/01_prime_basics.py +0 -0
  45. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/examples/02_assets_and_tokens_of_a_prime.py +0 -0
  46. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/examples/03_inverse_lookups.py +0 -0
  47. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/examples/04_atlas_provenance.py +0 -0
  48. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/examples/05_feeding_a_formula.py +0 -0
  49. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/examples/README.md +0 -0
  50. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/setup.cfg +0 -0
  51. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/__init__.py +0 -0
  52. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/export_entities.py +0 -0
  53. {axis_synome-0.1.0.dev69/src/axis_synome/spec → axis_synome-0.1.0.dev70/src/axis_synome/parser}/__init__.py +0 -0
  54. {axis_synome-0.1.0.dev69/src/axis_synome/spec/all_entities → axis_synome-0.1.0.dev70/src/axis_synome/spec}/__init__.py +0 -0
  55. {axis_synome-0.1.0.dev69/src/axis_synome/spec/crypto_lending → axis_synome-0.1.0.dev70/src/axis_synome/spec/all_entities}/__init__.py +0 -0
  56. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/all_entities/lending_markets.py +0 -0
  57. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
  58. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/alm_proxies.py +0 -0
  59. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/asset_categories.py +0 -0
  60. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/assets.py +0 -0
  61. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
  62. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/assets_missing_from_atlas.py +0 -0
  63. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/networks.py +0 -0
  64. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/primes.py +0 -0
  65. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
  66. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/tokens.py +0 -0
  67. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/entities/types.py +0 -0
  68. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/asc.py +0 -0
  69. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -0
  70. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/asc_incentive.py +0 -0
  71. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/dab.py +0 -0
  72. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/latent_asc.py +0 -0
  73. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +0 -0
  74. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/asc/formulas/resting_asc.py +0 -0
  75. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
  76. {axis_synome-0.1.0.dev69/src/axis_synome/spec/crypto_lending/formulas → axis_synome-0.1.0.dev70/src/axis_synome/spec/crypto_lending}/__init__.py +0 -0
  77. {axis_synome-0.1.0.dev69/src/axis_synome/spec/risk_capital/entities → axis_synome-0.1.0.dev70/src/axis_synome/spec/crypto_lending/formulas}/__init__.py +0 -0
  78. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/crypto_lending/formulas/lif.py +0 -0
  79. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/entities/__init__.py +0 -0
  80. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/entities/audit_firms.py +0 -0
  81. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/entities/capital_types.py +0 -0
  82. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/entities/protocol_risk.py +0 -0
  83. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/entities/risk_categories.py +0 -0
  84. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/entities/smart_contract_risk.py +0 -0
  85. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
  86. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/administrative_rrc/__init__.py +0 -0
  87. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/administrative_rrc/administrative_risk.py +0 -0
  88. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/aggregate_rrc.py +0 -0
  89. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/audit_factor.py +0 -0
  90. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/capital_composition.py +0 -0
  91. {axis_synome-0.1.0.dev69/src/axis_synome/spec/risk_capital/formulas → axis_synome-0.1.0.dev70/src/axis_synome/spec/risk_capital/entities}/__init__.py +0 -0
  92. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/entities/lending_protocol.py +0 -0
  93. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/entities/precomputed_crr.py +0 -0
  94. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/examples/risk_capital_walkthrough.ipynb +0 -0
  95. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/financial_rrc/__init__.py +0 -0
  96. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/financial_rrc/entities.py +0 -0
  97. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/financial_rrc/perpetual_positions.py +0 -0
  98. {axis_synome-0.1.0.dev69/src/axis_synome/spec/risk_capital/formulas/financial_rrc → axis_synome-0.1.0.dev70/src/axis_synome/spec/risk_capital/formulas}/__init__.py +0 -0
  99. {axis_synome-0.1.0.dev69/src/axis_synome/spec_support → axis_synome-0.1.0.dev70/src/axis_synome/spec/risk_capital/formulas/financial_rrc}/__init__.py +0 -0
  100. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/asset_correlation_coefficient.py +0 -0
  101. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/capital_requirement_without_buffers.py +0 -0
  102. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/exposure_caps.py +0 -0
  103. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/fixed_crr_rrc.py +0 -0
  104. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/instance_financial_rrc.py +0 -0
  105. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/lending_rrc.py +0 -0
  106. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/pipeline.py +0 -0
  107. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/precomputed_crr_rrc.py +0 -0
  108. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/probability_of_default.py +0 -0
  109. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +0 -0
  110. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/instance_rrc.py +0 -0
  111. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/jrc_loss_allocation.py +0 -0
  112. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/lindy_factor.py +0 -0
  113. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/smart_contract_rrc/__init__.py +0 -0
  114. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/smart_contract_rrc/exceptions.py +0 -0
  115. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/smart_contract_rrc/smart_contract_risk.py +0 -0
  116. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/risk_capital/total_risk_capital.py +0 -0
  117. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/suraf/README.md +0 -0
  118. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/suraf/entities/assessor_score.py +0 -0
  119. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/suraf/entities/assets.py +0 -0
  120. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/suraf/entities/mappings.py +0 -0
  121. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/suraf/formulas/crr.py +0 -0
  122. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec/suraf/formulas/scoring.py +0 -0
  123. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_inspect/__init__.py +0 -0
  124. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_inspect/__main__.py +0 -0
  125. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_inspect/cli.py +0 -0
  126. {axis_synome-0.1.0.dev69/src/axis_synome/spec_support/runtime → axis_synome-0.1.0.dev70/src/axis_synome/spec_support}/__init__.py +0 -0
  127. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/correlation_matrix.py +0 -0
  128. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/evm_address.py +0 -0
  129. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/math_protocol.py +0 -0
  130. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/pendle_validation.py +0 -0
  131. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/references/__init__.py +0 -0
  132. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/references/graph.py +0 -0
  133. {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital → axis_synome-0.1.0.dev70/src/axis_synome/spec_support/runtime}/__init__.py +0 -0
  134. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/runtime/base.py +0 -0
  135. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/runtime/math.py +0 -0
  136. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/runtime/reference.py +0 -0
  137. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/validated_dataclass.py +0 -0
  138. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_support/validated_str.py +0 -0
  139. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_validator/check_source_uuids.py +0 -0
  140. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_validator/checker.py +0 -0
  141. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome/spec_validator/python_subset.py +0 -0
  142. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome.egg-info/dependency_links.txt +0 -0
  143. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome.egg-info/entry_points.txt +0 -0
  144. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome.egg-info/requires.txt +0 -0
  145. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/src/axis_synome.egg-info/top_level.txt +0 -0
  146. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/__init__.py +0 -0
  147. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/conftest.py +0 -0
  148. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/mocks.py +0 -0
  149. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_alm_proxies.py +0 -0
  150. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_asc.py +0 -0
  151. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
  152. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
  153. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_asset_categories.py +0 -0
  154. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_dab.py +0 -0
  155. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_evm_address.py +0 -0
  156. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_latent_asc.py +0 -0
  157. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
  158. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
  159. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/asc/test_resting_asc.py +0 -0
  160. {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital/financial_rrc → axis_synome-0.1.0.dev70/tests/axis_synome/parser}/__init__.py +0 -0
  161. {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital/formulas → axis_synome-0.1.0.dev70/tests/axis_synome/risk_capital}/__init__.py +0 -0
  162. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/administrative_rrc/test_administrative_risk.py +0 -0
  163. {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital/smart_contract_rrc → axis_synome-0.1.0.dev70/tests/axis_synome/risk_capital/financial_rrc}/__init__.py +0 -0
  164. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/financial_rrc/test_lending_rrc.py +0 -0
  165. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/financial_rrc/test_perpetual_positions.py +0 -0
  166. {axis_synome-0.1.0.dev69/tests/axis_synome/spec_inspect → axis_synome-0.1.0.dev70/tests/axis_synome/risk_capital/formulas}/__init__.py +0 -0
  167. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/formulas/test_correlation_matrix.py +0 -0
  168. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
  169. {axis_synome-0.1.0.dev69/tests/axis_synome/spec_support → axis_synome-0.1.0.dev70/tests/axis_synome/risk_capital/smart_contract_rrc}/__init__.py +0 -0
  170. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/smart_contract_rrc/test_exceptions.py +0 -0
  171. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/smart_contract_rrc/test_smart_contract_risk.py +0 -0
  172. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/test_a321_a322_integration.py +0 -0
  173. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/test_aggregate_rrc.py +0 -0
  174. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/test_capital_composition.py +0 -0
  175. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/test_instance_rrc.py +0 -0
  176. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/test_jrc_loss_allocation.py +0 -0
  177. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/risk_capital/test_total_risk_capital.py +0 -0
  178. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/scripts/test_export_entities.py +0 -0
  179. {axis_synome-0.1.0.dev69/tests/axis_synome/spec_support/references → axis_synome-0.1.0.dev70/tests/axis_synome/spec_inspect}/__init__.py +0 -0
  180. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_inspect/test_cli.py +0 -0
  181. {axis_synome-0.1.0.dev69/tests/axis_synome/spec_support/runtime → axis_synome-0.1.0.dev70/tests/axis_synome/spec_support}/__init__.py +0 -0
  182. {axis_synome-0.1.0.dev69/tests/axis_synome/suraf → axis_synome-0.1.0.dev70/tests/axis_synome/spec_support/references}/__init__.py +0 -0
  183. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_support/references/test_graph.py +0 -0
  184. {axis_synome-0.1.0.dev69/tests/axis_synome/suraf/entities → axis_synome-0.1.0.dev70/tests/axis_synome/spec_support/runtime}/__init__.py +0 -0
  185. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_support/runtime/test_base.py +0 -0
  186. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_support/runtime/test_math.py +0 -0
  187. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_validator/test_check_source_uuids.py +0 -0
  188. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_validator/test_checker.py +0 -0
  189. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
  190. {axis_synome-0.1.0.dev69/tests/axis_synome/suraf/formulas → axis_synome-0.1.0.dev70/tests/axis_synome/suraf}/__init__.py +0 -0
  191. {axis_synome-0.1.0.dev69/tests/axis_synome/suraf/suraf_client → axis_synome-0.1.0.dev70/tests/axis_synome/suraf/entities}/__init__.py +0 -0
  192. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/entities/test_assessor_score.py +0 -0
  193. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/formulas/test_crr.py +0 -0
  194. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/formulas/test_scoring.py +0 -0
  195. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/static/aave_ausdc/v1/crr_mapping.csv +0 -0
  196. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/static/aave_ausdc/v1/penalty.csv +0 -0
  197. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_1_scores.csv +0 -0
  198. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_2_scores.csv +0 -0
  199. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_3_scores.csv +0 -0
  200. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/static/aave_ausdc/v1/weights.csv +0 -0
  201. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/suraf_client/suraf_client.py +0 -0
  202. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/suraf/suraf_client/test_suraf_client.py +0 -0
  203. {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev70}/tests/axis_synome/test_examples.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.0.dev69
3
+ Version: 0.1.0.dev70
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Author-email: Archon Tech <hello@archontech.ai>
6
6
  License: MIT
@@ -74,6 +74,20 @@ If none is available, importing `axis_synome.spec_support.runtime.math` raises `
74
74
 
75
75
  ### Spec Validation
76
76
 
77
+ Specs are validated by **two complementary gates**:
78
+
79
+ 1. **Static gate** — the `spec_validator` flake8 plugin enforces the
80
+ declarative Python subset specs are written in (no mutation, no
81
+ imperative control flow, restricted imports, …).
82
+ 2. **Semantic gate** — the parser (`python -m axis_synome.parser`)
83
+ round-trips every spec module into a SymPy expression graph and
84
+ identity records. If a spec uses a construct the parser cannot
85
+ lower, parsing fails — even if it would lint clean. Run via
86
+ `make parse-specs` (also part of `make check-all`).
87
+
88
+ See [ADR-0002](https://github.com/archon-research/synome/blob/main/docs/adr/0002-move-parser-to-next-gen-atlas.md)
89
+ for the rationale behind co-locating the parser with the specs.
90
+
77
91
  ```python
78
92
  from axis_synome.spec_validator import validate_file
79
93
 
@@ -59,6 +59,20 @@ If none is available, importing `axis_synome.spec_support.runtime.math` raises `
59
59
 
60
60
  ### Spec Validation
61
61
 
62
+ Specs are validated by **two complementary gates**:
63
+
64
+ 1. **Static gate** — the `spec_validator` flake8 plugin enforces the
65
+ declarative Python subset specs are written in (no mutation, no
66
+ imperative control flow, restricted imports, …).
67
+ 2. **Semantic gate** — the parser (`python -m axis_synome.parser`)
68
+ round-trips every spec module into a SymPy expression graph and
69
+ identity records. If a spec uses a construct the parser cannot
70
+ lower, parsing fails — even if it would lint clean. Run via
71
+ `make parse-specs` (also part of `make check-all`).
72
+
73
+ See [ADR-0002](https://github.com/archon-research/synome/blob/main/docs/adr/0002-move-parser-to-next-gen-atlas.md)
74
+ for the rationale behind co-locating the parser with the specs.
75
+
62
76
  ```python
63
77
  from axis_synome.spec_validator import validate_file
64
78
 
@@ -269,6 +269,34 @@ def penalty(concentration: float) -> float:
269
269
  return penalty_value
270
270
  ```
271
271
 
272
+ #### Set membership
273
+
274
+ Membership tests against a closed‑world set lower to finite symbolic case
275
+ analysis, so they stay fully reconstructible (no runtime predicates or
276
+ uninterpreted symbols). The right‑hand side must be a module‑level named
277
+ constant set whose members are enum members or scalar constants — typically a
278
+ `Final[set[...]]` declared in `entities/`. Inline set literals are not allowed
279
+ inside formulas (see the language subset), so always reference a named set.
280
+
281
+ ```python
282
+ TOP_TIER_AUDIT_FIRMS: Final[set[AuditFirm]] = {
283
+ AuditFirm.CHAINSECURITY,
284
+ AuditFirm.OPENZEPPELIN,
285
+ }
286
+
287
+
288
+ def audit_firm_effectiveness(firm: AuditFirm) -> float:
289
+ return TOP_TIER_COEFFICIENT if firm in TOP_TIER_AUDIT_FIRMS else MID_TIER_COEFFICIENT
290
+ ```
291
+
292
+ This lowers to a `Piecewise` whose condition expands to
293
+ `Or(Eq(firm, AuditFirm.CHAINSECURITY), Eq(firm, AuditFirm.OPENZEPPELIN))`;
294
+ `not in` expands to the conjunction of inequalities, and an empty set collapses
295
+ to `False` (`in`) or `True` (`not in`). When the right‑hand side cannot be
296
+ resolved statically the converter falls back to its uninterpreted comparison
297
+ form and emits a warning, so prefer named constant sets to keep formulas
298
+ reconstructible.
299
+
272
300
  #### Example
273
301
 
274
302
  A simple example of a function is given below:
@@ -47,6 +47,9 @@ dev = [
47
47
  "ruff>=0.15",
48
48
  "ty>=0.0.16",
49
49
  "lefthook>=1.9.5",
50
+ "hypothesis>=6.100",
51
+ "sympy>=1.14.0",
52
+ "typing-extensions>=4.12.0",
50
53
  "axis-synome[runtime]",
51
54
  ]
52
55
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.0.dev69'
22
- __version_tuple__ = version_tuple = (0, 1, 0, 'dev69')
21
+ __version__ = version = '0.1.0.dev70'
22
+ __version_tuple__ = version_tuple = (0, 1, 0, 'dev70')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,6 @@
1
+ """CLI entry point for synome parser module."""
2
+
3
+ from axis_synome.parser.parse_expressions import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -0,0 +1,34 @@
1
+ """Lightweight AST normalizer for expression nodes (no behavior changes).
2
+
3
+ Transforms a minimal subset of nodes to simplify downstream conversion:
4
+ - Flatten non-math attribute access: `obj.attr` -> Name("obj_attr")
5
+ - Leave `math.*` attributes intact to preserve existing math mapping
6
+
7
+ This pre-pass is intentionally conservative to avoid altering semantics.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import ast
13
+ from typing import Final
14
+
15
+ MATH_PREFIXES: Final[tuple[str, ...]] = ("math",)
16
+
17
+
18
+ class _ExprNormalizer(ast.NodeTransformer):
19
+ """AST NodeTransformer that flattens selected attributes for expressions."""
20
+
21
+ def visit_Attribute(self, node: ast.Attribute): # noqa: N802 - ast API
22
+ value = self.visit(node.value)
23
+ # Only flatten simple Name.attr cases where base is not a math-like module
24
+ if isinstance(value, ast.Name) and value.id not in MATH_PREFIXES:
25
+ # Use double underscore to reduce name-collision risk and let downstream
26
+ # converters reconstruct attr(value) if desired.
27
+ return ast.copy_location(ast.Name(id=f"{value.id}__{node.attr}"), node)
28
+ # Default: rebuild node with transformed value
29
+ return ast.copy_location(ast.Attribute(value=value, attr=node.attr, ctx=node.ctx), node)
30
+
31
+
32
+ def normalize_expr(expr_node: ast.expr) -> ast.expr:
33
+ """Normalize an expression AST node without changing its meaning."""
34
+ return _ExprNormalizer().visit(expr_node)
@@ -0,0 +1,37 @@
1
+ """AST parsing utilities for extracting function structure."""
2
+
3
+ import ast
4
+ import inspect
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+
9
+ def find_function_def(tree: ast.Module, func_name: str) -> ast.FunctionDef:
10
+ """Find function definition node in AST, raise ValueError if not found."""
11
+ for node in ast.walk(tree):
12
+ if isinstance(node, ast.FunctionDef) and node.name == func_name:
13
+ return node
14
+ raise ValueError(f"Could not find function {func_name} in AST")
15
+
16
+
17
+ def is_docstring(stmt: ast.stmt) -> bool:
18
+ """Check if statement is a docstring."""
19
+ return (
20
+ isinstance(stmt, ast.Expr)
21
+ and isinstance(stmt.value, ast.Constant)
22
+ and isinstance(stmt.value.value, str)
23
+ )
24
+
25
+
26
+ def extract_parameter_metadata(func: Callable) -> list[dict[str, Any]]:
27
+ """Extract parameter names from function signature."""
28
+ sig = inspect.signature(func)
29
+ params = []
30
+ for param_name, param in sig.parameters.items():
31
+ if param_name in ("self", "cls"):
32
+ continue
33
+ params.append({
34
+ "name": param_name,
35
+ "has_default": param.default != inspect.Parameter.empty,
36
+ })
37
+ return params
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env python3
2
+ # ruff: noqa: T201 -- CLI tool that legitimately prints results to stdout/stderr.
3
+ """Numeric fidelity gate for the parser (standalone spec-validation tool).
4
+
5
+ For every scalar formula extracted from a spec module, generate random inputs
6
+ (via Hypothesis), evaluate both the original Python function and the SymPy
7
+ expression the parser produced, and assert that the two agree to within a small
8
+ tolerance. A disagreement means the parser silently changed the semantics of
9
+ the formula.
10
+
11
+ Run it directly (it is intentionally *not* a pytest test, so spec validation is
12
+ driven by tools rather than the test runner)::
13
+
14
+ uv run python -m axis_synome.parser.checks.numeric_fidelity
15
+
16
+ Exits non-zero if any scalar formula disagrees with its SymPy lowering.
17
+
18
+ Scope:
19
+
20
+ - This gate covers **scalar formulas** -- functions whose declared parameter
21
+ annotations are all primitive (``int``, ``float``, ``bool``) and whose
22
+ extracted ``sympy_expr`` therefore depends only on bare parameter symbols.
23
+ - Entity-typed formulas (parameters annotated with dataclasses, ``list[X]``,
24
+ ``Enum`` subclasses, etc.) are out of scope here: faithfully evaluating the
25
+ SymPy side would require fabricating real dataclass instances and substituting
26
+ dotted/indexed sympy symbols accordingly. Their fidelity is guarded by
27
+ ``validate_formula_free_symbols`` and ``validate_entity_attribute_paths`` from
28
+ ``parser.validation``.
29
+
30
+ Failures from this tool are real bugs -- either in the parser's lowering or in
31
+ the spec author's assumptions about how their formula should evaluate.
32
+
33
+ Hypothesis is a dev/CI dependency only; this module is intended for local/CI
34
+ checks, not as part of the package's runtime import surface.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import importlib
40
+ import inspect
41
+ import math
42
+ import pkgutil
43
+ import sys
44
+ from collections.abc import Callable
45
+ from typing import Any, cast, get_origin
46
+
47
+ import sympy as sp
48
+ from hypothesis import given, settings
49
+ from hypothesis import strategies as st
50
+
51
+ from axis_synome.parser.formula_extraction import _extract_function_identities
52
+ from axis_synome.parser.validation import _unwrap_annotation
53
+
54
+ SPEC_ROOT_PKG = "axis_synome.spec"
55
+
56
+ # Conservative finite range to keep sympy evaluation cheap and avoid overflow in
57
+ # functions like ``exp`` / ``log``. Authors targeting exotic domains can narrow
58
+ # the strategy in their formula's parameter metadata in the future.
59
+ _FLOAT_STRAT = st.floats(
60
+ min_value=-1e3,
61
+ max_value=1e3,
62
+ allow_nan=False,
63
+ allow_infinity=False,
64
+ allow_subnormal=False,
65
+ )
66
+ _INT_STRAT = st.integers(min_value=-1000, max_value=1000)
67
+ _BOOL_STRAT = st.booleans()
68
+
69
+ # Absolute and relative tolerance for fidelity comparison. Generous to absorb
70
+ # float reassociation; narrow enough to catch sign flips, missing factors, etc.
71
+ _ATOL = 1e-9
72
+ _RTOL = 1e-6
73
+
74
+ # Examples per formula. ``derandomize`` makes a failure reproducible across runs
75
+ # so the gate behaves like the other deterministic spec-validation tools.
76
+ _MAX_EXAMPLES = 50
77
+
78
+ ScalarCase = tuple[str, Callable[..., Any], sp.Basic, dict[str, st.SearchStrategy[Any]]]
79
+
80
+
81
+ def _iter_spec_modules() -> list[str]:
82
+ """Walk ``axis_synome.spec`` and yield every importable submodule."""
83
+ pkg = importlib.import_module(SPEC_ROOT_PKG)
84
+ names: list[str] = []
85
+ for info in pkgutil.walk_packages(pkg.__path__, prefix=SPEC_ROOT_PKG + "."):
86
+ if info.ispkg:
87
+ continue
88
+ names.append(info.name)
89
+ return names
90
+
91
+
92
+ def _is_scalar_annotation(ann: Any) -> bool:
93
+ ann = _unwrap_annotation(ann)
94
+ if ann is inspect.Parameter.empty:
95
+ return False
96
+ if ann in (int, float, bool):
97
+ return True
98
+ origin = get_origin(ann)
99
+ if origin is None:
100
+ return False
101
+ # ``list[int]`` etc. are not scalar at the call site, but list-of-scalars is
102
+ # not used for any current scalar-path formula -- keep simple.
103
+ return False
104
+
105
+
106
+ def _strategy_for(ann: Any) -> st.SearchStrategy[Any] | None:
107
+ ann = _unwrap_annotation(ann)
108
+ if ann is bool:
109
+ return _BOOL_STRAT
110
+ if ann is int:
111
+ return _INT_STRAT
112
+ if ann is float:
113
+ return _FLOAT_STRAT
114
+ return None
115
+
116
+
117
+ def _is_pure_scalar_formula(
118
+ func: Callable[..., Any],
119
+ expr: sp.Basic,
120
+ sibling_exprs: dict[str, sp.Basic],
121
+ module_constants: dict[str, Any],
122
+ ) -> tuple[bool, sp.Basic, dict[str, st.SearchStrategy[Any]]]:
123
+ """Return (eligible, fully-substituted-expr, strategies-by-param-name).
124
+
125
+ Eligible iff every declared parameter has a primitive annotation AND every
126
+ free symbol in ``expr`` (after substituting sibling-formula intermediates and
127
+ module-level numeric constants) matches a declared parameter name.
128
+ """
129
+ try:
130
+ sig = inspect.signature(func)
131
+ except (TypeError, ValueError):
132
+ return False, expr, {}
133
+
134
+ strategies: dict[str, st.SearchStrategy[Any]] = {}
135
+ for name, param in sig.parameters.items():
136
+ if not _is_scalar_annotation(param.annotation):
137
+ return False, expr, {}
138
+ strat = _strategy_for(param.annotation)
139
+ if strat is None:
140
+ return False, expr, {}
141
+ strategies[name] = strat
142
+
143
+ param_names = set(strategies.keys())
144
+ seen_subs: set[str] = set()
145
+ while True:
146
+ unresolved = {str(s) for s in expr.free_symbols} - param_names
147
+ if not unresolved:
148
+ break
149
+ progress = False
150
+ for name in sorted(unresolved):
151
+ if name in seen_subs:
152
+ continue
153
+ sub_expr = sibling_exprs.get(name)
154
+ if sub_expr is not None:
155
+ expr = expr.subs(sp.Symbol(name), sub_expr)
156
+ seen_subs.add(name)
157
+ progress = True
158
+ continue
159
+ const = module_constants.get(name)
160
+ if isinstance(const, (int, float)) and not isinstance(const, bool):
161
+ expr = expr.subs(sp.Symbol(name), sp.Float(float(const)))
162
+ seen_subs.add(name)
163
+ progress = True
164
+ continue
165
+ if not progress:
166
+ return False, expr, {}
167
+
168
+ return True, expr, strategies
169
+
170
+
171
+ def _collect_scalar_formula_cases() -> list[ScalarCase]:
172
+ """Discover every scalar formula across the spec tree."""
173
+ import types
174
+
175
+ cases: list[ScalarCase] = []
176
+ for mod_name in _iter_spec_modules():
177
+ try:
178
+ module = importlib.import_module(mod_name)
179
+ except Exception:
180
+ continue
181
+ module_globals = vars(module)
182
+ for attr_name in sorted(dir(module)):
183
+ if attr_name.startswith("_"):
184
+ continue
185
+ obj = getattr(module, attr_name, None)
186
+ if not isinstance(obj, types.FunctionType):
187
+ continue
188
+ if getattr(obj, "__module__", None) != module.__name__:
189
+ continue
190
+ # Per-function extraction so a single failing formula doesn't mask
191
+ # all the rest in its module. Pass a *copy* of module_globals because
192
+ # _extract_function_identities mutates it (replaces callables with
193
+ # IdentityData placeholders), which would break subsequent direct
194
+ # calls to those functions in the python-vs-sympy comparison below.
195
+ try:
196
+ identities = _extract_function_identities(
197
+ obj,
198
+ description=None,
199
+ atlas_section=None,
200
+ target_globals=dict(module_globals),
201
+ )
202
+ except Exception:
203
+ continue
204
+ # Build a sibling map of intermediate name -> sympy expression so the
205
+ # "main" formula can be substituted into a parameter-only form.
206
+ sibling_exprs: dict[str, sp.Basic] = {}
207
+ for identity in identities:
208
+ expr = getattr(identity, "sympy_expr", None)
209
+ node_id = getattr(identity, "node_id", "") or ""
210
+ if expr is None or not isinstance(expr, sp.Basic):
211
+ continue
212
+ short = node_id.rsplit(".", 1)[-1]
213
+ if short:
214
+ sibling_exprs[short] = expr
215
+
216
+ # Only check the identity that represents the function's return.
217
+ main_id = f"{obj.__module__}.{attr_name}"
218
+ for identity in identities:
219
+ node_id = getattr(identity, "node_id", "") or ""
220
+ if node_id != main_id and node_id != attr_name:
221
+ continue
222
+ expr = getattr(identity, "sympy_expr", None)
223
+ if expr is None or not isinstance(expr, sp.Basic):
224
+ continue
225
+ ok, sub_expr, strategies = _is_pure_scalar_formula(
226
+ obj, expr, sibling_exprs, module_globals
227
+ )
228
+ if not ok:
229
+ continue
230
+ case_id = f"{mod_name}.{attr_name}"
231
+ cases.append((case_id, obj, sub_expr, strategies))
232
+ return cases
233
+
234
+
235
+ def _values_close(a: float, b: float) -> bool:
236
+ if isinstance(a, bool) or isinstance(b, bool):
237
+ return bool(a) == bool(b)
238
+ af = float(a)
239
+ bf = float(b)
240
+ if math.isnan(af) and math.isnan(bf):
241
+ return True
242
+ if math.isinf(af) or math.isinf(bf):
243
+ return af == bf
244
+ return math.isclose(af, bf, abs_tol=_ATOL, rel_tol=_RTOL)
245
+
246
+
247
+ def _run_case(case: ScalarCase) -> str | None:
248
+ """Run the fidelity check for one scalar formula.
249
+
250
+ Returns ``None`` on success or a human-readable failure message (carrying the
251
+ falsifying inputs) otherwise.
252
+ """
253
+ case_id, func, expr, strategies = case
254
+ symbol_map = {name: sp.Symbol(name) for name in strategies}
255
+
256
+ @given(values=st.fixed_dictionaries({name: strategies[name] for name in strategies}))
257
+ @settings(max_examples=_MAX_EXAMPLES, deadline=None, derandomize=True)
258
+ def _check(values: dict[str, Any]) -> None:
259
+ try:
260
+ py_value = func(**values)
261
+ except (ZeroDivisionError, ValueError, ArithmeticError):
262
+ # Inputs outside the function's natural domain -- skip.
263
+ return
264
+ sub = {
265
+ symbol_map[name]: sp.Float(v) if isinstance(v, float) else sp.Integer(int(v))
266
+ for name, v in values.items()
267
+ if name in symbol_map
268
+ }
269
+ try:
270
+ substituted = cast(Any, expr.subs(cast(Any, sub)))
271
+ if isinstance(substituted, sp.logic.boolalg.Boolean):
272
+ sym_value = bool(substituted)
273
+ else:
274
+ sym_value = float(substituted.evalf())
275
+ except (TypeError, ValueError, ZeroDivisionError, AttributeError):
276
+ return
277
+
278
+ assert _values_close(py_value, sym_value), (
279
+ f"Numeric mismatch for {case_id} with {values}: "
280
+ f"python={py_value!r} sympy={sym_value!r} expr={expr!r}"
281
+ )
282
+
283
+ try:
284
+ _check()
285
+ except Exception as exc: # noqa: BLE001 -- report any failure as a gate failure.
286
+ return f"{type(exc).__name__}: {exc}"
287
+ return None
288
+
289
+
290
+ def main() -> int:
291
+ cases = _collect_scalar_formula_cases()
292
+ failures: list[tuple[str, str]] = []
293
+ for case in cases:
294
+ error = _run_case(case)
295
+ if error is not None:
296
+ failures.append((case[0], error))
297
+
298
+ if failures:
299
+ for case_id, error in failures:
300
+ print(f"FAIL: {case_id}\n {error}", file=sys.stderr)
301
+ print(
302
+ f"\n{len(failures)} of {len(cases)} scalar formulas disagree with their "
303
+ f"SymPy lowering.",
304
+ file=sys.stderr,
305
+ )
306
+ return 1
307
+
308
+ print(f"OK: all {len(cases)} scalar formulas match their SymPy lowering.")
309
+ return 0
310
+
311
+
312
+ if __name__ == "__main__":
313
+ raise SystemExit(main())