physure 0.2.0__tar.gz → 0.2.1__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.
- {physure-0.2.0 → physure-0.2.1}/Cargo.lock +2 -2
- {physure-0.2.0 → physure-0.2.1}/Cargo.toml +1 -1
- {physure-0.2.0 → physure-0.2.1}/PKG-INFO +11 -7
- {physure-0.2.0 → physure-0.2.1}/README.md +10 -6
- {physure-0.2.0 → physure-0.2.1}/physure/__init__.py +47 -1
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/_arithmetic_mixin.py +15 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/native.py +8 -4
- {physure-0.2.0 → physure-0.2.1}/physure/ext/grammar.py +188 -2
- physure-0.2.1/physure/ext/helpers.py +146 -0
- {physure-0.2.0 → physure-0.2.1}/physure/repl.py +9 -1
- {physure-0.2.0 → physure-0.2.1}/physure-core/README.md +9 -2
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/quantity.rs +17 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/ast.rs +2 -1
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/tests.rs +10 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/rational.rs +4 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/README.md +10 -6
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/benchmark_lineage.py +7 -7
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/benchmark_propagation.py +4 -4
- {physure-0.2.0 → physure-0.2.1}/physure-python/src/lib.rs +12 -0
- physure-0.2.1/physure-python/tests/domain/test_bare_number_add_sub.py +60 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_grammar.py +1 -1
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/test_tracing.py +9 -9
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_jit_uncertainty.py +4 -4
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_new_features.py +2 -2
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_plotting.py +10 -10
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_rust_transcendental_delegation.py +6 -6
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_uncertainty_api.py +4 -4
- {physure-0.2.0 → physure-0.2.1}/pyproject.toml +1 -1
- {physure-0.2.0 → physure-0.2.1}/LICENSE +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/__main__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/_core.pyi +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/_generated_types.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/_jit/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/_jit/tracer.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/context.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/factories.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/fit_service.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/functions/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/functions/functions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/io.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/parsing.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/solver_service.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/startup.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/application/tracing/context.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/core_backend.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/jax_backend.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/kernels/covariance.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/numpy_backend.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/python_backend.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/torch/autograd_store.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/backends/torch_backend.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/cli.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/constants.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/autograd.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/dispatcher.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/formatting.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/functional.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/parsing/sanitizer.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/parsing/sympy_parser.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/parsing/transformer.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/protocols.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/registry.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/core/registry.pyi +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/exceptions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/_backend_mixin.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/base_entity.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/conversions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/converters.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/dimensions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/equivalencies.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/ports/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/ports/unit_repository.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/quantity.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/system.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/uncertainty.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/units.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/measurement/vectorized_uncertainty.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/export.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/expression.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/graph.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/quantity.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/domain/symbolic/tracer.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/chemistry/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/chemistry/equivalency.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/chemistry/reaction.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/chemistry/species.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/chemistry/thermo_kinetics.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/compiler.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/currency.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/io.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/numba_support.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/pandas_support.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/ext/symbolic_regression.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/functional.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/infrastructure/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/infrastructure/config/physure.conf +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/infrastructure/config/systems/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/infrastructure/config/systems/imperial.conf +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/infrastructure/config/systems/international.conf +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/nn/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/nn/layers.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/nn/loss.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/nn/networks.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/nn/utils.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/pandas_ext.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/plotting.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/py.typed +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/scripts/generate_types.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/symbolic.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/units/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/units/__init__.pyi +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/units/_index.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/units/core.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure/units/electromagnetism.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/.cargo/config.toml +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/.github/workflows/CI.yml +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/.gitignore +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/Cargo.lock +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/Cargo.toml +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/LICENSE +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/benches/core_benchmarks.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/examples/quickstart.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/scripts/filter_lcov.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/sonar-project.properties +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/covariance/arrow.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/covariance/mod.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/covariance/pruning.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/covariance/store.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/covariance/tests.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/error.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/lib.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/dimension_vector.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/dual.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/hessian_propagation.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/interval.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/mod.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/sparse_kernels.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/math/tests.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/serialization.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/compiler.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/diff.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/expr.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/factor.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/integrate.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/symbolic/mod.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/uncertainty/gaussian.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/uncertainty/mod.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/uncertainty/monte_carlo.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/uncertainty/tests.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/uncertainty/trait_def.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/uncertainty/unscented.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/converters.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/definition.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/dimension.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/mod.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/parser.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/registry.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/src/units/tests.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-core/tests/edge_cases_and_concurrency.rs +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/Cargo.toml +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/LICENSE +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/MANIFEST.in +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/Makefile +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/benchmark_comparison.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/benchmark_vectorized.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/comprehensive_plot.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/profile_vectorized.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/benchmarks/run_compile_bench.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/UNITS.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/benchmarks.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/chemistry_roadmap.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/diagrams_roadmap.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/index.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/reference/quantity.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/scientific_roadmap.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/plans/2026-07-11-mkml-math-functions.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/plans/2026-07-11-mkml-notation-layer.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/plans/2026-07-12-mkml-user-functions.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/plans/2026-07-16-kill-rust-fallback-illusion.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/plans/2026-07-16-rust-transcendental-delegation.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/plans/2026-07-16-unit-parser-zero-exponent-guard.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/specs/2026-07-11-mkml-math-functions-design.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/specs/2026-07-11-notation-layer-design.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/specs/2026-07-12-mkml-user-functions-design.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/specs/2026-07-16-kill-rust-fallback-illusion-design.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/specs/2026-07-16-rust-transcendental-delegation-design.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/superpowers/specs/2026-07-16-unit-parser-zero-exponent-guard-design.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/symbolic_math_roadmap.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/torch_compile_integration.md +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/tutorials/01_best_practices.ipynb +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/docs/tutorials/jax_tutorial.ipynb +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/examples/advanced_physics.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/examples/pandas_integration.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/examples/pinn_harmonic_oscillator.ipynb +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/mkdocs.yml +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/scripts/benchmark_import.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/scripts/compile_units.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/scripts/generate_constants_config.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/scripts/generate_units_readme.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/sonar-project.properties +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/conftest.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_backends.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_backends_and_quantity.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_concurrency.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_consistency.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_covariance_store.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_functional.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_functional_core.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_functional_jax.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_gradients.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_invariants.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_jit.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_pydantic.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_registry.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_serialization.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_sparse_backends.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/core/test_vectorized_unc.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/dynamics/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/dynamics/test_fit_service.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/dynamics/test_solver.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_autograd.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_chemistry_integration.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_compiler.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_currency.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_io.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_molar_equivalency.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_pandas_support.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_reaction.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_species.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_symbolic_regression.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_thermo_kinetics.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/ext/test_torch_compile.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/functions/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/functions/test_functions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/integration_tests/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/integration_tests/test_measurement_system.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/integration_tests/test_physics_realworld.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/integration_tests/test_workflow.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_advanced.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_affine_arithmetic.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_api.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_autograd_propagation.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_backend_mixin.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_constants_codata.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_constants_module.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_conversions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_conversions_realworld.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_correctness_fixes.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_dimensions.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_lazy_prefixes.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_nonlinear_and_calculus.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_quantity.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_uncertainty_models.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/measurement_tests/test_units.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/notation_tests/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/notation_tests/test_base_entity.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/notation_tests/test_lexer.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/notation_tests/test_parsers.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/performance/benchmarks.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/pinn/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/pinn/test_integration.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/pinn/test_nn_utils.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/strategies.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/test_calculus.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/test_native_coverage.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/test_native_expr.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/test_solve_all.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/symbolic/test_solver.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_ecosystem.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_equivalencies.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_jit_core.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_performance_and_interop.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_repl.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_uncertainty.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/test_uncertainty_gum.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/tools/__init__.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/tests/tools/test_stub_gen.py +0 -0
- {physure-0.2.0 → physure-0.2.1}/physure-python/uv.lock +0 -0
|
@@ -1044,7 +1044,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|
|
1044
1044
|
|
|
1045
1045
|
[[package]]
|
|
1046
1046
|
name = "physure"
|
|
1047
|
-
version = "0.2.
|
|
1047
|
+
version = "0.2.1"
|
|
1048
1048
|
dependencies = [
|
|
1049
1049
|
"approx 0.5.1",
|
|
1050
1050
|
"arrow",
|
|
@@ -1063,7 +1063,7 @@ dependencies = [
|
|
|
1063
1063
|
|
|
1064
1064
|
[[package]]
|
|
1065
1065
|
name = "physure-python"
|
|
1066
|
-
version = "0.2.
|
|
1066
|
+
version = "0.2.1"
|
|
1067
1067
|
dependencies = [
|
|
1068
1068
|
"arrow",
|
|
1069
1069
|
"chrono",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: physure
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -80,17 +80,21 @@ Project-URL: Documentation, https://physure.readthedocs.io/
|
|
|
80
80
|
Project-URL: Homepage, https://github.com/Alexisrx96/physure
|
|
81
81
|
Project-URL: Source, https://github.com/Alexisrx96/physure
|
|
82
82
|
|
|
83
|
-
# physure 🔬
|
|
84
|
-
|
|
85
83
|
<div align="center">
|
|
86
84
|
|
|
85
|
+
<picture>
|
|
86
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Alexisrx96/physure/main/assets/logo-horizontal-dark.svg">
|
|
87
|
+
<img src="https://raw.githubusercontent.com/Alexisrx96/physure/main/assets/logo-horizontal-light.svg" alt="physure" width="440">
|
|
88
|
+
</picture>
|
|
89
|
+
|
|
87
90
|
<h3><b>Unit-aware, dimension-correct computing for Python — powered by a Rust core</b></h3>
|
|
88
91
|
<p><i>Units, dimensions, and correlated uncertainty tracked through every calculation, with zero overhead under <code>torch.compile</code> / <code>jax.jit</code>.</i></p>
|
|
89
92
|
|
|
90
|
-
[](https://pypi.org/project/physure/)
|
|
91
|
-
[](https://pypi.org/project/physure/)
|
|
94
|
+
[](https://crates.io/crates/physure)
|
|
95
|
+
[](https://github.com/Alexisrx96/physure/actions/workflows/tests.yml)
|
|
96
|
+
[](https://pypi.org/project/physure/)
|
|
97
|
+
[](https://github.com/Alexisrx96/physure/blob/main/LICENSE)
|
|
94
98
|
|
|
95
99
|
</div>
|
|
96
100
|
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
# physure 🔬
|
|
2
|
-
|
|
3
1
|
<div align="center">
|
|
4
2
|
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Alexisrx96/physure/main/assets/logo-horizontal-dark.svg">
|
|
5
|
+
<img src="https://raw.githubusercontent.com/Alexisrx96/physure/main/assets/logo-horizontal-light.svg" alt="physure" width="440">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
5
8
|
<h3><b>Unit-aware, dimension-correct computing for Python — powered by a Rust core</b></h3>
|
|
6
9
|
<p><i>Units, dimensions, and correlated uncertainty tracked through every calculation, with zero overhead under <code>torch.compile</code> / <code>jax.jit</code>.</i></p>
|
|
7
10
|
|
|
8
|
-
[](https://pypi.org/project/physure/)
|
|
9
|
-
[](https://pypi.org/project/physure/)
|
|
12
|
+
[](https://crates.io/crates/physure)
|
|
13
|
+
[](https://github.com/Alexisrx96/physure/actions/workflows/tests.yml)
|
|
14
|
+
[](https://pypi.org/project/physure/)
|
|
15
|
+
[](https://github.com/Alexisrx96/physure/blob/main/LICENSE)
|
|
12
16
|
|
|
13
17
|
</div>
|
|
14
18
|
|
|
@@ -26,7 +26,7 @@ from collections.abc import Callable
|
|
|
26
26
|
from typing import TYPE_CHECKING, Any
|
|
27
27
|
|
|
28
28
|
# ── Version ───────────────────────────────────────────────────────────────────
|
|
29
|
-
__version__ = "0.2.
|
|
29
|
+
__version__ = "0.2.1"
|
|
30
30
|
|
|
31
31
|
# ── Rust-first: hard import of native core ────────────────────────────────────
|
|
32
32
|
# Unlike physure, physure has NO pure-Python fallback.
|
|
@@ -143,6 +143,26 @@ _PLOT_ATTRS = {
|
|
|
143
143
|
"plot_pairplot",
|
|
144
144
|
"plot_covariance",
|
|
145
145
|
}
|
|
146
|
+
_HELPER_ATTRS = {
|
|
147
|
+
"approx_eq",
|
|
148
|
+
"linspace",
|
|
149
|
+
"sqrt",
|
|
150
|
+
"sin",
|
|
151
|
+
"cos",
|
|
152
|
+
"tan",
|
|
153
|
+
"asin",
|
|
154
|
+
"acos",
|
|
155
|
+
"atan",
|
|
156
|
+
"atan2",
|
|
157
|
+
"sinh",
|
|
158
|
+
"cosh",
|
|
159
|
+
"tanh",
|
|
160
|
+
"exp",
|
|
161
|
+
"log",
|
|
162
|
+
"log10",
|
|
163
|
+
"pi",
|
|
164
|
+
"e",
|
|
165
|
+
}
|
|
146
166
|
|
|
147
167
|
|
|
148
168
|
def _load_io(name: str) -> Any:
|
|
@@ -151,6 +171,12 @@ def _load_io(name: str) -> Any:
|
|
|
151
171
|
return save_state if name == "save_state" else load_state
|
|
152
172
|
|
|
153
173
|
|
|
174
|
+
def _load_helpers(name: str) -> Any:
|
|
175
|
+
import physure.ext.helpers as helpers
|
|
176
|
+
|
|
177
|
+
return getattr(helpers, name)
|
|
178
|
+
|
|
179
|
+
|
|
154
180
|
def _load_q(name: str) -> Any:
|
|
155
181
|
from physure.application.factories import QuantityFactory
|
|
156
182
|
|
|
@@ -283,6 +309,8 @@ for _attr in ("equivalencies", "spectral", "thermodynamic"):
|
|
|
283
309
|
_ATTR_LOADERS[_attr] = _load_equivalencies
|
|
284
310
|
for _attr in _PLOT_ATTRS:
|
|
285
311
|
_ATTR_LOADERS[_attr] = _load_plotting
|
|
312
|
+
for _attr in _HELPER_ATTRS:
|
|
313
|
+
_ATTR_LOADERS[_attr] = _load_helpers
|
|
286
314
|
del _attr
|
|
287
315
|
|
|
288
316
|
|
|
@@ -332,15 +360,28 @@ __all__ = [
|
|
|
332
360
|
"Uncertainty",
|
|
333
361
|
"UnitNotFoundError",
|
|
334
362
|
"UnknownUnitError",
|
|
363
|
+
"acos",
|
|
364
|
+
"approx_eq",
|
|
365
|
+
"asin",
|
|
366
|
+
"atan",
|
|
367
|
+
"atan2",
|
|
368
|
+
"cos",
|
|
369
|
+
"cosh",
|
|
335
370
|
"create_default_system",
|
|
336
371
|
"create_system",
|
|
337
372
|
"default_system",
|
|
373
|
+
"e",
|
|
338
374
|
"equivalencies",
|
|
375
|
+
"exp",
|
|
339
376
|
"get_active_system",
|
|
340
377
|
"get_current_system",
|
|
341
378
|
"get_unit",
|
|
342
379
|
"jit",
|
|
380
|
+
"linspace",
|
|
343
381
|
"load_state",
|
|
382
|
+
"log",
|
|
383
|
+
"log10",
|
|
384
|
+
"pi",
|
|
344
385
|
"plot",
|
|
345
386
|
"plot_covariance",
|
|
346
387
|
"plot_interactive",
|
|
@@ -348,8 +389,13 @@ __all__ = [
|
|
|
348
389
|
"plot_parallel_coordinates",
|
|
349
390
|
"plot_slices",
|
|
350
391
|
"save_state",
|
|
392
|
+
"sin",
|
|
393
|
+
"sinh",
|
|
394
|
+
"sqrt",
|
|
351
395
|
"spectral",
|
|
352
396
|
"system_context",
|
|
397
|
+
"tan",
|
|
398
|
+
"tanh",
|
|
353
399
|
"thermodynamic",
|
|
354
400
|
"uncertainty_mode",
|
|
355
401
|
"units",
|
|
@@ -1144,10 +1144,24 @@ class ArithmeticMixin:
|
|
|
1144
1144
|
self._record_op(op, other, res)
|
|
1145
1145
|
return res
|
|
1146
1146
|
|
|
1147
|
+
def _reject_bare_number_add_sub(self, other: Quantity | Numeric) -> None:
|
|
1148
|
+
"""A bare number carries no unit: '5 m + 2' must raise, never.
|
|
1149
|
+
|
|
1150
|
+
silently assume the 2 is metres. Only a dimensionless quantity
|
|
1151
|
+
may absorb bare numbers additively.
|
|
1152
|
+
"""
|
|
1153
|
+
if isinstance(other, _q()) or self.dimension.is_dimensionless:
|
|
1154
|
+
return
|
|
1155
|
+
if isinstance(other, (int, float, complex)) or self._backend.is_array(
|
|
1156
|
+
other
|
|
1157
|
+
):
|
|
1158
|
+
raise IncompatibleUnitsError(self.unit, CompoundUnit({}))
|
|
1159
|
+
|
|
1147
1160
|
def _add_sub_impl(
|
|
1148
1161
|
self, other: Quantity | Numeric, is_add: bool
|
|
1149
1162
|
) -> Quantity:
|
|
1150
1163
|
op = "add" if is_add else "sub"
|
|
1164
|
+
self._reject_bare_number_add_sub(other)
|
|
1151
1165
|
rust_res = self._check_and_handle_rust_propagation(other, op)
|
|
1152
1166
|
if rust_res is not None:
|
|
1153
1167
|
return rust_res
|
|
@@ -1186,6 +1200,7 @@ class ArithmeticMixin:
|
|
|
1186
1200
|
|
|
1187
1201
|
def __rsub__(self, other: Quantity | Numeric) -> Quantity:
|
|
1188
1202
|
"""Right subtraction."""
|
|
1203
|
+
self._reject_bare_number_add_sub(other)
|
|
1189
1204
|
rust_res = self._check_and_handle_rust_propagation(other, "rsub")
|
|
1190
1205
|
if rust_res is not None:
|
|
1191
1206
|
return rust_res
|
|
@@ -140,8 +140,8 @@ def _add_like_unit(nodes: tuple[Node, ...]) -> CompoundUnit | None:
|
|
|
140
140
|
return result
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
def
|
|
144
|
-
return
|
|
143
|
+
def _infer_unit_number(node: Number) -> CompoundUnit | None:
|
|
144
|
+
return CompoundUnit({})
|
|
145
145
|
|
|
146
146
|
|
|
147
147
|
def _infer_unit_quantity(node: Quantity) -> CompoundUnit | None:
|
|
@@ -198,9 +198,13 @@ def _infer_unit_transcendental(
|
|
|
198
198
|
return None
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
def _infer_unit_symbol(node: Symbol) -> CompoundUnit | None:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
201
205
|
_INFER_UNIT_DISPATCH: dict[type, Callable[[Any], CompoundUnit | None]] = {
|
|
202
|
-
Number:
|
|
203
|
-
Symbol:
|
|
206
|
+
Number: _infer_unit_number,
|
|
207
|
+
Symbol: _infer_unit_symbol,
|
|
204
208
|
Quantity: _infer_unit_quantity,
|
|
205
209
|
Add: _infer_unit_add,
|
|
206
210
|
Sub: _infer_unit_sub,
|
|
@@ -73,7 +73,7 @@ _NUMBER_PAT = r"\d+\.?\d*(?:[eE]\s*[+-]?\s*\d+)?|\.\d+(?:[eE]\s*[+-]?\s*\d+)?"
|
|
|
73
73
|
_IDENT_PAT = r"[^\W\d]\w*"
|
|
74
74
|
_SUP_PAT = r"[⁻⁰¹²³⁴⁵⁶⁷⁸⁹]+"
|
|
75
75
|
_SQRT_PAT = r"√"
|
|
76
|
-
_OP_PAT = r"
|
|
76
|
+
_OP_PAT = r"\+/-|±|<=|>=|!=|==|=>|->|≈|\*\s*\*|\*\*|[-+*/^()=?<>×÷,:|]" # noqa: RUF001
|
|
77
77
|
|
|
78
78
|
_OP_ALIASES = {"×": "*", "÷": "/"} # noqa: RUF001
|
|
79
79
|
_TOKEN_RE = re.compile(
|
|
@@ -158,6 +158,25 @@ def _top_level_index(tokens: list[Token], op: str) -> int:
|
|
|
158
158
|
return -1
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
def _approx_eq(
|
|
162
|
+
a: Any, b: Any, rel_tol: float = 0.1, abs_tol: float = 1e-5
|
|
163
|
+
) -> bool:
|
|
164
|
+
a_mag = getattr(a, "magnitude", a)
|
|
165
|
+
b_mag = getattr(b, "magnitude", b)
|
|
166
|
+
if hasattr(a, "to") and hasattr(b, "unit"):
|
|
167
|
+
try:
|
|
168
|
+
b_converted = b.to(a.unit)
|
|
169
|
+
b_mag = b_converted.magnitude
|
|
170
|
+
except Exception:
|
|
171
|
+
return False
|
|
172
|
+
try:
|
|
173
|
+
return math.isclose(
|
|
174
|
+
float(a_mag), float(b_mag), rel_tol=rel_tol, abs_tol=abs_tol
|
|
175
|
+
)
|
|
176
|
+
except Exception:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
|
|
161
180
|
_COMPARISONS: dict[str, Callable[[Any, Any], bool]] = {
|
|
162
181
|
"<": operator.lt,
|
|
163
182
|
">": operator.gt,
|
|
@@ -165,6 +184,7 @@ _COMPARISONS: dict[str, Callable[[Any, Any], bool]] = {
|
|
|
165
184
|
">=": operator.ge,
|
|
166
185
|
"==": operator.eq,
|
|
167
186
|
"!=": operator.ne,
|
|
187
|
+
"≈": _approx_eq,
|
|
168
188
|
}
|
|
169
189
|
|
|
170
190
|
|
|
@@ -511,9 +531,160 @@ _FUNCTIONS: dict[str, tuple[int, float, Callable[..., GrammarValue]]] = {
|
|
|
511
531
|
"exp": (1, 1, lambda x: _transcendental(x, "exp")),
|
|
512
532
|
"log": (1, 1, lambda x: _transcendental(x, "log")),
|
|
513
533
|
"ln": (1, 1, lambda x: _transcendental(x, "log")),
|
|
534
|
+
"linspace": (2, 3, lambda *a: _linspace_fn(*a)),
|
|
535
|
+
"plot": (1, 3, lambda *a: _plot_fn(*a)),
|
|
514
536
|
}
|
|
515
537
|
|
|
516
538
|
|
|
539
|
+
def _linspace_fn(
|
|
540
|
+
start: GrammarValue, stop: GrammarValue, num: GrammarValue = 50
|
|
541
|
+
) -> Any:
|
|
542
|
+
import numpy as np
|
|
543
|
+
|
|
544
|
+
from physure.domain.measurement.quantity import Quantity
|
|
545
|
+
|
|
546
|
+
start_mag = getattr(start, "magnitude", start)
|
|
547
|
+
stop_mag = getattr(stop, "magnitude", stop)
|
|
548
|
+
unit = getattr(start, "unit", getattr(stop, "unit", None))
|
|
549
|
+
system = getattr(start, "system", getattr(stop, "system", None))
|
|
550
|
+
|
|
551
|
+
arr = np.linspace(float(start_mag), float(stop_mag), int(num))
|
|
552
|
+
if unit is not None:
|
|
553
|
+
return Quantity.from_input(arr, unit, system)
|
|
554
|
+
return arr
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _format_unit_str(unit_raw: str) -> str:
|
|
558
|
+
u = str(unit_raw).strip()
|
|
559
|
+
if u in ("1", "", "None"):
|
|
560
|
+
return ""
|
|
561
|
+
aliases = {
|
|
562
|
+
"kg·m/s²": "N",
|
|
563
|
+
"kg*m/s^2": "N",
|
|
564
|
+
"kg·m³/(A·s³)": "N/C",
|
|
565
|
+
"A·s": "C",
|
|
566
|
+
}
|
|
567
|
+
return aliases.get(u, u)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _draw_ascii_plot(
|
|
571
|
+
x: Any, y: Any, title: str = "Plot", x_unit: str = "", y_unit: str = ""
|
|
572
|
+
) -> str:
|
|
573
|
+
import numpy as np
|
|
574
|
+
|
|
575
|
+
x_arr = np.asarray(x, dtype=float)
|
|
576
|
+
y_arr = np.asarray(y, dtype=float)
|
|
577
|
+
if len(x_arr) == 0 or len(y_arr) == 0:
|
|
578
|
+
return f"📊 {title}: [No data points]"
|
|
579
|
+
|
|
580
|
+
width = 46
|
|
581
|
+
height = 8
|
|
582
|
+
|
|
583
|
+
x_grid = np.linspace(x_arr.min(), x_arr.max(), width)
|
|
584
|
+
y_grid = np.interp(x_grid, x_arr, y_arr)
|
|
585
|
+
|
|
586
|
+
y_min, y_max = y_grid.min(), y_grid.max()
|
|
587
|
+
y_span = (y_max - y_min) if (y_max != y_min) else 1.0
|
|
588
|
+
|
|
589
|
+
fmt_x = _format_unit_str(x_unit)
|
|
590
|
+
fmt_y = _format_unit_str(y_unit)
|
|
591
|
+
|
|
592
|
+
lines = []
|
|
593
|
+
lines.append(f" 📊 {title}")
|
|
594
|
+
lines.append(f" {y_max:.3e} {fmt_y}".strip().rjust(18) + " ┐")
|
|
595
|
+
|
|
596
|
+
for r in range(height - 1, -1, -1):
|
|
597
|
+
y_level = y_min + (r / (height - 1)) * y_span
|
|
598
|
+
row_chars = []
|
|
599
|
+
for c in range(width):
|
|
600
|
+
val = y_grid[c]
|
|
601
|
+
diff = abs(val - y_level) / y_span
|
|
602
|
+
if diff < (1.0 / (2 * height)):
|
|
603
|
+
row_chars.append("█")
|
|
604
|
+
elif val > y_level:
|
|
605
|
+
row_chars.append("░")
|
|
606
|
+
else:
|
|
607
|
+
row_chars.append(" ")
|
|
608
|
+
lines.append(" │ " + "".join(row_chars))
|
|
609
|
+
|
|
610
|
+
lines.append(
|
|
611
|
+
f" {y_min:.3e} {fmt_y}".strip().rjust(18) + " └" + "─" * width
|
|
612
|
+
)
|
|
613
|
+
x_min_str = f"{x_arr.min():.3e} {fmt_x}".strip()
|
|
614
|
+
x_max_str = f"{x_arr.max():.3e} {fmt_x}".strip()
|
|
615
|
+
pad_len = max(1, width - len(x_min_str) - len(x_max_str) + 12)
|
|
616
|
+
lines.append(
|
|
617
|
+
" " + x_min_str + " " * pad_len + x_max_str
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
return "\n".join(lines)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _plot_fn(*args: Any, **kwargs: Any) -> Any:
|
|
624
|
+
import base64
|
|
625
|
+
import io
|
|
626
|
+
|
|
627
|
+
import matplotlib
|
|
628
|
+
|
|
629
|
+
matplotlib.use("Agg")
|
|
630
|
+
import matplotlib.pyplot as plt
|
|
631
|
+
import numpy as np
|
|
632
|
+
|
|
633
|
+
x_val = args[0] if len(args) >= 2 else None
|
|
634
|
+
y_val = args[1] if len(args) >= 2 else args[0]
|
|
635
|
+
|
|
636
|
+
y_mag = getattr(y_val, "magnitude", y_val)
|
|
637
|
+
y_unit = _format_unit_str(getattr(y_val, "unit", ""))
|
|
638
|
+
y_arr = np.asarray(y_mag, dtype=float)
|
|
639
|
+
|
|
640
|
+
if x_val is not None:
|
|
641
|
+
x_mag = getattr(x_val, "magnitude", x_val)
|
|
642
|
+
x_unit = _format_unit_str(getattr(x_val, "unit", ""))
|
|
643
|
+
x_arr = np.asarray(x_mag, dtype=float)
|
|
644
|
+
else:
|
|
645
|
+
x_arr = np.arange(len(y_arr))
|
|
646
|
+
x_unit = ""
|
|
647
|
+
|
|
648
|
+
title = str(kwargs.get("title", "Physure Live Plot"))
|
|
649
|
+
ascii_plot = _draw_ascii_plot(
|
|
650
|
+
x_arr, y_arr, title=title, x_unit=x_unit, y_unit=y_unit
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
fig, ax = plt.subplots(figsize=(6.5, 3.8))
|
|
654
|
+
ax.plot(x_arr, y_arr, color="#4ec9b0", linewidth=2.5)
|
|
655
|
+
ax.fill_between(x_arr, y_arr, color="#4ec9b0", alpha=0.12)
|
|
656
|
+
|
|
657
|
+
x_label_str = f"x ({x_unit})" if x_unit else "x"
|
|
658
|
+
y_label_str = f"y ({y_unit})" if y_unit else "y"
|
|
659
|
+
|
|
660
|
+
ax.set_xlabel(x_label_str, color="#cccccc", fontsize=9.5)
|
|
661
|
+
ax.set_ylabel(y_label_str, color="#cccccc", fontsize=9.5)
|
|
662
|
+
|
|
663
|
+
ax.set_title(
|
|
664
|
+
title, color="#569cd6", fontsize=11, fontweight="bold", pad=12
|
|
665
|
+
)
|
|
666
|
+
ax.grid(True, linestyle=":", alpha=0.35, color="#666666")
|
|
667
|
+
ax.tick_params(colors="#cccccc", labelsize=8.5)
|
|
668
|
+
|
|
669
|
+
for spine in ax.spines.values():
|
|
670
|
+
spine.set_color("#444444")
|
|
671
|
+
ax.spines["top"].set_visible(False)
|
|
672
|
+
ax.spines["right"].set_visible(False)
|
|
673
|
+
|
|
674
|
+
fig.patch.set_facecolor("#1e1e1e")
|
|
675
|
+
ax.set_facecolor("#252526")
|
|
676
|
+
fig.tight_layout()
|
|
677
|
+
|
|
678
|
+
buf = io.BytesIO()
|
|
679
|
+
fig.savefig(buf, format="png", bbox_inches="tight", dpi=140)
|
|
680
|
+
buf.seek(0)
|
|
681
|
+
b64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
682
|
+
plt.close(fig)
|
|
683
|
+
|
|
684
|
+
b64_tag = f"[PLOT_IMAGE:data:image/png;base64,{b64}]"
|
|
685
|
+
return f"{ascii_plot}\n{b64_tag}"
|
|
686
|
+
|
|
687
|
+
|
|
517
688
|
def _format_sig_figs(val: GrammarValue, sig_figs: int) -> GrammarValue:
|
|
518
689
|
"""Formats a scalar or Quantity magnitude to sig_figs significant figures."""
|
|
519
690
|
mag = getattr(val, "magnitude", val)
|
|
@@ -584,7 +755,7 @@ class GrammarInterpreter:
|
|
|
584
755
|
text = text[1:]
|
|
585
756
|
if text.endswith("\n"):
|
|
586
757
|
text = text[:-1]
|
|
587
|
-
results.append(text)
|
|
758
|
+
results.append(self._interpolate_text(text))
|
|
588
759
|
current_line += match.group(0).count("\n")
|
|
589
760
|
pos = match.end()
|
|
590
761
|
|
|
@@ -592,6 +763,21 @@ class GrammarInterpreter:
|
|
|
592
763
|
results.extend(self._run_segment(tail, start_line_num=current_line))
|
|
593
764
|
return results
|
|
594
765
|
|
|
766
|
+
def _interpolate_text(self, text: str) -> str:
|
|
767
|
+
def replacer(m: re.Match[str]) -> str:
|
|
768
|
+
expr = m.group(1).strip()
|
|
769
|
+
try:
|
|
770
|
+
res_list = self._run_segment(expr)
|
|
771
|
+
if res_list:
|
|
772
|
+
val = res_list[-1]
|
|
773
|
+
if val is not None:
|
|
774
|
+
return str(val)
|
|
775
|
+
except Exception as e:
|
|
776
|
+
return f"{{ERR: {e}}}"
|
|
777
|
+
return m.group(0)
|
|
778
|
+
|
|
779
|
+
return re.sub(r"\{([^}]+)\}", replacer, text)
|
|
780
|
+
|
|
595
781
|
def _run_segment(
|
|
596
782
|
self, segment: str, start_line_num: int = 1
|
|
597
783
|
) -> list[GrammarValue | None]:
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Helper functions for exported Physure DSL scripts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from physure.application.factories import QuantityFactory
|
|
9
|
+
|
|
10
|
+
Q_ = QuantityFactory()
|
|
11
|
+
|
|
12
|
+
pi = math.pi
|
|
13
|
+
e = math.e
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _to_float(val: Any) -> float:
|
|
17
|
+
return float(getattr(val, "magnitude", val))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def approx_eq(
|
|
21
|
+
a: Any, b: Any, rel_tol: float = 0.1, abs_tol: float = 1e-5
|
|
22
|
+
) -> bool:
|
|
23
|
+
"""Check approximate equality between numbers or Quantities."""
|
|
24
|
+
if hasattr(a, "approx_eq") and hasattr(b, "_core") and hasattr(a, "_core"):
|
|
25
|
+
try:
|
|
26
|
+
return bool(a._core.approx_eq(b._core, rel_tol, abs_tol))
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
a_mag = getattr(a, "magnitude", a)
|
|
30
|
+
b_mag = getattr(b, "magnitude", b)
|
|
31
|
+
if hasattr(a, "to") and hasattr(b, "unit"):
|
|
32
|
+
try:
|
|
33
|
+
b_mag = b.to(a.unit).magnitude
|
|
34
|
+
except Exception:
|
|
35
|
+
return False
|
|
36
|
+
try:
|
|
37
|
+
return math.isclose(
|
|
38
|
+
float(a_mag), float(b_mag), rel_tol=rel_tol, abs_tol=abs_tol
|
|
39
|
+
)
|
|
40
|
+
except Exception:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def linspace(start: Any, stop: Any, num: int = 50) -> Any:
|
|
45
|
+
"""Generate linearly spaced numbers or Quantities."""
|
|
46
|
+
import numpy as np
|
|
47
|
+
|
|
48
|
+
start_val = getattr(start, "magnitude", start)
|
|
49
|
+
stop_val = getattr(stop, "magnitude", stop)
|
|
50
|
+
unit = getattr(start, "unit", getattr(stop, "unit", None))
|
|
51
|
+
arr = np.linspace(float(start_val), float(stop_val), int(num))
|
|
52
|
+
if unit:
|
|
53
|
+
return Q_(arr, str(unit))
|
|
54
|
+
return arr
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def plot(*args: Any) -> None:
|
|
58
|
+
"""Plot physical quantities using matplotlib."""
|
|
59
|
+
try:
|
|
60
|
+
import matplotlib.pyplot as plt
|
|
61
|
+
|
|
62
|
+
_fig, ax = plt.subplots()
|
|
63
|
+
if len(args) >= 2:
|
|
64
|
+
x, y = args[0], args[1]
|
|
65
|
+
x_val = getattr(x, "magnitude", x)
|
|
66
|
+
y_val = getattr(y, "magnitude", y)
|
|
67
|
+
ax.plot(x_val, y_val)
|
|
68
|
+
elif len(args) == 1:
|
|
69
|
+
y = args[0]
|
|
70
|
+
y_val = getattr(y, "magnitude", y)
|
|
71
|
+
ax.plot(y_val)
|
|
72
|
+
plt.show()
|
|
73
|
+
except Exception as err:
|
|
74
|
+
print(f"Plot error: {err}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def sqrt(x: Any) -> Any:
|
|
78
|
+
"""Square root supporting Quantities and numbers."""
|
|
79
|
+
if hasattr(x, "unit") or hasattr(x, "magnitude"):
|
|
80
|
+
return x**0.5
|
|
81
|
+
return math.sqrt(float(x))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def sin(x: Any) -> float:
|
|
85
|
+
"""Sine of scalar value."""
|
|
86
|
+
return math.sin(_to_float(x))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cos(x: Any) -> float:
|
|
90
|
+
"""Cosine of scalar value."""
|
|
91
|
+
return math.cos(_to_float(x))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def tan(x: Any) -> float:
|
|
95
|
+
"""Tangent of scalar value."""
|
|
96
|
+
return math.tan(_to_float(x))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def asin(x: Any) -> float:
|
|
100
|
+
"""Arcsine of scalar value."""
|
|
101
|
+
return math.asin(_to_float(x))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def acos(x: Any) -> float:
|
|
105
|
+
"""Arccosine of scalar value."""
|
|
106
|
+
return math.acos(_to_float(x))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def atan(x: Any) -> float:
|
|
110
|
+
"""Arctangent of scalar value."""
|
|
111
|
+
return math.atan(_to_float(x))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def atan2(y: Any, x: Any) -> float:
|
|
115
|
+
"""Two-argument arctangent of scalar values."""
|
|
116
|
+
return math.atan2(_to_float(y), _to_float(x))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def sinh(x: Any) -> float:
|
|
120
|
+
"""Hyperbolic sine of scalar value."""
|
|
121
|
+
return math.sinh(_to_float(x))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def cosh(x: Any) -> float:
|
|
125
|
+
"""Hyperbolic cosine of scalar value."""
|
|
126
|
+
return math.cosh(_to_float(x))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def tanh(x: Any) -> float:
|
|
130
|
+
"""Hyperbolic tangent of scalar value."""
|
|
131
|
+
return math.tanh(_to_float(x))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def exp(x: Any) -> float:
|
|
135
|
+
"""Exponential of scalar value."""
|
|
136
|
+
return math.exp(_to_float(x))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def log(x: Any) -> float:
|
|
140
|
+
"""Natural logarithm of scalar value."""
|
|
141
|
+
return math.log(_to_float(x))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def log10(x: Any) -> float:
|
|
145
|
+
"""Base-10 logarithm of scalar value."""
|
|
146
|
+
return math.log10(_to_float(x))
|
|
@@ -24,7 +24,15 @@ _BANNER = (
|
|
|
24
24
|
def _print_results(results: list[Any]) -> None:
|
|
25
25
|
for result in results:
|
|
26
26
|
if result is not None:
|
|
27
|
-
|
|
27
|
+
s = str(result)
|
|
28
|
+
lines = [
|
|
29
|
+
line
|
|
30
|
+
for line in s.split("\n")
|
|
31
|
+
if not line.startswith("[PLOT_IMAGE:")
|
|
32
|
+
]
|
|
33
|
+
clean_str = "\n".join(lines).rstrip()
|
|
34
|
+
if clean_str:
|
|
35
|
+
print(clean_str)
|
|
28
36
|
|
|
29
37
|
|
|
30
38
|
def _run_source(source: str) -> int:
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Alexisrx96/physure/main/assets/logo-horizontal-dark.svg">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Alexisrx96/physure/main/assets/logo-horizontal-light.svg" alt="physure" width="380">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
1
6
|
# physure 🦀
|
|
2
7
|
|
|
3
|
-
[](https://crates.io/crates/physure)
|
|
4
|
-
[](https://crates.io/crates/physure)
|
|
9
|
+
[](https://docs.rs/physure)
|
|
10
|
+
[](https://github.com/Alexisrx96/physure/actions/workflows/tests.yml)
|
|
11
|
+
[](https://github.com/Alexisrx96/physure/blob/main/LICENSE)
|
|
5
12
|
|
|
6
13
|
**Pure Rust physics engine: dimensional analysis with rational exponents, physical quantities, and uncertainty propagation.**
|
|
7
14
|
|