axis-synome 0.1.0.dev69__tar.gz → 0.1.0.dev71__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.
- {axis_synome-0.1.0.dev69/src/axis_synome.egg-info → axis_synome-0.1.0.dev71}/PKG-INFO +15 -1
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/README.md +14 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/WRITING_SPECS.md +28 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/pyproject.toml +3 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/_version.py +2 -2
- axis_synome-0.1.0.dev71/src/axis_synome/parser/__main__.py +6 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/ast_normalizer.py +34 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/ast_utils.py +37 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/checks/numeric_fidelity.py +313 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/converters.py +640 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/entity_parser.py +697 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/formula_extraction.py +1001 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/helpers.py +212 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/identity.py +415 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/latex/__init__.py +19 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/latex/converter.py +100 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/latex/formatter.py +310 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/latex/render.py +212 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/parse_expressions.py +144 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/statement_handler.py +410 -0
- axis_synome-0.1.0.dev71/src/axis_synome/parser/validation.py +493 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/assets_by_prime.py +15 -1
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/primes.py +4 -4
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_validator/__init__.py +1 -1
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_validator/flake8_plugin.py +1 -1
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71/src/axis_synome.egg-info}/PKG-INFO +15 -1
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome.egg-info/SOURCES.txt +30 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/fixture_latex_chain.py +38 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/fixture_mixed_module.py +15 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/fixture_set_membership.py +38 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/graph_validator.py +82 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/latex/__init__.py +1 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/latex/test_formatter.py +93 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/latex/test_render.py +208 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/test_entity_parser.py +159 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/test_formula_extraction.py +183 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/test_helpers.py +270 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/test_parser.py +419 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/parser/test_set_membership.py +208 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/suraf/formulas/__init__.py +0 -0
- axis_synome-0.1.0.dev71/tests/axis_synome/suraf/suraf_client/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/uv.lock +48 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/.flake8 +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/AGENTS.md +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/CLAUDE.md +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/examples/01_prime_basics.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/examples/02_assets_and_tokens_of_a_prime.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/examples/03_inverse_lookups.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/examples/04_atlas_provenance.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/examples/05_feeding_a_formula.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/examples/README.md +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/setup.cfg +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/export_entities.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec → axis_synome-0.1.0.dev71/src/axis_synome/parser}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec/all_entities → axis_synome-0.1.0.dev71/src/axis_synome/spec}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec/crypto_lending → axis_synome-0.1.0.dev71/src/axis_synome/spec/all_entities}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/all_entities/lending_markets.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/alm_proxies.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/asset_categories.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/assets.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/assets_missing_from_atlas.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/networks.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/tokens.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/entities/types.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/asc_incentive.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/dab.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/latent_asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/asc/formulas/resting_asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec/crypto_lending/formulas → axis_synome-0.1.0.dev71/src/axis_synome/spec/crypto_lending}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec/risk_capital/entities → axis_synome-0.1.0.dev71/src/axis_synome/spec/crypto_lending/formulas}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/crypto_lending/formulas/lif.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/entities/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/entities/audit_firms.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/entities/capital_types.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/entities/protocol_risk.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/entities/risk_categories.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/entities/smart_contract_risk.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/administrative_rrc/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/administrative_rrc/administrative_risk.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/aggregate_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/audit_factor.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/capital_composition.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec/risk_capital/formulas → axis_synome-0.1.0.dev71/src/axis_synome/spec/risk_capital/entities}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/entities/lending_protocol.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/entities/precomputed_crr.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/examples/risk_capital_walkthrough.ipynb +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/financial_rrc/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/financial_rrc/entities.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/financial_rrc/perpetual_positions.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec/risk_capital/formulas/financial_rrc → axis_synome-0.1.0.dev71/src/axis_synome/spec/risk_capital/formulas}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec_support → axis_synome-0.1.0.dev71/src/axis_synome/spec/risk_capital/formulas/financial_rrc}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/asset_correlation_coefficient.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/capital_requirement_without_buffers.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/exposure_caps.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/fixed_crr_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/instance_financial_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/lending_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/pipeline.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/precomputed_crr_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/financial_rrc/probability_of_default.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/instance_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/jrc_loss_allocation.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/lindy_factor.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/smart_contract_rrc/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/smart_contract_rrc/exceptions.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/smart_contract_rrc/smart_contract_risk.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/risk_capital/total_risk_capital.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/suraf/README.md +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/suraf/entities/assessor_score.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/suraf/entities/assets.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/suraf/entities/mappings.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/suraf/formulas/crr.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec/suraf/formulas/scoring.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_inspect/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_inspect/__main__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_inspect/cli.py +0 -0
- {axis_synome-0.1.0.dev69/src/axis_synome/spec_support/runtime → axis_synome-0.1.0.dev71/src/axis_synome/spec_support}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/correlation_matrix.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/evm_address.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/math_protocol.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/pendle_validation.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/references/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/references/graph.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital → axis_synome-0.1.0.dev71/src/axis_synome/spec_support/runtime}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/runtime/base.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/runtime/math.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/runtime/reference.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/validated_dataclass.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_support/validated_str.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_validator/check_source_uuids.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_validator/checker.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome/spec_validator/python_subset.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome.egg-info/dependency_links.txt +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome.egg-info/entry_points.txt +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome.egg-info/requires.txt +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/src/axis_synome.egg-info/top_level.txt +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/conftest.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/mocks.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_alm_proxies.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_asset_categories.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_dab.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_evm_address.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_latent_asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/asc/test_resting_asc.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital/financial_rrc → axis_synome-0.1.0.dev71/tests/axis_synome/parser}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital/formulas → axis_synome-0.1.0.dev71/tests/axis_synome/risk_capital}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/administrative_rrc/test_administrative_risk.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/risk_capital/smart_contract_rrc → axis_synome-0.1.0.dev71/tests/axis_synome/risk_capital/financial_rrc}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/financial_rrc/test_lending_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/financial_rrc/test_perpetual_positions.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/spec_inspect → axis_synome-0.1.0.dev71/tests/axis_synome/risk_capital/formulas}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/formulas/test_correlation_matrix.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/spec_support → axis_synome-0.1.0.dev71/tests/axis_synome/risk_capital/smart_contract_rrc}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/smart_contract_rrc/test_exceptions.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/smart_contract_rrc/test_smart_contract_risk.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/test_a321_a322_integration.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/test_aggregate_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/test_capital_composition.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/test_instance_rrc.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/test_jrc_loss_allocation.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/risk_capital/test_total_risk_capital.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/scripts/test_export_entities.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/spec_support/references → axis_synome-0.1.0.dev71/tests/axis_synome/spec_inspect}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_inspect/test_cli.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/spec_support/runtime → axis_synome-0.1.0.dev71/tests/axis_synome/spec_support}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/suraf → axis_synome-0.1.0.dev71/tests/axis_synome/spec_support/references}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_support/references/test_graph.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/suraf/entities → axis_synome-0.1.0.dev71/tests/axis_synome/spec_support/runtime}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_support/runtime/test_base.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_support/runtime/test_math.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_validator/test_check_source_uuids.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_validator/test_checker.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/suraf/formulas → axis_synome-0.1.0.dev71/tests/axis_synome/suraf}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69/tests/axis_synome/suraf/suraf_client → axis_synome-0.1.0.dev71/tests/axis_synome/suraf/entities}/__init__.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/entities/test_assessor_score.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/formulas/test_crr.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/formulas/test_scoring.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/static/aave_ausdc/v1/crr_mapping.csv +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/static/aave_ausdc/v1/penalty.csv +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_1_scores.csv +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_2_scores.csv +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_3_scores.csv +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/static/aave_ausdc/v1/weights.csv +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/suraf_client/suraf_client.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/tests/axis_synome/suraf/suraf_client/test_suraf_client.py +0 -0
- {axis_synome-0.1.0.dev69 → axis_synome-0.1.0.dev71}/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.
|
|
3
|
+
Version: 0.1.0.dev71
|
|
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:
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 0, '
|
|
21
|
+
__version__ = version = '0.1.0.dev71'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0, 'dev71')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -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())
|