From 7a04ba014bec74074cc196f76e975c1d4d9562a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 15:23:16 +0000 Subject: [PATCH 1/2] Rename the package to blochsim and add its logo The distribution, import name, repository links and documentation are blochsim. scripts/make_artwork.py draws the logo and the mark (a Bloch sphere with a precessing magnetization vector), light and dark, into docs/_static; the README and the documentation sidebar show them. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01B57XCqwDsAPeMVUSDSGxc5 --- .github/DISCUSSION_TEMPLATE/ideas.yml | 2 +- .github/DISCUSSION_TEMPLATE/q-a.yml | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +- .github/ISSUE_TEMPLATE/config.yml | 4 +- .github/ISSUE_TEMPLATE/feature_request.yml | 4 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/SECURITY.md | 6 +- .github/skills/add-a-sequence/SKILL.md | 10 +- .github/skills/build-and-test/SKILL.md | 8 +- .github/skills/build-the-docs/SKILL.md | 22 +-- .github/skills/cut-a-release/SKILL.md | 4 +- .github/workflows/docs.yml | 10 +- .github/workflows/test.yml | 4 +- .github/workflows/wheels.yml | 6 +- .gitignore | 2 +- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 56 +++--- CLAUDE.md | 22 +-- CMakeLists.txt | 16 +- README.md | 43 ++--- benchmarks/README.md | 40 ++--- benchmarks/anatomy.py | 12 +- .../{bench_torchsim.py => bench_blochsim.py} | 14 +- benchmarks/bench_epgpy.py | 4 +- benchmarks/julia/Project.toml | 2 +- benchmarks/julia/bench_blochsimulators.jl | 6 +- benchmarks/make_figures.py | 6 +- benchmarks/results/anatomy.txt | 4 +- ...t4.json => blochsim-forward-1-cpu-t4.json} | 4 +- ...cuda.json => blochsim-forward-1-cuda.json} | 4 +- ...4.json => blochsim-forward-10-cpu-t4.json} | 4 +- ...uda.json => blochsim-forward-10-cuda.json} | 4 +- ....json => blochsim-forward-100-cpu-t4.json} | 4 +- ...da.json => blochsim-forward-100-cuda.json} | 4 +- ...json => blochsim-forward-1000-cpu-t1.json} | 4 +- ...json => blochsim-forward-1000-cpu-t4.json} | 4 +- ...a.json => blochsim-forward-1000-cuda.json} | 4 +- ...son => blochsim-forward-10000-cpu-t1.json} | 4 +- ...son => blochsim-forward-10000-cpu-t4.json} | 4 +- ....json => blochsim-forward-10000-cuda.json} | 4 +- ...on => blochsim-forward-100000-cpu-t1.json} | 4 +- ...on => blochsim-forward-100000-cpu-t4.json} | 4 +- ...json => blochsim-forward-100000-cuda.json} | 4 +- ...4.json => blochsim-jacobian-1-cpu-t4.json} | 4 +- ...uda.json => blochsim-jacobian-1-cuda.json} | 4 +- ....json => blochsim-jacobian-10-cpu-t4.json} | 4 +- ...da.json => blochsim-jacobian-10-cuda.json} | 4 +- ...json => blochsim-jacobian-100-cpu-t4.json} | 4 +- ...a.json => blochsim-jacobian-100-cuda.json} | 4 +- ...son => blochsim-jacobian-1000-cpu-t4.json} | 4 +- ....json => blochsim-jacobian-1000-cuda.json} | 4 +- ...on => blochsim-jacobian-10000-cpu-t4.json} | 4 +- ...json => blochsim-jacobian-10000-cuda.json} | 4 +- ...n => blochsim-jacobian-100000-cpu-t4.json} | 4 +- ...son => blochsim-jacobian-100000-cuda.json} | 4 +- ....json => blochsim-jacobian1-1-cpu-t4.json} | 4 +- ...da.json => blochsim-jacobian1-1-cuda.json} | 4 +- ...json => blochsim-jacobian1-10-cpu-t4.json} | 4 +- ...a.json => blochsim-jacobian1-10-cuda.json} | 4 +- ...son => blochsim-jacobian1-100-cpu-t4.json} | 4 +- ....json => blochsim-jacobian1-100-cuda.json} | 4 +- ...on => blochsim-jacobian1-1000-cpu-t4.json} | 4 +- ...json => blochsim-jacobian1-1000-cuda.json} | 4 +- ...n => blochsim-jacobian1-10000-cpu-t4.json} | 4 +- ...son => blochsim-jacobian1-10000-cuda.json} | 4 +- ... => blochsim-jacobian1-100000-cpu-t4.json} | 4 +- ...on => blochsim-jacobian1-100000-cuda.json} | 4 +- benchmarks/results/epgpy-forward-1.json | 2 +- benchmarks/results/epgpy-forward-10.json | 2 +- benchmarks/results/epgpy-forward-100.json | 2 +- benchmarks/results/epgpy-forward-1000.json | 2 +- benchmarks/results/epgpy-forward-10000.json | 2 +- benchmarks/results/epgpy-jacobian-1.json | 2 +- benchmarks/results/epgpy-jacobian-10.json | 2 +- benchmarks/results/epgpy-jacobian-100.json | 2 +- benchmarks/results/epgpy-jacobian-1000.json | 2 +- benchmarks/results/epgpy-jacobian-10000.json | 2 +- benchmarks/results/table.md | 98 +++++------ benchmarks/results/validation.json | 2 +- benchmarks/results/validation.txt | 42 ++--- benchmarks/run_all.py | 20 +-- benchmarks/setup.sh | 8 +- benchmarks/validate.py | 24 +-- docs/_static/blochsim-logo-dark.svg | 19 +++ docs/_static/blochsim-logo.svg | 19 +++ docs/_static/blochsim-mark-dark.svg | 18 ++ docs/_static/blochsim-mark.svg | 18 ++ docs/api/estimators.md | 2 +- docs/api/execution.md | 4 +- docs/api/index.md | 4 +- docs/api/model.md | 16 +- docs/api/optim.md | 2 +- docs/api/recon.md | 2 +- docs/api/sequence.md | 8 +- docs/api/simulators.md | 74 ++++---- docs/binder/environment.yml | 4 +- docs/conf.py | 32 ++-- docs/developer_guide.md | 40 ++--- docs/explanation_figures.py | 14 +- docs/explanations/description.md | 16 +- docs/explanations/epg.md | 18 +- docs/explanations/implementation.md | 22 +-- docs/explanations/index.md | 6 +- docs/misc/contributors.md | 6 +- docs/misc/related.md | 12 +- docs/sphinx_add_colab_link.py | 4 +- docs/user_guide.md | 52 +++--- examples/01-framework/01-getting-started.py | 42 ++--- examples/01-framework/02-expanded-physics.py | 16 +- .../01-framework/03-writing-a-simulator.py | 24 +-- examples/01-framework/04-custom-operator.py | 12 +- examples/01-framework/README.rst | 4 +- .../01-dictionary-matching.py | 14 +- .../02-parameter-inference/02-lookup-table.py | 14 +- .../03-nonlinear-least-squares.py | 12 +- examples/02-parameter-inference/04-perk.py | 16 +- .../01-echo-train-design.py | 10 +- .../02-joint-relaxometry.py | 20 +-- .../03-rf-pulse-design.py | 6 +- examples/03-sequence-optimization/README.rst | 2 +- .../01-linear-subspace.py | 20 +-- .../02-nonlinear-inversion.py | 28 +-- examples/05-misc/01-synthetic-data.py | 14 +- examples/_gallery_header.md | 6 +- pyproject.toml | 34 ++-- scripts/build_docs.sh | 6 +- scripts/check_wheel.py | 8 +- scripts/make_artwork.py | 161 ++++++++++++++++++ scripts/publish_docs.py | 4 +- src/{torchsim => blochsim}/__init__.py | 2 +- src/{torchsim => blochsim}/_bounds.py | 0 src/{torchsim => blochsim}/_epg_cpu.cpp | 72 ++++---- src/{torchsim => blochsim}/_execution.py | 0 .../_functional/__init__.py | 0 .../_functional/_bssfp.py | 0 .../_functional/_fse.py | 0 .../_functional/_mp2rage.py | 0 .../_functional/_mpnrage.py | 0 .../_functional/_mprage.py | 0 .../_functional/_mrf.py | 0 .../_functional/_run.py | 0 .../_functional/_spgr.py | 0 src/{torchsim => blochsim}/_ifunc.hpp | 16 +- src/{torchsim => blochsim}/_perk_cpu.cpp | 18 +- src/{torchsim => blochsim}/_subspace.py | 2 +- src/{torchsim => blochsim}/_threads.hpp | 12 +- .../estimators/__init__.py | 2 +- .../estimators/_dictionary.py | 8 +- .../estimators/_grouped.py | 0 .../estimators/_lookup.py | 12 +- .../estimators/_mapping.py | 12 +- .../estimators/_nlls.py | 24 +-- .../estimators/_perk.py | 10 +- .../estimators/_perk_native.py | 0 .../estimators/_perk_triton.py | 0 src/{torchsim => blochsim}/model/__init__.py | 0 src/{torchsim => blochsim}/model/_binding.py | 4 +- src/{torchsim => blochsim}/model/_signal.py | 6 +- .../model/_state_machine.py | 20 +-- src/{torchsim => blochsim}/optim/__init__.py | 0 src/{torchsim => blochsim}/optim/_design.py | 0 src/{torchsim => blochsim}/recon/__init__.py | 0 .../recon/_gauss_newton.py | 10 +- src/{torchsim => blochsim}/recon/_operator.py | 12 +- .../sequence/__init__.py | 0 .../sequence/_accelerators.py | 20 +-- src/{torchsim => blochsim}/sequence/_array.py | 2 +- .../sequence/_builders.py | 0 .../sequence/_calibration.py | 0 .../sequence/_description.py | 2 +- .../sequence/_epg_triton.py | 6 +- .../sequence/_fixed_point.py | 0 .../sequence/_lineshape.py | 0 src/{torchsim => blochsim}/sequence/_mrd.py | 0 .../sequence/_operators.py | 0 .../sequence/_parameters.py | 4 +- src/{torchsim => blochsim}/sequence/_pools.py | 0 .../sequence/_pools_triton.py | 2 +- .../sequence/_pulseq.py | 6 +- .../sequence/_settling.py | 0 .../sequence/_simulation.py | 10 +- .../sequence/_transition.py | 0 .../sequence/_transmit.py | 0 .../simulators/__init__.py | 2 +- .../simulators/_contrast.py | 0 .../simulators/_pulses.py | 2 +- src/{torchsim => blochsim}/simulators/asl.py | 2 +- .../simulators/bssfp.py | 2 +- src/{torchsim => blochsim}/simulators/cest.py | 4 +- .../simulators/diffusion.py | 2 +- .../simulators/flash.py | 2 +- .../simulators/fmssfp.py | 2 +- src/{torchsim => blochsim}/simulators/fse.py | 4 +- .../simulators/gradient_echo.py | 8 +- src/{torchsim => blochsim}/simulators/hsfp.py | 2 +- .../simulators/looklocker.py | 6 +- .../simulators/mp2rage.py | 2 +- .../simulators/mpnrage.py | 2 +- .../simulators/mprage.py | 2 +- src/{torchsim => blochsim}/simulators/mrf.py | 2 +- .../simulators/relaxometry.py | 6 +- src/{torchsim => blochsim}/simulators/spgr.py | 2 +- .../simulators/spin_echo.py | 2 +- .../simulators/stimulated_echo.py | 2 +- .../simulators/truefisp.py | 2 +- src/{torchsim => blochsim}/utils/__init__.py | 0 src/{torchsim => blochsim}/utils/_b1rms.py | 0 tests/conftest.py | 2 +- tests/estimators/test_dictionary.py | 4 +- tests/estimators/test_grouped.py | 10 +- tests/estimators/test_lookup.py | 4 +- tests/estimators/test_mapping.py | 4 +- tests/estimators/test_mapping_comparison.py | 4 +- tests/estimators/test_nlls.py | 12 +- tests/estimators/test_perk.py | 6 +- tests/estimators/test_perk_kernel.py | 6 +- tests/estimators/test_streaming.py | 8 +- tests/estimators/test_subspace.py | 4 +- tests/estimators/test_uncertainty.py | 12 +- tests/functionals/test_bssfp_func.py | 2 +- tests/functionals/test_fse_func.py | 4 +- tests/functionals/test_mp2rage_func.py | 2 +- tests/functionals/test_mp2rage_reference.py | 4 +- tests/functionals/test_mpnrage_func.py | 2 +- tests/functionals/test_mprage_func.py | 2 +- tests/functionals/test_mrf_func.py | 4 +- tests/functionals/test_spgr_func.py | 2 +- tests/model/test_array_backends.py | 20 +-- tests/model/test_jacobian_contract.py | 14 +- tests/model/test_model_physics.py | 6 +- tests/model/test_signal_model.py | 4 +- tests/model/test_state_machine.py | 22 +-- tests/optim/test_binding.py | 10 +- tests/optim/test_design.py | 4 +- tests/recon/test_constraints.py | 8 +- tests/recon/test_deepinv.py | 10 +- tests/recon/test_gauss_newton.py | 12 +- tests/recon/test_operator.py | 6 +- tests/recon/test_subspace_route.py | 6 +- tests/sequence/test_against_epgpy.py | 14 +- tests/sequence/test_both_pools.py | 56 +++--- tests/sequence/test_bound_pool.py | 60 +++---- tests/sequence/test_calibration.py | 2 +- tests/sequence/test_cuda_parity.py | 24 +-- tests/sequence/test_description.py | 2 +- tests/sequence/test_description_helper.py | 8 +- tests/sequence/test_diffusion_kernels.py | 8 +- tests/sequence/test_distribute.py | 12 +- tests/sequence/test_dynamic_transmit.py | 144 ++++++++-------- tests/sequence/test_engine_coverage.py | 16 +- tests/sequence/test_exact_profile.py | 28 +-- tests/sequence/test_exchange.py | 46 ++--- tests/sequence/test_execution.py | 10 +- tests/sequence/test_feature_gates.py | 24 +-- tests/sequence/test_features.py | 4 +- tests/sequence/test_first_order_adjoint.py | 8 +- tests/sequence/test_fixed_point.py | 8 +- tests/sequence/test_flow_kernels.py | 8 +- tests/sequence/test_gradient_operators.py | 2 +- tests/sequence/test_host_feature_mask.py | 8 +- tests/sequence/test_lineshape.py | 2 +- tests/sequence/test_many_pools.py | 16 +- tests/sequence/test_mrd_reader.py | 8 +- tests/sequence/test_offload.py | 34 ++-- tests/sequence/test_operators.py | 8 +- tests/sequence/test_parameters.py | 8 +- tests/sequence/test_pulseq_reader.py | 8 +- tests/sequence/test_real_subspace.py | 68 ++++---- tests/sequence/test_rf_definition.py | 6 +- tests/sequence/test_settling.py | 8 +- tests/sequence/test_simulation.py | 8 +- tests/sequence/test_slice_table.py | 30 ++-- tests/sequence/test_ssfp_readouts.py | 10 +- tests/sequence/test_static_spread.py | 14 +- tests/sequence/test_steady_state.py | 6 +- tests/sequence/test_subspace_streams.py | 6 +- tests/sequence/test_train_batching.py | 16 +- tests/sequence/test_transition.py | 8 +- tests/sequence/test_transmit_array.py | 22 +-- tests/sequence/test_unrefocused_time.py | 8 +- tests/sequence/test_vjp.py | 16 +- tests/sequence/test_washout_kernels.py | 8 +- tests/sequence/test_worker_pool.py | 10 +- tests/simulators/test_closed_forms.py | 2 +- tests/simulators/test_epg_trains.py | 2 +- tests/simulators/test_sampled_pulses.py | 4 +- tests/utils/interpreted.py | 66 +++---- tests/utils/interpreted_pools.py | 8 +- tests/utils/packed_reference.py | 12 +- 289 files changed, 1656 insertions(+), 1406 deletions(-) rename benchmarks/{bench_torchsim.py => bench_blochsim.py} (89%) rename benchmarks/results/{torchsim-forward-1-cpu-t4.json => blochsim-forward-1-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-forward-1-cuda.json => blochsim-forward-1-cuda.json} (94%) rename benchmarks/results/{torchsim-forward-10-cpu-t4.json => blochsim-forward-10-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-forward-10-cuda.json => blochsim-forward-10-cuda.json} (94%) rename benchmarks/results/{torchsim-forward-100-cpu-t4.json => blochsim-forward-100-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-forward-100-cuda.json => blochsim-forward-100-cuda.json} (94%) rename benchmarks/results/{torchsim-forward-1000-cpu-t1.json => blochsim-forward-1000-cpu-t1.json} (92%) rename benchmarks/results/{torchsim-forward-1000-cpu-t4.json => blochsim-forward-1000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-forward-1000-cuda.json => blochsim-forward-1000-cuda.json} (94%) rename benchmarks/results/{torchsim-forward-10000-cpu-t1.json => blochsim-forward-10000-cpu-t1.json} (92%) rename benchmarks/results/{torchsim-forward-10000-cpu-t4.json => blochsim-forward-10000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-forward-10000-cuda.json => blochsim-forward-10000-cuda.json} (94%) rename benchmarks/results/{torchsim-forward-100000-cpu-t1.json => blochsim-forward-100000-cpu-t1.json} (92%) rename benchmarks/results/{torchsim-forward-100000-cpu-t4.json => blochsim-forward-100000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-forward-100000-cuda.json => blochsim-forward-100000-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian-1-cpu-t4.json => blochsim-jacobian-1-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian-1-cuda.json => blochsim-jacobian-1-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian-10-cpu-t4.json => blochsim-jacobian-10-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian-10-cuda.json => blochsim-jacobian-10-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian-100-cpu-t4.json => blochsim-jacobian-100-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian-100-cuda.json => blochsim-jacobian-100-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian-1000-cpu-t4.json => blochsim-jacobian-1000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian-1000-cuda.json => blochsim-jacobian-1000-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian-10000-cpu-t4.json => blochsim-jacobian-10000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian-10000-cuda.json => blochsim-jacobian-10000-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian-100000-cpu-t4.json => blochsim-jacobian-100000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian-100000-cuda.json => blochsim-jacobian-100000-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian1-1-cpu-t4.json => blochsim-jacobian1-1-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian1-1-cuda.json => blochsim-jacobian1-1-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian1-10-cpu-t4.json => blochsim-jacobian1-10-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian1-10-cuda.json => blochsim-jacobian1-10-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian1-100-cpu-t4.json => blochsim-jacobian1-100-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian1-100-cuda.json => blochsim-jacobian1-100-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian1-1000-cpu-t4.json => blochsim-jacobian1-1000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian1-1000-cuda.json => blochsim-jacobian1-1000-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian1-10000-cpu-t4.json => blochsim-jacobian1-10000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian1-10000-cuda.json => blochsim-jacobian1-10000-cuda.json} (94%) rename benchmarks/results/{torchsim-jacobian1-100000-cpu-t4.json => blochsim-jacobian1-100000-cpu-t4.json} (92%) rename benchmarks/results/{torchsim-jacobian1-100000-cuda.json => blochsim-jacobian1-100000-cuda.json} (94%) create mode 100644 docs/_static/blochsim-logo-dark.svg create mode 100644 docs/_static/blochsim-logo.svg create mode 100644 docs/_static/blochsim-mark-dark.svg create mode 100644 docs/_static/blochsim-mark.svg create mode 100644 scripts/make_artwork.py rename src/{torchsim => blochsim}/__init__.py (96%) rename src/{torchsim => blochsim}/_bounds.py (100%) rename src/{torchsim => blochsim}/_epg_cpu.cpp (99%) rename src/{torchsim => blochsim}/_execution.py (100%) rename src/{torchsim => blochsim}/_functional/__init__.py (100%) rename src/{torchsim => blochsim}/_functional/_bssfp.py (100%) rename src/{torchsim => blochsim}/_functional/_fse.py (100%) rename src/{torchsim => blochsim}/_functional/_mp2rage.py (100%) rename src/{torchsim => blochsim}/_functional/_mpnrage.py (100%) rename src/{torchsim => blochsim}/_functional/_mprage.py (100%) rename src/{torchsim => blochsim}/_functional/_mrf.py (100%) rename src/{torchsim => blochsim}/_functional/_run.py (100%) rename src/{torchsim => blochsim}/_functional/_spgr.py (100%) rename src/{torchsim => blochsim}/_ifunc.hpp (83%) rename src/{torchsim => blochsim}/_perk_cpu.cpp (97%) rename src/{torchsim => blochsim}/_subspace.py (98%) rename src/{torchsim => blochsim}/_threads.hpp (96%) rename src/{torchsim => blochsim}/estimators/__init__.py (95%) rename src/{torchsim => blochsim}/estimators/_dictionary.py (98%) rename src/{torchsim => blochsim}/estimators/_grouped.py (100%) rename src/{torchsim => blochsim}/estimators/_lookup.py (97%) rename src/{torchsim => blochsim}/estimators/_mapping.py (98%) rename src/{torchsim => blochsim}/estimators/_nlls.py (94%) rename src/{torchsim => blochsim}/estimators/_perk.py (99%) rename src/{torchsim => blochsim}/estimators/_perk_native.py (100%) rename src/{torchsim => blochsim}/estimators/_perk_triton.py (100%) rename src/{torchsim => blochsim}/model/__init__.py (100%) rename src/{torchsim => blochsim}/model/_binding.py (99%) rename src/{torchsim => blochsim}/model/_signal.py (98%) rename src/{torchsim => blochsim}/model/_state_machine.py (98%) rename src/{torchsim => blochsim}/optim/__init__.py (100%) rename src/{torchsim => blochsim}/optim/_design.py (100%) rename src/{torchsim => blochsim}/recon/__init__.py (100%) rename src/{torchsim => blochsim}/recon/_gauss_newton.py (98%) rename src/{torchsim => blochsim}/recon/_operator.py (98%) rename src/{torchsim => blochsim}/sequence/__init__.py (100%) rename src/{torchsim => blochsim}/sequence/_accelerators.py (99%) rename src/{torchsim => blochsim}/sequence/_array.py (99%) rename src/{torchsim => blochsim}/sequence/_builders.py (100%) rename src/{torchsim => blochsim}/sequence/_calibration.py (100%) rename src/{torchsim => blochsim}/sequence/_description.py (99%) rename src/{torchsim => blochsim}/sequence/_epg_triton.py (99%) rename src/{torchsim => blochsim}/sequence/_fixed_point.py (100%) rename src/{torchsim => blochsim}/sequence/_lineshape.py (100%) rename src/{torchsim => blochsim}/sequence/_mrd.py (100%) rename src/{torchsim => blochsim}/sequence/_operators.py (100%) rename src/{torchsim => blochsim}/sequence/_parameters.py (99%) rename src/{torchsim => blochsim}/sequence/_pools.py (100%) rename src/{torchsim => blochsim}/sequence/_pools_triton.py (99%) rename src/{torchsim => blochsim}/sequence/_pulseq.py (98%) rename src/{torchsim => blochsim}/sequence/_settling.py (100%) rename src/{torchsim => blochsim}/sequence/_simulation.py (99%) rename src/{torchsim => blochsim}/sequence/_transition.py (100%) rename src/{torchsim => blochsim}/sequence/_transmit.py (100%) rename src/{torchsim => blochsim}/simulators/__init__.py (96%) rename src/{torchsim => blochsim}/simulators/_contrast.py (100%) rename src/{torchsim => blochsim}/simulators/_pulses.py (98%) rename src/{torchsim => blochsim}/simulators/asl.py (99%) rename src/{torchsim => blochsim}/simulators/bssfp.py (98%) rename src/{torchsim => blochsim}/simulators/cest.py (98%) rename src/{torchsim => blochsim}/simulators/diffusion.py (98%) rename src/{torchsim => blochsim}/simulators/flash.py (99%) rename src/{torchsim => blochsim}/simulators/fmssfp.py (98%) rename src/{torchsim => blochsim}/simulators/fse.py (98%) rename src/{torchsim => blochsim}/simulators/gradient_echo.py (97%) rename src/{torchsim => blochsim}/simulators/hsfp.py (98%) rename src/{torchsim => blochsim}/simulators/looklocker.py (98%) rename src/{torchsim => blochsim}/simulators/mp2rage.py (99%) rename src/{torchsim => blochsim}/simulators/mpnrage.py (98%) rename src/{torchsim => blochsim}/simulators/mprage.py (98%) rename src/{torchsim => blochsim}/simulators/mrf.py (97%) rename src/{torchsim => blochsim}/simulators/relaxometry.py (97%) rename src/{torchsim => blochsim}/simulators/spgr.py (98%) rename src/{torchsim => blochsim}/simulators/spin_echo.py (98%) rename src/{torchsim => blochsim}/simulators/stimulated_echo.py (98%) rename src/{torchsim => blochsim}/simulators/truefisp.py (99%) rename src/{torchsim => blochsim}/utils/__init__.py (100%) rename src/{torchsim => blochsim}/utils/_b1rms.py (100%) diff --git a/.github/DISCUSSION_TEMPLATE/ideas.yml b/.github/DISCUSSION_TEMPLATE/ideas.yml index 9e32dd22..a77b37c6 100644 --- a/.github/DISCUSSION_TEMPLATE/ideas.yml +++ b/.github/DISCUSSION_TEMPLATE/ideas.yml @@ -5,7 +5,7 @@ body: attributes: label: The idea description: | - A model, an interface or a direction you think TorchSim should take, + A model, an interface or a direction you think BlochSim should take, before it is concrete enough to be a feature request. validations: required: true diff --git a/.github/DISCUSSION_TEMPLATE/q-a.yml b/.github/DISCUSSION_TEMPLATE/q-a.yml index 931eee4c..77687eeb 100644 --- a/.github/DISCUSSION_TEMPLATE/q-a.yml +++ b/.github/DISCUSSION_TEMPLATE/q-a.yml @@ -20,5 +20,5 @@ body: - type: input id: version attributes: - label: TorchSim version + label: BlochSim version placeholder: "0.4.1, or the commit you are on" diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a9c81265..8807346b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -5,8 +5,8 @@ body: - type: markdown attributes: value: | - Questions about how to use TorchSim, and results you are unsure about, - belong in [Discussions](https://github.com/pulserver/torchsim/discussions). + Questions about how to use BlochSim, and results you are unsure about, + belong in [Discussions](https://github.com/pulserver/blochsim/discussions). This form is for behaviour you can pin down. - type: textarea @@ -47,7 +47,7 @@ body: Paste the output of ```sh - python -c "from importlib.metadata import version; print('torchsim', version('torchsim'))" + python -c "from importlib.metadata import version; print('blochsim', version('blochsim'))" python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.get_device_name() if torch.cuda.is_available() else 'cpu')" python -VV ``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2518bebe..f39206b5 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: Question, or a result you are unsure about - url: https://github.com/pulserver/torchsim/discussions + url: https://github.com/pulserver/blochsim/discussions about: Ask how to model a sequence, or whether a signal you got is expected. - name: Security report - url: https://github.com/pulserver/torchsim/security/policy + url: https://github.com/pulserver/blochsim/security/policy about: Report a vulnerability privately rather than in a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index ce057f39..ce12d8f0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: Feature request -description: A sequence, a physical effect, an estimator or an interface TorchSim should carry. +description: A sequence, a physical effect, an estimator or an interface BlochSim should carry. labels: ["enhancement"] body: - type: textarea @@ -16,7 +16,7 @@ body: - type: textarea id: proposal attributes: - label: What you would like TorchSim to do + label: What you would like BlochSim to do description: | If you already know the shape of it -- an operator, a tissue property, a keyword on an existing simulator -- sketch the call you would like diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8c97eebb..62ded5fa 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,6 +23,6 @@ pytest tests/ - [ ] Comments and docstrings describe the code as it is now -- no "previously", no "this replaces", no naming of a bug that is fixed. - [ ] A change to what the kernels compute is in both of them, and arrives - with a test that pins it against something outside TorchSim. + with a test that pins it against something outside BlochSim. - [ ] The documentation is updated where the change is visible to a caller, and `bash scripts/build_docs.sh` still builds. diff --git a/.github/SECURITY.md b/.github/SECURITY.md index f28c3e4b..a3483bc1 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -10,7 +10,7 @@ Older releases are not patched. Report privately, not as a public issue: - open a draft advisory through - [Security -> Report a vulnerability](https://github.com/pulserver/torchsim/security/advisories/new), + [Security -> Report a vulnerability](https://github.com/pulserver/blochsim/security/advisories/new), which is the preferred route; or - email **matteo.cencini@gmail.com** if you cannot use GitHub. @@ -22,7 +22,7 @@ publicly. ## Scope -TorchSim executes the code you give it: a sequence layout, a cost function and +BlochSim executes the code you give it: a sequence layout, a cost function and a signal model are all Python that runs in your process, so a malicious *script* is out of scope in the same way it is for NumPy or PyTorch. @@ -36,4 +36,4 @@ merely a bug. ## Not a vulnerability A simulation that returns wrong physics, diverges, or raises is a -[bug report](https://github.com/pulserver/torchsim/issues/new/choose). +[bug report](https://github.com/pulserver/blochsim/issues/new/choose). diff --git a/.github/skills/add-a-sequence/SKILL.md b/.github/skills/add-a-sequence/SKILL.md index f0096a5a..db4c4bca 100644 --- a/.github/skills/add-a-sequence/SKILL.md +++ b/.github/skills/add-a-sequence/SKILL.md @@ -1,9 +1,9 @@ --- name: add-a-sequence -description: Write a new simulator, signal model, or sequence operator in TorchSim. Use when asked to add a sequence, a readout module, a physical effect, or a tissue property. +description: Write a new simulator, signal model, or sequence operator in BlochSim. Use when asked to add a sequence, a readout module, a physical effect, or a tissue property. --- -# Add a sequence to TorchSim +# Add a sequence to BlochSim **There is one base class, `Simulator`, written in one of two ways.** Nothing downstream ever asks which way. @@ -62,15 +62,15 @@ and a run reads which properties were passed to decide which terms the kernel evaluates. Adding an effect is one line in a model's property declaration, plus the term itself in **both** kernels. -The shared parameter ABI is `src/torchsim/sequence/_parameters.py`, read by the +The shared parameter ABI is `src/blochsim/sequence/_parameters.py`, read by the Python dispatch, the C++ extension and the Triton kernels. A parameter added there is added in all three or in none. ## What the change has to arrive with -A test that pins the new physics against something **outside** TorchSim: a +A test that pins the new physics against something **outside** BlochSim: a closed form, a published figure, or an isochromat summation written out in the -test itself. Comparing TorchSim to TorchSim proves the two agree, which was +test itself. Comparing BlochSim to BlochSim proves the two agree, which was never in doubt. Put it in `tests/epg/` and state the invariant in the module docstring. diff --git a/.github/skills/build-and-test/SKILL.md b/.github/skills/build-and-test/SKILL.md index cbda28f9..3f851c91 100644 --- a/.github/skills/build-and-test/SKILL.md +++ b/.github/skills/build-and-test/SKILL.md @@ -1,9 +1,9 @@ --- name: build-and-test -description: Compile the C++ kernels and run the TorchSim suite, including the Triton paths on a machine with no GPU. Use when asked to build, install, test, or reproduce a failure in torchsim. +description: Compile the C++ kernels and run the BlochSim suite, including the Triton paths on a machine with no GPU. Use when asked to build, install, test, or reproduce a failure in blochsim. --- -# Build and test TorchSim +# Build and test BlochSim ## Install @@ -15,7 +15,7 @@ pip install -e ".[dev]" ; echo "exit: $?" run alone, `pip install -e ".[test]"` is enough and much smaller. An editable install puts the Python sources on the path, so an edit under -`src/torchsim` takes effect on the next import. The two C++ extensions are +`src/blochsim` takes effect on the next import. The two C++ extensions are compiled artifacts and do not: re-run the install after editing any `.cpp`. **Read the exit status, not the output.** A failed compile leaves the @@ -24,7 +24,7 @@ that no longer corresponds to the source. Confirm the extension is the one you just built: ```sh -python -c "import torchsim._epg_cpu as k; print(k.__file__)" +python -c "import blochsim._epg_cpu as k; print(k.__file__)" ``` ## Test diff --git a/.github/skills/build-the-docs/SKILL.md b/.github/skills/build-the-docs/SKILL.md index 3c121f7b..3cbd9589 100644 --- a/.github/skills/build-the-docs/SKILL.md +++ b/.github/skills/build-the-docs/SKILL.md @@ -1,26 +1,26 @@ --- name: build-the-docs -description: Build the TorchSim documentation, add a figure to an explanation page, or add a gallery example. Use when asked about docs, Sphinx, the gallery, MyST, or Read the Docs for torchsim. +description: Build the BlochSim documentation, add a figure to an explanation page, or add a gallery example. Use when asked about docs, Sphinx, the gallery, MyST, or Read the Docs for blochsim. --- -# Build the TorchSim documentation +# Build the BlochSim documentation ```sh bash scripts/build_docs.sh # incremental bash scripts/build_docs.sh --clean # re-execute every example -PYTHON_BIN=~/envs/torchsim/bin/python bash scripts/build_docs.sh +PYTHON_BIN=~/envs/blochsim/bin/python bash scripts/build_docs.sh python -m http.server --directory docs/build/html 8000 ``` -The script checks that the interpreter it is given can import TorchSim and the +The script checks that the interpreter it is given can import BlochSim and the documentation extensions, then builds into `docs/build/html`. Two things make it slower than a plain Sphinx run and are the point of it: **sphinx-gallery executes the examples**, and **the explanation pages' figures are re-rendered** -by `docs/explanation_figures.py` with the TorchSim in your working tree. +by `docs/explanation_figures.py` with the BlochSim in your working tree. An example is executed when the interpreter can import everything it imports: an environment with the `dev` or `examples` extra runs the whole gallery, one -with `doc` alone runs what needs nothing but TorchSim. A page already carrying +with `doc` alone runs what needs nothing but BlochSim. A page already carrying output executed from the source as it stands is reused rather than re-run, and the build names each example it did not run and where its output came from. To build the pages without running any of them, pass `-D plot_gallery=0`. @@ -28,10 +28,10 @@ build the pages without running any of them, pass `-D plot_gallery=0`. ## Where the published pages are built `.github/workflows/docs.yml` builds them, and GitHub Pages serves them from -the `gh-pages` branch at . +the `gh-pages` branch at . Its **HTML** job runs on every branch and pull request with the `doc` extra: -every page is written and the examples needing nothing but TorchSim are +every page is written and the examples needing nothing but BlochSim are executed, in a few minutes, and the tree is uploaded as an artifact to read. Its **Site** job runs on `main`, on a `v*.*.*` tag and on demand. It installs @@ -43,7 +43,7 @@ Sphinx wrote to `scripts/publish_docs.py`, which places it in the site: site/index.html a page sending a reader to the newest release site/versions.json the list the switcher in the sidebar reads -`TORCHSIM_DOCS_VERSION` is what a build calls itself; it names the directory, +`BLOCHSIM_DOCS_VERSION` is what a build calls itself; it names the directory, the switcher's mark and the Binder path prefix, and defaults to `latest`. ## Markdown, and the two places it is not @@ -90,7 +90,7 @@ executed and where it is published as source. ## Writing The audience is MR scientists: pulse sequences and physics, not software -architecture. Describe what TorchSim does and why that is right on its own +architecture. Describe what BlochSim does and why that is right on its own terms. These pages are not a changelog — never justify a design by describing the design it replaced. @@ -109,7 +109,7 @@ Keep the public sidebar shallow and in this order: The first four Framework examples are the Course. They teach the public abstraction in order; the remaining example sections are standalone Tours. -For a new sequence, teach {class}`torchsim.model.Simulator` as the extension +For a new sequence, teach {class}`blochsim.model.Simulator` as the extension point. A state-machine simulator has two complementary inputs: its class-level handlers interpret commands from an incoming Pulseq/MRD description, and `layout()` constructs the same sequence offline. Lower-level diff --git a/.github/skills/cut-a-release/SKILL.md b/.github/skills/cut-a-release/SKILL.md index 9fd7989c..7b80ec58 100644 --- a/.github/skills/cut-a-release/SKILL.md +++ b/.github/skills/cut-a-release/SKILL.md @@ -1,9 +1,9 @@ --- name: cut-a-release -description: Publish a TorchSim release to PyPI, and diagnose a wheel build. Use when asked about releasing, versioning, cibuildwheel, abi3 wheels, or the packaging setup. +description: Publish a BlochSim release to PyPI, and diagnose a wheel build. Use when asked about releasing, versioning, cibuildwheel, abi3 wheels, or the packaging setup. --- -# Cut a TorchSim release +# Cut a BlochSim release The version comes from the git tag through setuptools-scm; nothing in the tree states it. To release: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index da5c9a8e..ebf07fbd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -46,7 +46,7 @@ jobs: --extra-index-url https://pypi.org/simple # The ``doc`` extra alone: every page is written and the examples - # needing nothing but TorchSim are executed, which is what a branch + # needing nothing but BlochSim are executed, which is what a branch # wants to know within a few minutes. The Site job below runs the rest. - name: Build the extensions and install run: python -m pip install ".[doc]" @@ -86,7 +86,7 @@ jobs: # A tag is published under its own name; anything else is the # development version. It names the directory of the site, the entry # the switcher marks, and the prefix the notebook links reach through. - TORCHSIM_DOCS_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || 'latest' }} + BLOCHSIM_DOCS_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || 'latest' }} steps: - uses: actions/checkout@v7 @@ -125,7 +125,7 @@ jobs: - uses: actions/upload-artifact@v7 with: - name: html-${{ env.TORCHSIM_DOCS_VERSION }} + name: html-${{ env.BLOCHSIM_DOCS_VERSION }} path: docs/build/html retention-days: 7 @@ -141,8 +141,8 @@ jobs: else git worktree add --orphan ../pages fi - python scripts/publish_docs.py docs/build/html "$TORCHSIM_DOCS_VERSION" ../pages + python scripts/publish_docs.py docs/build/html "$BLOCHSIM_DOCS_VERSION" ../pages git -C ../pages add --all git -C ../pages commit --quiet \ - -m "Publish $TORCHSIM_DOCS_VERSION from ${{ github.sha }}" || exit 0 + -m "Publish $BLOCHSIM_DOCS_VERSION from ${{ github.sha }}" || exit 0 git -C ../pages push origin HEAD:gh-pages diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7fd8bf55..aaec7d27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,7 +67,7 @@ jobs: shell: python run: | import importlib.util, pathlib - spec = importlib.util.find_spec("torchsim") + spec = importlib.util.find_spec("blochsim") root = pathlib.Path(next(iter(spec.submodule_search_locations))) for kernel in sorted(root.glob("_*_cpu*")): print(kernel.name, kernel.stat().st_size, "bytes") @@ -84,6 +84,6 @@ jobs: uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} - slug: pulserver/torchsim + slug: pulserver/blochsim files: coverage.xml fail_ci_if_error: false diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 61a3e254..6ca1ae4f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -10,8 +10,8 @@ on: - pyproject.toml - CMakeLists.txt - setup.py - - "src/torchsim/*.cpp" - - "src/torchsim/*.hpp" + - "src/blochsim/*.cpp" + - "src/blochsim/*.hpp" - scripts/check_wheel.py - .github/workflows/wheels.yml workflow_dispatch: @@ -95,7 +95,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/torchsim + url: https://pypi.org/p/blochsim permissions: # Trusted publishing: no API token is stored anywhere. id-token: write diff --git a/.gitignore b/.gitignore index ba7cdf01..dad77b30 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ CMakeFiles/ cmake_install.cmake # Generated at build time -src/torchsim/_version.py +src/blochsim/_version.py # Test and coverage output .coverage* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33dd3476..2df1e55e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: autoupdate_schedule: monthly -exclude: ^src/torchsim/_version\.py$ +exclude: ^src/blochsim/_version\.py$ repos: - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/CHANGELOG.md b/CHANGELOG.md index 026ea234..5f2a5c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +### Changed + +- **The package is `blochsim`.** The distribution, the import name and the + repository are `blochsim`; `import torchsim` becomes `import blochsim`, with + the same modules and names beneath it. + ### Added - **Up to four chemically exchanging pools beside the free water.** @@ -10,7 +16,7 @@ five pools of BART's Bloch-McConnell simulation, each exchanging with the free water alone. From two exchanging pools on, a tissue reaches the kernels as the relaxation-exchange operators of each distinct interval length, formed per - voxel in double precision (`torchsim.sequence._pools`). The C++ and Triton + voxel in double precision (`blochsim.sequence._pools`). The C++ and Triton kernels apply them in the forward, forward-mode, adjoint and forward-over-reverse passes, under hard, tabulated and per-voxel pulses and a transmit array, and the pool properties take their derivatives back through @@ -88,7 +94,7 @@ Both are table lookups now, `sequence/_calibration.py` is a table, and `_calibrate.py` -- the same idea for the estimators and the model-based operator -- is gone, along with the four closures those built for it to time. - `torchsim._execution.choose` and `per_voxel` take the crossover as a number + `blochsim._execution.choose` and `per_voxel` take the crossover as a number rather than a callable to answer later. Placement is unchanged and stays the caller's: with no `execution()` block a call runs wherever its tensors are, a named device is an instruction, and whether a volume is resident or streamed @@ -104,7 +110,7 @@ of twenty-seven: 7.75 ms against 8.02 ms forward, 41.7 ms against 41.3 ms for the Jacobian. - `calibrate` and `TORCHSIM_CALIBRATION` are gone with the probes. + `calibrate` and `BLOCHSIM_CALIBRATION` are gone with the probes. - **How many orders a run keeps is a number, not a compilation.** The Triton kernels read `state_count` as a mask bound and as the stride between the @@ -691,10 +697,10 @@ documentation build. *Extended phase graphs* is the physics: dephasing as a helix, configuration states as its Fourier coefficients, the RF and shift operators, relaxation reaching only order zero, the phase graph read against - the echo amplitudes TorchSim computes for the same train, why a low-flip + the echo amplitudes BlochSim computes for the same train, why a low-flip train is not a mono-exponential, how many orders a sequence has to carry, the order-weighted diffusion b-factors, and the two-pool extensions. *How - TorchSim runs it* is the realization: events carrying their own action word, + BlochSim runs it* is the realization: events carrying their own action word, one fused kernel per voxel, the terms a declaration switches on, the real subspace, forward against reverse derivatives, the affine rebinding, the execution policy, and the closed forms the state machine is held to. @@ -729,7 +735,7 @@ Nothing was added to the library for it. -- **`torchsim.recon`: the signal model as an operator.** A physics-based +- **`blochsim.recon`: the signal model as an operator.** A physics-based reconstruction writes its forward operator as `P F C M` -- sampling, Fourier encoding, coil sensitivities, signal model -- and solves for the parameter maps against k-space directly. Only `M` changes with the sequence, and only @@ -796,7 +802,7 @@ echo times still on the host. `bound()` adds or replaces tissue, which is what a fit does with a property measured separately. -- **`torchsim.ParameterMapping`**, with `Estimator` and `Subspace`. A mapping +- **`blochsim.ParameterMapping`**, with `Estimator` and `Subspace`. A mapping problem is stated the way a design problem is: an `Acquisition`, the properties that are unknown and the range to train each over, the ones measured separately, and the noise. `train(method)` simulates the training @@ -859,7 +865,7 @@ grid it can rule out the group holding the match; widening it recovers the voxel, and a test pins both halves of that. -- **`torchsim.NonlinearLeastSquares`**, Levenberg-Marquardt stepping every +- **`blochsim.NonlinearLeastSquares`**, Levenberg-Marquardt stepping every voxel at once. Where a dictionary spans a grid whose size is the product of the parameter ranges, a nonlinear fit pays for a third parameter with a third column of the Jacobian. Voxels do not take turns: each carries its own @@ -892,7 +898,7 @@ expression. The inversion recovery carries the repetition time as well, which the usual fully-relaxed expression drops. -- **`torchsim.LookupTable`**, for a model with a single unknown. Its atoms lie +- **`blochsim.LookupTable`**, for a model with a single unknown. Its atoms lie on a curve rather than filling a space, so the nearest one is found by looking along it, and interpolating between the two nearest removes the grid spacing from the answer -- which is otherwise what a matched estimate is @@ -923,7 +929,7 @@ individual readouts agree. - **Fused kernels for PERK**, forward and adjoint, in Triton on CUDA and in a - new `torchsim._perk_cpu` extension on the host. The feature matrix is never + new `blochsim._perk_cpu` extension on the host. The feature matrix is never written: a tile of features is formed and consumed into the output accumulator in registers. On a million voxels of 64 contrasts at a thousand features that is 3.3x on this card and 2.4-2.9x on this host, agreeing with @@ -962,7 +968,7 @@ `docs/conf.py` drops sphinx-gallery's code-link pass on interpreters whose standard library has no `dbm`. -- **`torchsim.model`**, with `SignalModel`, `StateMachineModel`, `Triggers` +- **`blochsim.model`**, with `SignalModel`, `StateMachineModel`, `Triggers` and `AbstractSimulator`. A signal model is written in two pieces: a state-machine model saying what a voxel holds -- `properties` maps the name a caller uses to the tissue field it fills, so declaring `b0_hz` or a second @@ -980,20 +986,20 @@ `AbstractSimulator.from_description` takes a stream someone else assembled, which is the path a description arriving from a scanner takes. -- **`torchsim.simulators`**, replacing `torchsim.models`. Each shipped +- **`blochsim.simulators`**, replacing `blochsim.models`. Each shipped sequence names its protocol at construction (`MRFSimulator(flip=..., TR=...)`) and its tissue at the call (`.simulate(T1=..., T2=...)`), so parameter inference, sequence optimization and a reconstruction pipeline take all of them the same way. A protocol argument may still be overridden per call. -- **`torchsim.sequence` operators.** `Operator`, `compose`, `module` and the +- **`blochsim.sequence` operators.** `Operator`, `compose`, `module` and the factories `excitation`, `refocusing`, `inversion`, `saturation`, `readout` and `delay`, with a name registry (`register_operator`, `operator`, `operator_names`). A preparation, a readout or a shaped pulse is written by composing these and reaches the fused kernels with no change to them. The five shipped builders are written over them. -- **`torchsim.sequence.ideal_rf_definition`**, the hard-pulse RF definition a +- **`blochsim.sequence.ideal_rf_definition`**, the hard-pulse RF definition a description built by hand needs. - **Sequence design: `Acquisition`, `Bounded`, `SequenceDesign` and `crlb`.** @@ -1153,11 +1159,11 @@ Anything matching the `LeastSquares` protocol -- `A`, `AT`, `y`, `z`, `gamma`, `max_iter`, `tol` in, the damped step out -- is called directly. A reconstruction that brings its own conjugate gradients, or wraps a proximal - solver to carry a regularizer, needs nothing from a dependency TorchSim does + solver to carry a regularizer, needs nothing from a dependency BlochSim does not have. Given nothing, it falls back to deepinv's `least_squares`, which satisfies the protocol unchanged; one of deepinv's others is that same function with its argument bound, which is the caller's own composition - rather than a name TorchSim interprets. + rather than a name BlochSim interprets. - **The API pages follow one subject each.** *Sequences* is the description and what assembles it -- events, operators, builders. The engine and the @@ -1178,9 +1184,9 @@ Under a streaming fit that is the difference between simulating the training set twice and simulating it three times. -- **`Acquisition` moved to `torchsim.model`**, where both `torchsim.optim` and - `torchsim.estimators` can reach it without either depending on the other. It - is still exported from `torchsim` and `torchsim.optim`. +- **`Acquisition` moved to `blochsim.model`**, where both `blochsim.optim` and + `blochsim.estimators` can reach it without either depending on the other. It + is still exported from `blochsim` and `blochsim.optim`. - **`SubspaceBasis` carries a `Subspace`** rather than its own copy of the basis and singular values, which it still exposes under the same names. @@ -1245,11 +1251,11 @@ reaching for is `Triggers`, where it decides something. `simulate_subspace` loses its simulator argument for the same reason. -- **`torchsim.models` and the seven `*Model` classes.** Use - `torchsim.simulators` and the `*Simulator` classes, which take the protocol +- **`blochsim.models` and the seven `*Model` classes.** Use + `blochsim.simulators` and the `*Simulator` classes, which take the protocol at construction. The seven `*_sim` functional wrappers are unchanged. -- **`torchsim.epg`.** The package held the state-machine operators the +- **`blochsim.epg`.** The package held the state-machine operators the simulator's torch loop was written from. That loop is gone: every sequence now runs on the fused CPU and CUDA kernels, which reach the same operators through their own code. The operators survive as the tests' parity oracle @@ -1269,7 +1275,7 @@ there is nothing to select between. A tissue no kernel can take now raises. - **`slice_profile=` as a tensor of flip-angle scalings**, and - `torchsim.utils.slice_prof` with it. A slice profile is a Bloch response and + `blochsim.utils.slice_prof` with it. A slice profile is a Bloch response and is worked out from the RF definition; `slice_profile=` now says only where across the slice to sample it, through `exact_slice_profile(...)`. @@ -1277,10 +1283,10 @@ functional wrappers, for the same reason. Give the RF definition its waveform instead. -- **`torchsim.base`**, with `AbstractModel`, `autocast`, and the +- **`blochsim.base`**, with `AbstractModel`, `autocast`, and the `prepare_single_pool` / `prepare_two_pool_bm` / `prepare_two_pool_mt` / `prepare_three_pool` / `prepare_environmental_parameters` helpers. Write a - model over `torchsim.model.SignalModel` or `EpgModel` instead; a tissue is + model over `blochsim.model.SignalModel` or `EpgModel` instead; a tissue is built by naming the fields a model exposes. - **`chunk_size` on the seven `*_sim` wrappers.** It selected a `torch.vmap` diff --git a/CLAUDE.md b/CLAUDE.md index b18a83cc..6b6972f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ -# TorchSim, for an agent working on it +# BlochSim, for an agent working on it -TorchSim simulates MR signals in PyTorch, differentiably. A **signal model** is +BlochSim simulates MR signals in PyTorch, differentiably. A **signal model** is the only thing a sequence has to supply; differentiation, device placement, parameter estimation, model-based reconstruction and sequence design are all written once against that interface. @@ -13,15 +13,15 @@ broken checkout (Windows without `core.symlinks`), not two documents. | Path | What is in it | | --- | --- | -| `src/torchsim/sequence/` | The description an acquisition is assembled from — events, operators, builders — and the dispatch that turns one into a kernel launch. | -| `src/torchsim/model/` | What a signal model *is*: the physics, the simulator that orders its events, and the binding that resolves a protocol's structure once and rebinds its values per call. | -| `src/torchsim/_epg_cpu.cpp`, `_perk_cpu.cpp` | The CPU kernels. Every path the GPU has exists here too, and the two agree to float32 round-off. | -| `src/torchsim/sequence/_epg_triton.py`, `estimators/_perk_triton.py` | The GPU kernels. | -| `src/torchsim/simulators/`, `estimators/`, `recon/`, `optim/` | The sequences that ship, and what is built on top of them. | +| `src/blochsim/sequence/` | The description an acquisition is assembled from — events, operators, builders — and the dispatch that turns one into a kernel launch. | +| `src/blochsim/model/` | What a signal model *is*: the physics, the simulator that orders its events, and the binding that resolves a protocol's structure once and rebinds its values per call. | +| `src/blochsim/_epg_cpu.cpp`, `_perk_cpu.cpp` | The CPU kernels. Every path the GPU has exists here too, and the two agree to float32 round-off. | +| `src/blochsim/sequence/_epg_triton.py`, `estimators/_perk_triton.py` | The GPU kernels. | +| `src/blochsim/simulators/`, `estimators/`, `recon/`, `optim/` | The sequences that ship, and what is built on top of them. | | `tests/`, `examples/`, `docs/` | Mirrored by subpackage, executed by the gallery, built by Sphinx. | The shared parameter ABI — read by the Python dispatch, the C++ extension and -the Triton kernels alike — is `src/torchsim/sequence/_parameters.py`. A +the Triton kernels alike — is `src/blochsim/sequence/_parameters.py`. A parameter added there is added in all three places or in none. ## Commands @@ -54,7 +54,7 @@ reading. Read the exit status of the install, not the last lines of its output: ```sh pip install -e ".[dev]" ; echo "exit: $?" -python -c "import torchsim._epg_cpu as k; print(k.__file__)" +python -c "import blochsim._epg_cpu as k; print(k.__file__)" ``` **Kernel compiles dominate a cold GPU run**, not the arithmetic. A suite that @@ -123,10 +123,10 @@ what a human decides: ## Changing the physics A change to what the kernels compute arrives with a test that pins it against -something **outside** TorchSim: a closed form, a published figure, or an +something **outside** BlochSim: a closed form, a published figure, or an isochromat summation written out in the test itself. The `tests/epg` files are written that way and each states its invariant in its module docstring. A test -that only compares TorchSim to TorchSim proves the two agree, which was never +that only compares BlochSim to BlochSim proves the two agree, which was never in doubt. Whatever you change in one kernel, change in the other. The C++ and Triton diff --git a/CMakeLists.txt b/CMakeLists.txt index c791d3ef..5d80ec2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,12 @@ cmake_minimum_required(VERSION 3.26) -project(torchsim LANGUAGES CXX) +project(blochsim LANGUAGES CXX) # Both kernels are plain CPython extensions against the stable ABI: they call # no PyTorch API and link no PyTorch library, which is why one wheel per # platform serves every supported interpreter and why that wheel is a couple of # megabytes rather than the size of libtorch. -set(TORCHSIM_ABI3_VERSION 3.10) +set(BLOCHSIM_ABI3_VERSION 3.10) find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module Development.SABIModule @@ -19,14 +19,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -function(torchsim_add_kernel name source) +function(blochsim_add_kernel name source) python_add_library(${name} MODULE - USE_SABI ${TORCHSIM_ABI3_VERSION} + USE_SABI ${BLOCHSIM_ABI3_VERSION} WITH_SOABI "${source}" ) # The shared headers sit beside the sources they are shared by. - target_include_directories(${name} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/torchsim") + target_include_directories(${name} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/blochsim") target_link_libraries(${name} PRIVATE Threads::Threads) # Nothing the C++ runtime owns crosses the module boundary: the kernel @@ -51,8 +51,8 @@ function(torchsim_add_kernel name source) target_compile_options(${name} PRIVATE -O3 -ffp-contract=off) endif() - install(TARGETS ${name} DESTINATION torchsim) + install(TARGETS ${name} DESTINATION blochsim) endfunction() -torchsim_add_kernel(_epg_cpu src/torchsim/_epg_cpu.cpp) -torchsim_add_kernel(_perk_cpu src/torchsim/_perk_cpu.cpp) +blochsim_add_kernel(_epg_cpu src/blochsim/_epg_cpu.cpp) +blochsim_add_kernel(_perk_cpu src/blochsim/_perk_cpu.cpp) diff --git a/README.md b/README.md index b53dfd93..f2e33ca2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,20 @@ -# TorchSim + + + blochsim + -TorchSim is a differentiable MR signal simulator built on PyTorch, with +# BlochSim + +BlochSim is a differentiable MR signal simulator built on PyTorch, with closed-form signal models and a fused extended-phase-graph (EPG) state machine for pulse trains. -[![codecov](https://codecov.io/gh/pulserver/torchsim/graph/badge.svg?token=l8xhIVORYm)](https://codecov.io/gh/pulserver/torchsim) -[![Tests](https://github.com/pulserver/torchsim/actions/workflows/test.yml/badge.svg)](https://github.com/pulserver/torchsim/actions/workflows/test.yml) -[![Lint](https://github.com/pulserver/torchsim/actions/workflows/lint.yml/badge.svg)](https://github.com/pulserver/torchsim/actions/workflows/lint.yml) -[![License](https://img.shields.io/github/license/pulserver/torchsim)](https://github.com/pulserver/torchsim/blob/main/LICENSE.txt) -[![Documentation](https://github.com/pulserver/torchsim/actions/workflows/docs.yml/badge.svg)](https://pulserver.github.io/torchsim/) -[![PyPi](https://img.shields.io/pypi/v/torchsim)](https://pypi.org/project/torchsim) +[![codecov](https://codecov.io/gh/pulserver/blochsim/graph/badge.svg?token=l8xhIVORYm)](https://codecov.io/gh/pulserver/blochsim) +[![Tests](https://github.com/pulserver/blochsim/actions/workflows/test.yml/badge.svg)](https://github.com/pulserver/blochsim/actions/workflows/test.yml) +[![Lint](https://github.com/pulserver/blochsim/actions/workflows/lint.yml/badge.svg)](https://github.com/pulserver/blochsim/actions/workflows/lint.yml) +[![License](https://img.shields.io/github/license/pulserver/blochsim)](https://github.com/pulserver/blochsim/blob/main/LICENSE.txt) +[![Documentation](https://github.com/pulserver/blochsim/actions/workflows/docs.yml/badge.svg)](https://pulserver.github.io/blochsim/) +[![PyPi](https://img.shields.io/pypi/v/blochsim)](https://pypi.org/project/blochsim) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PythonVersion](https://img.shields.io/badge/Python-%3E=3.10-blue?logo=python&logoColor=white)](https://python.org) @@ -28,13 +33,13 @@ for pulse trains. ## Installation -Install the PyTorch build appropriate for your machine first, then TorchSim: +Install the PyTorch build appropriate for your machine first, then BlochSim: ```bash -pip install torchsim +pip install blochsim ``` -See the [User Guide](https://pulserver.github.io/torchsim/latest/user_guide.html) +See the [User Guide](https://pulserver.github.io/blochsim/latest/user_guide.html) for CPU, CUDA, macOS and source-build details. ## Basic usage @@ -44,7 +49,7 @@ sequence; `simulate` and `jacobian` evaluate it over the tissue you pass: ```python import numpy as np -from torchsim.simulators import MRFSimulator +from blochsim.simulators import MRFSimulator flip = np.concatenate( (np.linspace(5.0, 60.0, 300), np.linspace(60.0, 2.0, 300), np.full(280, 2.0)) @@ -58,14 +63,14 @@ signal, jacobian = sequence.jacobian( ) ``` -Functional helpers such as `torchsim.mrf_sim(...)` remain convenient for +Functional helpers such as `blochsim.mrf_sim(...)` remain convenient for one-off calls. The class interface is the canonical one for reusable models, parameter estimation, reconstruction, optimization, Pulseq input and scanner descriptions. ## Implementing a sequence -Subclass `torchsim.model.Simulator`. For a state-machine sequence you define: +Subclass `blochsim.model.Simulator`. For a state-machine sequence you define: 1. the event handlers that say how excitation, refocusing, inversion, saturation, readout and delay commands are interpreted; and @@ -77,14 +82,14 @@ That gives offline design and scanner-driven simulation one public sequence abstraction. The executable -[Framework course](https://pulserver.github.io/torchsim/latest/generated/autoexamples/01-framework/index.html) +[Framework course](https://pulserver.github.io/blochsim/latest/generated/autoexamples/01-framework/index.html) walks through the complete pattern. ## Development ```bash -git clone git@github.com:pulserver/torchsim -cd torchsim +git clone git@github.com:pulserver/blochsim +cd blochsim pip install -e ".[dev]" pre-commit install ``` @@ -96,6 +101,6 @@ format/lint checks as CI. ## Related projects The documentation's -[Related projects](https://pulserver.github.io/torchsim/latest/misc/related.html) -page places TorchSim among other MR simulators and links to the relevant +[Related projects](https://pulserver.github.io/blochsim/latest/misc/related.html) +page places BlochSim among other MR simulators and links to the relevant packages and literature. diff --git a/benchmarks/README.md b/benchmarks/README.md index 985e9765..3b2972fa 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -18,7 +18,7 @@ bash benchmarks/setup.sh # or: setup.sh python / setup.sh julia source benchmarks/.env/activate ``` -That builds a virtual environment with PyTorch, TorchSim, epgpy and sycomore +That builds a virtual environment with PyTorch, BlochSim, epgpy and sycomore in it, and a Julia with BlochSimulators.jl, KomaMRI.jl and CUDA.jl, all under `benchmarks/.env`, which is the only thing to delete afterwards. Every piece is optional: `run_all.py` skips a backend that is not installed and says so. @@ -28,7 +28,7 @@ Three notes on the awkward ones. **Which interpreter the environment is built on matters.** `PYTHON` says which, and the default is whatever `python3` finds first. An interpreter that ships a C++ runtime of its own -- a conda one does -- loads that runtime ahead of the -system's, and TorchSim's kernels are compiled against the system's. The +system's, and BlochSim's kernels are compiled against the system's. The extension then fails to load with a missing `GLIBCXX` version, every fused kernel is reported absent, and nothing here can run: @@ -52,11 +52,11 @@ benchmarks alone. python benchmarks/run_all.py # the CPU sweep, into results/ python benchmarks/run_all.py --device cuda # the same sweep, on a card python benchmarks/run_all.py --quick # stop at 1000 tissues -python benchmarks/run_all.py --backends torchsim,blochsimulators +python benchmarks/run_all.py --backends blochsim,blochsimulators python benchmarks/validate.py # do they agree? python benchmarks/summarize.py # the table, from results/ python benchmarks/make_figures.py # the figures, into figures/ -python benchmarks/anatomy.py [--device cuda] # what TorchSim's runtime is made of +python benchmarks/anatomy.py [--device cuda] # what BlochSim's runtime is made of ``` `--device` reaches every backend that has a card to be placed on. Sycomore and @@ -105,7 +105,7 @@ measures what it costs rather than pretending it is the same computation. **Wall time**, the fastest of the timed runs. What comes before them is a warm-up given a budget of seconds rather than a count, because what has to be -warm differs by orders of magnitude: TorchSim resolves the *structure* of a +warm differs by orders of magnitude: BlochSim resolves the *structure* of a sequence once and rebinds values on every call afterwards, Julia compiles on first call, and a card idles at a low clock and takes the better part of a second of continuous work to reach its boost one. That last is the one that @@ -140,7 +140,7 @@ placed on a card computed what the same run computes on a CPU. *every* order in double precision as the reference. Where 32 orders are enough for the tissue -- everything at brain T2 -- the implementations agree to **float32 round-off**, and what is left at long T2 is the truncation and not a -disagreement: it halves as the orders double. TorchSim's two kernels are held +disagreement: it halves as the orders double. BlochSim's two kernels are held against each other as well, since they are separate implementations of one recursion: a dictionary run on the card matches the same dictionary on the CPU to **6e-06 relative**. `results/validation.json` and `results/validation.txt` @@ -173,16 +173,16 @@ The measurements committed here were taken on an Intel Core i7-13700H -- six performance cores, eight efficiency cores, 23 GB -- with an NVIDIA GeForce RTX 4060 Laptop GPU, 8 GB; Python 3.11.5, PyTorch 2.13 with CUDA 13.0, Triton 3.7.1, NumPy 2.4.6, sycomore 2.0.0, epgpy at 82eebbf, Julia 1.12.7, -BlochSimulators 0.9.0, KomaMRICore 0.13.0, CUDA.jl 5. TorchSim and +BlochSimulators 0.9.0, KomaMRICore 0.13.0, CUDA.jl 5. BlochSim and BlochSimulators compute in float32, sycomore and epgpy in float64. The CPU sweep pins **four threads** for every backend that takes them, which is what makes the packages comparable to each other rather than to the machine. At ten thousand tissues, four CPU threads, 32 orders: -| | forward | against TorchSim | +| | forward | against BlochSim | | --- | ---: | ---: | -| TorchSim | 0.187 s | -- | +| BlochSim | 0.187 s | -- | | BlochSimulators.jl, real states | 0.212 s | 1.1x slower | | BlochSimulators.jl, complex states | 0.520 s | 2.8x slower | | sycomore | 28.40 s | 152x slower | @@ -194,8 +194,8 @@ asking: | | CPU, four threads | RTX 4060 Laptop | what the card buys | | --- | ---: | ---: | ---: | | BlochSimulators.jl | 2.276 s | 0.037 s | 61x | -| TorchSim | 1.903 s | 0.069 s | 28x | -| TorchSim, Jacobian in T1 and T2 | 9.657 s | 0.381 s | 25x | +| BlochSim | 1.903 s | 0.069 s | 28x | +| BlochSim, Jacobian in T1 and T2 | 9.657 s | 0.381 s | 25x | | BlochSimulators.jl, finite differences | 7.409 s | 0.119 s | 62x | KomaMRI reaches a thousand tissues, and 64 isochromats each: 7.33 s on four @@ -208,18 +208,18 @@ BlochSimulators.jl is the comparison that matters -- it is the one other package built for exactly this workload -- and at ten thousand tissues on four threads the two are within a tenth of each other. Both take a real path when a train's pulses share one axis: BlochSimulators reads that off the element type -of its `RF_train`, TorchSim decides it per run from the phases the description +of its `RF_train`, BlochSim decides it per run from the phases the description carries, and both fall back to complex arithmetic when they cannot. The real path is worth 2.5x in BlochSimulators (0.212 s against 0.520 s) and 4.9x in -TorchSim, which `anatomy.py` measures on one event stream with only the verdict +BlochSim, which `anatomy.py` measures on one event stream with only the verdict changed. On the card the two separate. BlochSimulators simulates a hundred thousand -tissues in 37 ms against TorchSim's 69 ms, and takes its finite-difference -Jacobian in 119 ms against TorchSim's 381 ms. The two lay the same recursion out +tissues in 37 ms against BlochSim's 69 ms, and takes its finite-difference +Jacobian in 119 ms against BlochSim's 381 ms. The two lay the same recursion out differently: BlochSimulators gives each voxel a whole warp and each thread `states / 32` of the configuration orders, held in registers as an immutable -`SMatrix`, while TorchSim's Triton kernel takes a tile of voxels by all of +`SMatrix`, while BlochSim's Triton kernel takes a tile of voxels by all of their orders per program. Whatever the gap is, it is in the kernel and not in the dispatch around it: `anatomy.py --device cuda` puts a forced-real verdict at 106.3 ms against 107.2 ms for the automatic one, so the fast path is reached and @@ -243,15 +243,15 @@ an order of magnitude between one tissue and a hundred, then stops improving. **A Jacobian costs a few times the forward pass it comes from, on every package that has one.** At ten thousand tissues on four threads: BlochSimulators' finite differences 0.847 s, which is 4.0x its own forward pass -and is what three passes should cost; TorchSim's dual arithmetic 1.088 s for +and is what three passes should cost; BlochSim's dual arithmetic 1.088 s for two properties and 0.551 s for one, which is 2.9x its own forward pass per -property; epgpy's analytic derivative 156 s, 4.5x its own. TorchSim's is linear +property; epgpy's analytic derivative 156 s, 4.5x its own. BlochSim's is linear in the number of properties, as forward mode should be, and exact where the finite differences are not. The reverse pass takes the same lanes: a gradient through a 500-echo train costs 4.6x its forward pass on the real path, against the Jacobian's 5.9x. -**TorchSim pays a structure cost the others do not.** Resolving a +**BlochSim pays a structure cost the others do not.** Resolving a 500-repetition fingerprinting train -- walking 2 000 events, packing them, learning the affine rebinding -- takes 1.9 s, once per sequence *shape*; a 500-echo refocused train takes 0.16 s. What it buys is the calls afterwards: @@ -275,7 +275,7 @@ samples and the fastest of them, but a desktop or datacentre card would separate the kernels differently and neither ratio above should be read as architectural. -**A second and third task.** One 64-echo CPMG train, where TorchSim's +**A second and third task.** One 64-echo CPMG train, where BlochSim's real-subspace specialization does apply; one two-pool run, which sycomore cannot express at all and epgpy can. That pair separates "faster" from "does more", which no single-task benchmark can. diff --git a/benchmarks/anatomy.py b/benchmarks/anatomy.py index 104d2d8d..5851acc8 100644 --- a/benchmarks/anatomy.py +++ b/benchmarks/anatomy.py @@ -1,4 +1,4 @@ -"""What TorchSim's own runtime is made of, on one CPU core count. +"""What BlochSim's own runtime is made of, on one CPU core count. Four experiments, each isolating one thing the dictionary benchmark cannot: @@ -40,7 +40,7 @@ import torch -from torchsim.simulators import FSESimulator, MRFSimulator +from blochsim.simulators import FSESimulator, MRFSimulator WARMUP_SECONDS = 2.0 @@ -84,14 +84,14 @@ def _reached( which kernel a verdict selects, which is not something a caller can ask for. """ - from torchsim.sequence import _builders - from torchsim.sequence._accelerators import ( + from blochsim.sequence import _builders + from blochsim.sequence._accelerators import ( _pack_events, _run_packed, _run_packed_jvp, real_subspace_axis, ) - from torchsim.sequence._simulation import TissueProperties, _prepare_tissue + from blochsim.sequence._simulation import TissueProperties, _prepare_tissue description = _builders.fse_description( echo_train.to(torch.float32) * (torch.pi / 180.0), @@ -308,7 +308,7 @@ def gradient(s: Any = sequence) -> None: " few times the plain one. The multiple is the check that the dual and\n" " adjoint kernels take the same path the plain ones take, rather than\n" " the arithmetic being cheap where the plumbing is not. Run the whole\n" - " file again under TORCHSIM_REAL_SCALAR=1 to separate what the real\n" + " file again under BLOCHSIM_REAL_SCALAR=1 to separate what the real\n" " subspace is worth from what the lane kernels on top of it are worth:\n" " a multiple that does not move is a pass with no laned kernel to take." ) diff --git a/benchmarks/bench_torchsim.py b/benchmarks/bench_blochsim.py similarity index 89% rename from benchmarks/bench_torchsim.py rename to benchmarks/bench_blochsim.py index f2a718c9..e8de8692 100644 --- a/benchmarks/bench_torchsim.py +++ b/benchmarks/bench_blochsim.py @@ -1,6 +1,6 @@ -"""The fingerprinting dictionary, on TorchSim's fused state machine. +"""The fingerprinting dictionary, on BlochSim's fused state machine. -Run as ``python benchmarks/bench_torchsim.py --atoms 10000``. The structure of +Run as ``python benchmarks/bench_blochsim.py --atoms 10000``. The structure of the sequence is resolved once before anything is timed, which is what a dictionary sweep or a design loop does too: the schedule is the same at every call and only the tissue changes. @@ -28,7 +28,7 @@ def main() -> None: """Time one dictionary, and say what it cost.""" - cli = parser("torchsim") + cli = parser("blochsim") cli.add_argument( "--diff", default="T1,T2", @@ -37,13 +37,13 @@ def main() -> None: args = cli.parse_args() diff = tuple(name for name in args.diff.split(",") if name) if args.threads: - os.environ["TORCHSIM_NUM_THREADS"] = str(args.threads) + os.environ["BLOCHSIM_NUM_THREADS"] = str(args.threads) import time import torch - from torchsim.simulators import MRFSimulator + from blochsim.simulators import MRFSimulator if args.threads: torch.set_num_threads(args.threads) @@ -85,7 +85,7 @@ def jacobian() -> torch.Tensor: seconds, signal = timed(run, repeats=args.repeats, synchronize=synchronize) measurement = Measurement( - backend="torchsim", + backend="blochsim", mode=args.mode if args.mode == "forward" else f"jacobian({args.diff})", atoms=args.atoms, length=args.length, @@ -106,7 +106,7 @@ def jacobian() -> torch.Tensor: else 0.0 ), checksum=complex(signal.reshape(-1).to(torch.complex64).sum().cpu()), - versions={"torchsim": version("torchsim"), "torch": torch.__version__}, + versions={"blochsim": version("blochsim"), "torch": torch.__version__}, machine=machine(), ) report(measurement, args.json) diff --git a/benchmarks/bench_epgpy.py b/benchmarks/bench_epgpy.py index 8ff6c4e5..af885e7e 100644 --- a/benchmarks/bench_epgpy.py +++ b/benchmarks/bench_epgpy.py @@ -1,6 +1,6 @@ """The same fingerprinting dictionary, on epgpy's operator-per-event EPG. -Epgpy is the closest thing to TorchSim in Python: NumPy under the hood, the +Epgpy is the closest thing to BlochSim in Python: NumPy under the hood, the dictionary carried in the array shape rather than in a loop, analytic first- and second-order derivatives, and CuPy for a card. What it does not do is fuse: each operator is a pass over the whole state matrix, so a train of a thousand @@ -86,7 +86,7 @@ def run() -> np.ndarray: checksum=complex(np.asarray(signal).sum()), versions={"epgpy": release, "numpy": np.__version__}, machine=machine(), - note="max_nstate matches the orders TorchSim keeps", + note="max_nstate matches the orders BlochSim keeps", ) report(measurement, args.json) diff --git a/benchmarks/julia/Project.toml b/benchmarks/julia/Project.toml index 721b5cbd..d3ebd793 100644 --- a/benchmarks/julia/Project.toml +++ b/benchmarks/julia/Project.toml @@ -1,4 +1,4 @@ -name = "TorchSimBenchmarks" +name = "BlochSimBenchmarks" # The two Julia simulators the dictionary benchmark runs against, and what the # scripts here need to drive them. Instantiate with: diff --git a/benchmarks/julia/bench_blochsimulators.jl b/benchmarks/julia/bench_blochsimulators.jl index 695cfba9..282aa903 100644 --- a/benchmarks/julia/bench_blochsimulators.jl +++ b/benchmarks/julia/bench_blochsimulators.jl @@ -12,7 +12,7 @@ # * `FISP2D` requires the number of configuration states to be a multiple of # 32, so the shared task carries 32 orders rather than 20. # * A real `RF_train` keeps the configuration states real, which is the same -# specialization TorchSim applies when a sequence earns it. The train here +# specialization BlochSim applies when a sequence earns it. The train here # has no phase, so both packages take their real path. # # Run as (from the repository root): @@ -66,7 +66,7 @@ function main() baseline = peak_rss_mib() # Seconds and milliseconds: BlochSimulators takes times in seconds, - # TorchSim in milliseconds, and the task is stated in milliseconds. + # BlochSim in milliseconds, and the task is stated in milliseconds. TR = 0.010 TE = 0.0 TI = 0.0 @@ -83,7 +83,7 @@ function main() atoms = length(T1) end # A real RF train keeps the configuration states real, which is the same - # saving TorchSim's real-subspace kernels make. Asking for a complex train + # saving BlochSim's real-subspace kernels make. Asking for a complex train # is how the two halves of the difference are told apart: what the # specialization buys, and what is left over. train = options["rf"] == "complex" ? complex.(flip) : flip diff --git a/benchmarks/make_figures.py b/benchmarks/make_figures.py index 420fef2b..02f546b0 100644 --- a/benchmarks/make_figures.py +++ b/benchmarks/make_figures.py @@ -42,7 +42,7 @@ # Categorical slots in a fixed order: a backend keeps its colour whether or # not the others are present. COLORS = { - "torchsim": "#2a78d6", + "blochsim": "#2a78d6", "sycomore": "#eb6834", "epgpy": "#1baf7a", "BlochSimulators.jl": "#eda100", @@ -177,7 +177,7 @@ def draw(axes: plt.Axes, name: str, points: list[tuple[int, float]]) -> None: def throughput(records: list[dict], suffix: str) -> None: """Tissues a second against dictionary size, one line per backend.""" threaded = [ - r for r in records if not (r["backend"] == "torchsim" and r["threads"] == 1) + r for r in records if not (r["backend"] == "blochsim" and r["threads"] == 1) ] series = curves(threaded, "forward", lambda r: r["atoms"] / r["best"]) figure, axes = plt.subplots(figsize=(7.2, 4.2), dpi=200) @@ -276,7 +276,7 @@ def memory(records: list[dict], suffix: str) -> None: r for r in records if r.get("device", "cpu") == "cpu" - and not (r["backend"] == "torchsim" and r["threads"] == 1) + and not (r["backend"] == "blochsim" and r["threads"] == 1) ] series = curves( threaded, "forward", lambda r: r["peak_rss_mib"] - r["baseline_rss_mib"] diff --git a/benchmarks/results/anatomy.txt b/benchmarks/results/anatomy.txt index 62ad8c03..e9c9a588 100644 --- a/benchmarks/results/anatomy.txt +++ b/benchmarks/results/anatomy.txt @@ -38,7 +38,7 @@ What the Jacobian costs on each path, same event stream few times the plain one. The multiple is the check that the dual and adjoint kernels take the same path the plain ones take, rather than the arithmetic being cheap where the plumbing is not. Run the whole - file again under TORCHSIM_REAL_SCALAR=1 to separate what the real + file again under BLOCHSIM_REAL_SCALAR=1 to separate what the real subspace is worth from what the lane kernels on top of it are worth: a multiple that does not move is a pass with no laned kernel to take. @@ -84,6 +84,6 @@ What the Jacobian costs on each path, same event stream few times the plain one. The multiple is the check that the dual and adjoint kernels take the same path the plain ones take, rather than the arithmetic being cheap where the plumbing is not. Run the whole - file again under TORCHSIM_REAL_SCALAR=1 to separate what the real + file again under BLOCHSIM_REAL_SCALAR=1 to separate what the real subspace is worth from what the lane kernels on top of it are worth: a multiple that does not move is a pass with no laned kernel to take. diff --git a/benchmarks/results/torchsim-forward-1-cpu-t4.json b/benchmarks/results/blochsim-forward-1-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-forward-1-cpu-t4.json rename to benchmarks/results/blochsim-forward-1-cpu-t4.json index e72799d7..b58d5462 100644 --- a/benchmarks/results/torchsim-forward-1-cpu-t4.json +++ b/benchmarks/results/blochsim-forward-1-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 1, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 708.36328125, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-1-cuda.json b/benchmarks/results/blochsim-forward-1-cuda.json similarity index 94% rename from benchmarks/results/torchsim-forward-1-cuda.json rename to benchmarks/results/blochsim-forward-1-cuda.json index 4e4da2d2..e80c1e87 100644 --- a/benchmarks/results/torchsim-forward-1-cuda.json +++ b/benchmarks/results/blochsim-forward-1-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 1, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1152.296875, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-10-cpu-t4.json b/benchmarks/results/blochsim-forward-10-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-forward-10-cpu-t4.json rename to benchmarks/results/blochsim-forward-10-cpu-t4.json index d0b05863..d2bce726 100644 --- a/benchmarks/results/torchsim-forward-10-cpu-t4.json +++ b/benchmarks/results/blochsim-forward-10-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 10, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 707.640625, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-10-cuda.json b/benchmarks/results/blochsim-forward-10-cuda.json similarity index 94% rename from benchmarks/results/torchsim-forward-10-cuda.json rename to benchmarks/results/blochsim-forward-10-cuda.json index f326da1b..c3a6dea0 100644 --- a/benchmarks/results/torchsim-forward-10-cuda.json +++ b/benchmarks/results/blochsim-forward-10-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 10, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1179.51953125, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-100-cpu-t4.json b/benchmarks/results/blochsim-forward-100-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-forward-100-cpu-t4.json rename to benchmarks/results/blochsim-forward-100-cpu-t4.json index 79f072fa..c9fe0af7 100644 --- a/benchmarks/results/torchsim-forward-100-cpu-t4.json +++ b/benchmarks/results/blochsim-forward-100-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 100, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 708.12890625, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-100-cuda.json b/benchmarks/results/blochsim-forward-100-cuda.json similarity index 94% rename from benchmarks/results/torchsim-forward-100-cuda.json rename to benchmarks/results/blochsim-forward-100-cuda.json index a3232af7..42ac441f 100644 --- a/benchmarks/results/torchsim-forward-100-cuda.json +++ b/benchmarks/results/blochsim-forward-100-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 100, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1240.3984375, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-1000-cpu-t1.json b/benchmarks/results/blochsim-forward-1000-cpu-t1.json similarity index 92% rename from benchmarks/results/torchsim-forward-1000-cpu-t1.json rename to benchmarks/results/blochsim-forward-1000-cpu-t1.json index f0917469..52115e16 100644 --- a/benchmarks/results/torchsim-forward-1000-cpu-t1.json +++ b/benchmarks/results/blochsim-forward-1000-cpu-t1.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 1000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 724.49609375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-1000-cpu-t4.json b/benchmarks/results/blochsim-forward-1000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-forward-1000-cpu-t4.json rename to benchmarks/results/blochsim-forward-1000-cpu-t4.json index 02175fc2..b1b5d05d 100644 --- a/benchmarks/results/torchsim-forward-1000-cpu-t4.json +++ b/benchmarks/results/blochsim-forward-1000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 1000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 720.44921875, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-1000-cuda.json b/benchmarks/results/blochsim-forward-1000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-forward-1000-cuda.json rename to benchmarks/results/blochsim-forward-1000-cuda.json index ea95eb79..905b032f 100644 --- a/benchmarks/results/torchsim-forward-1000-cuda.json +++ b/benchmarks/results/blochsim-forward-1000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 1000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1201.71875, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-10000-cpu-t1.json b/benchmarks/results/blochsim-forward-10000-cpu-t1.json similarity index 92% rename from benchmarks/results/torchsim-forward-10000-cpu-t1.json rename to benchmarks/results/blochsim-forward-10000-cpu-t1.json index a03431e5..e937dbdd 100644 --- a/benchmarks/results/torchsim-forward-10000-cpu-t1.json +++ b/benchmarks/results/blochsim-forward-10000-cpu-t1.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 10000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 822.4609375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-10000-cpu-t4.json b/benchmarks/results/blochsim-forward-10000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-forward-10000-cpu-t4.json rename to benchmarks/results/blochsim-forward-10000-cpu-t4.json index 88bd5f74..1074ba27 100644 --- a/benchmarks/results/torchsim-forward-10000-cpu-t4.json +++ b/benchmarks/results/blochsim-forward-10000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 10000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 859.69921875, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-10000-cuda.json b/benchmarks/results/blochsim-forward-10000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-forward-10000-cuda.json rename to benchmarks/results/blochsim-forward-10000-cuda.json index 62d84918..57090f1b 100644 --- a/benchmarks/results/torchsim-forward-10000-cuda.json +++ b/benchmarks/results/blochsim-forward-10000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 10000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1194.02734375, "peak_device_mib": 196.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-100000-cpu-t1.json b/benchmarks/results/blochsim-forward-100000-cpu-t1.json similarity index 92% rename from benchmarks/results/torchsim-forward-100000-cpu-t1.json rename to benchmarks/results/blochsim-forward-100000-cpu-t1.json index d3526f16..1b2432d6 100644 --- a/benchmarks/results/torchsim-forward-100000-cpu-t1.json +++ b/benchmarks/results/blochsim-forward-100000-cpu-t1.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 100000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 1860.96875, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-100000-cpu-t4.json b/benchmarks/results/blochsim-forward-100000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-forward-100000-cpu-t4.json rename to benchmarks/results/blochsim-forward-100000-cpu-t4.json index 879b18db..62f0d4f8 100644 --- a/benchmarks/results/torchsim-forward-100000-cpu-t4.json +++ b/benchmarks/results/blochsim-forward-100000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 100000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 1860.58203125, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-forward-100000-cuda.json b/benchmarks/results/blochsim-forward-100000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-forward-100000-cuda.json rename to benchmarks/results/blochsim-forward-100000-cuda.json index b847e43e..5a0a3bc9 100644 --- a/benchmarks/results/torchsim-forward-100000-cuda.json +++ b/benchmarks/results/blochsim-forward-100000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "forward", "atoms": 100000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1203.59375, "peak_device_mib": 1586.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-1-cpu-t4.json b/benchmarks/results/blochsim-jacobian-1-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian-1-cpu-t4.json rename to benchmarks/results/blochsim-jacobian-1-cpu-t4.json index f9f448d8..0453b864 100644 --- a/benchmarks/results/torchsim-jacobian-1-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian-1-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 1, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 712.3828125, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-1-cuda.json b/benchmarks/results/blochsim-jacobian-1-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian-1-cuda.json rename to benchmarks/results/blochsim-jacobian-1-cuda.json index 91ff2ca2..10a91845 100644 --- a/benchmarks/results/torchsim-jacobian-1-cuda.json +++ b/benchmarks/results/blochsim-jacobian-1-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 1, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1164.125, "peak_device_mib": 72.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-10-cpu-t4.json b/benchmarks/results/blochsim-jacobian-10-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian-10-cpu-t4.json rename to benchmarks/results/blochsim-jacobian-10-cpu-t4.json index b3b71c59..8e119478 100644 --- a/benchmarks/results/torchsim-jacobian-10-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian-10-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 10, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 712.0234375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-10-cuda.json b/benchmarks/results/blochsim-jacobian-10-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian-10-cuda.json rename to benchmarks/results/blochsim-jacobian-10-cuda.json index 15ffc5ca..bf016b43 100644 --- a/benchmarks/results/torchsim-jacobian-10-cuda.json +++ b/benchmarks/results/blochsim-jacobian-10-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 10, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1197.01953125, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-100-cpu-t4.json b/benchmarks/results/blochsim-jacobian-100-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian-100-cpu-t4.json rename to benchmarks/results/blochsim-jacobian-100-cpu-t4.json index efffb419..e64c464c 100644 --- a/benchmarks/results/torchsim-jacobian-100-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian-100-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 100, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 714.125, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-100-cuda.json b/benchmarks/results/blochsim-jacobian-100-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian-100-cuda.json rename to benchmarks/results/blochsim-jacobian-100-cuda.json index 87eb5789..9d39af1b 100644 --- a/benchmarks/results/torchsim-jacobian-100-cuda.json +++ b/benchmarks/results/blochsim-jacobian-100-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 100, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1252.91796875, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-1000-cpu-t4.json b/benchmarks/results/blochsim-jacobian-1000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian-1000-cpu-t4.json rename to benchmarks/results/blochsim-jacobian-1000-cpu-t4.json index dec50db2..989aa3ca 100644 --- a/benchmarks/results/torchsim-jacobian-1000-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian-1000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 1000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 756.9609375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-1000-cuda.json b/benchmarks/results/blochsim-jacobian-1000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian-1000-cuda.json rename to benchmarks/results/blochsim-jacobian-1000-cuda.json index bcbc4461..c9676ee0 100644 --- a/benchmarks/results/torchsim-jacobian-1000-cuda.json +++ b/benchmarks/results/blochsim-jacobian-1000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 1000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1206.015625, "peak_device_mib": 56.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-10000-cpu-t4.json b/benchmarks/results/blochsim-jacobian-10000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian-10000-cpu-t4.json rename to benchmarks/results/blochsim-jacobian-10000-cpu-t4.json index d9467761..6e9bc006 100644 --- a/benchmarks/results/torchsim-jacobian-10000-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian-10000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 10000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 1054.765625, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-10000-cuda.json b/benchmarks/results/blochsim-jacobian-10000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian-10000-cuda.json rename to benchmarks/results/blochsim-jacobian-10000-cuda.json index 431983f3..e9b06254 100644 --- a/benchmarks/results/torchsim-jacobian-10000-cuda.json +++ b/benchmarks/results/blochsim-jacobian-10000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 10000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1206.015625, "peak_device_mib": 512.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-100000-cpu-t4.json b/benchmarks/results/blochsim-jacobian-100000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian-100000-cpu-t4.json rename to benchmarks/results/blochsim-jacobian-100000-cpu-t4.json index 8f9758c1..9d3aa230 100644 --- a/benchmarks/results/torchsim-jacobian-100000-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian-100000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 100000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 4147.16796875, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian-100000-cuda.json b/benchmarks/results/blochsim-jacobian-100000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian-100000-cuda.json rename to benchmarks/results/blochsim-jacobian-100000-cuda.json index db664941..0cadf2b7 100644 --- a/benchmarks/results/torchsim-jacobian-100000-cuda.json +++ b/benchmarks/results/blochsim-jacobian-100000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1,T2)", "atoms": 100000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1207.609375, "peak_device_mib": 4642.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-1-cpu-t4.json b/benchmarks/results/blochsim-jacobian1-1-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian1-1-cpu-t4.json rename to benchmarks/results/blochsim-jacobian1-1-cpu-t4.json index 972e2bbf..8aaf968b 100644 --- a/benchmarks/results/torchsim-jacobian1-1-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian1-1-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 1, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 711.5234375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-1-cuda.json b/benchmarks/results/blochsim-jacobian1-1-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian1-1-cuda.json rename to benchmarks/results/blochsim-jacobian1-1-cuda.json index 4d7c71c7..833debb7 100644 --- a/benchmarks/results/torchsim-jacobian1-1-cuda.json +++ b/benchmarks/results/blochsim-jacobian1-1-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 1, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1166.8359375, "peak_device_mib": 122.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-10-cpu-t4.json b/benchmarks/results/blochsim-jacobian1-10-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian1-10-cpu-t4.json rename to benchmarks/results/blochsim-jacobian1-10-cpu-t4.json index d9fa1388..3595b884 100644 --- a/benchmarks/results/torchsim-jacobian1-10-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian1-10-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 10, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 712.04296875, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-10-cuda.json b/benchmarks/results/blochsim-jacobian1-10-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian1-10-cuda.json rename to benchmarks/results/blochsim-jacobian1-10-cuda.json index 7334a45e..22d6d67d 100644 --- a/benchmarks/results/torchsim-jacobian1-10-cuda.json +++ b/benchmarks/results/blochsim-jacobian1-10-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 10, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1157.8125, "peak_device_mib": 54.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-100-cpu-t4.json b/benchmarks/results/blochsim-jacobian1-100-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian1-100-cpu-t4.json rename to benchmarks/results/blochsim-jacobian1-100-cpu-t4.json index 661c3671..2870640d 100644 --- a/benchmarks/results/torchsim-jacobian1-100-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian1-100-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 100, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 712.90234375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-100-cuda.json b/benchmarks/results/blochsim-jacobian1-100-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian1-100-cuda.json rename to benchmarks/results/blochsim-jacobian1-100-cuda.json index c1c0bc88..627f69e3 100644 --- a/benchmarks/results/torchsim-jacobian1-100-cuda.json +++ b/benchmarks/results/blochsim-jacobian1-100-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 100, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1209.2578125, "peak_device_mib": 72.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-1000-cpu-t4.json b/benchmarks/results/blochsim-jacobian1-1000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian1-1000-cpu-t4.json rename to benchmarks/results/blochsim-jacobian1-1000-cpu-t4.json index 0254d8ca..2c087373 100644 --- a/benchmarks/results/torchsim-jacobian1-1000-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian1-1000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 1000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 745.24609375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-1000-cuda.json b/benchmarks/results/blochsim-jacobian1-1000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian1-1000-cuda.json rename to benchmarks/results/blochsim-jacobian1-1000-cuda.json index c76f9cee..027d205a 100644 --- a/benchmarks/results/torchsim-jacobian1-1000-cuda.json +++ b/benchmarks/results/blochsim-jacobian1-1000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 1000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1210.796875, "peak_device_mib": 56.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-10000-cpu-t4.json b/benchmarks/results/blochsim-jacobian1-10000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian1-10000-cpu-t4.json rename to benchmarks/results/blochsim-jacobian1-10000-cpu-t4.json index c7a57ca7..f1a14f45 100644 --- a/benchmarks/results/torchsim-jacobian1-10000-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian1-10000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 10000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 940.640625, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-10000-cuda.json b/benchmarks/results/blochsim-jacobian1-10000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian1-10000-cuda.json rename to benchmarks/results/blochsim-jacobian1-10000-cuda.json index 2dbea12d..43d41a2e 100644 --- a/benchmarks/results/torchsim-jacobian1-10000-cuda.json +++ b/benchmarks/results/blochsim-jacobian1-10000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 10000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1206.37890625, "peak_device_mib": 316.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-100000-cpu-t4.json b/benchmarks/results/blochsim-jacobian1-100000-cpu-t4.json similarity index 92% rename from benchmarks/results/torchsim-jacobian1-100000-cpu-t4.json rename to benchmarks/results/blochsim-jacobian1-100000-cpu-t4.json index 24c6c1a8..e703c445 100644 --- a/benchmarks/results/torchsim-jacobian1-100000-cpu-t4.json +++ b/benchmarks/results/blochsim-jacobian1-100000-cpu-t4.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 100000, "length": 500, @@ -17,7 +17,7 @@ "peak_rss_mib": 3006.68359375, "peak_device_mib": 0.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/torchsim-jacobian1-100000-cuda.json b/benchmarks/results/blochsim-jacobian1-100000-cuda.json similarity index 94% rename from benchmarks/results/torchsim-jacobian1-100000-cuda.json rename to benchmarks/results/blochsim-jacobian1-100000-cuda.json index e7e23ce5..e25306a4 100644 --- a/benchmarks/results/torchsim-jacobian1-100000-cuda.json +++ b/benchmarks/results/blochsim-jacobian1-100000-cuda.json @@ -1,5 +1,5 @@ { - "backend": "torchsim", + "backend": "blochsim", "mode": "jacobian(T1)", "atoms": 100000, "length": 500, @@ -29,7 +29,7 @@ "peak_rss_mib": 1209.09375, "peak_device_mib": 2732.0, "versions": { - "torchsim": "0.0.4.dev137", + "blochsim": "0.0.4.dev137", "torch": "2.13.0+cu130" }, "machine": { diff --git a/benchmarks/results/epgpy-forward-1.json b/benchmarks/results/epgpy-forward-1.json index c3d9ef39..4debe88d 100644 --- a/benchmarks/results/epgpy-forward-1.json +++ b/benchmarks/results/epgpy-forward-1.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 0.02916437999920163, "median": 0.030261382000389858, "atoms_per_second": 34.28840249740865, diff --git a/benchmarks/results/epgpy-forward-10.json b/benchmarks/results/epgpy-forward-10.json index 71b7008c..3bc1a7b6 100644 --- a/benchmarks/results/epgpy-forward-10.json +++ b/benchmarks/results/epgpy-forward-10.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 0.05330446900006791, "median": 0.054138121000505635, "atoms_per_second": 187.60153112091334, diff --git a/benchmarks/results/epgpy-forward-100.json b/benchmarks/results/epgpy-forward-100.json index fe993a1b..cfe39db6 100644 --- a/benchmarks/results/epgpy-forward-100.json +++ b/benchmarks/results/epgpy-forward-100.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 0.2370688480004901, "median": 0.27986130800127285, "atoms_per_second": 421.81839091652085, diff --git a/benchmarks/results/epgpy-forward-1000.json b/benchmarks/results/epgpy-forward-1000.json index f07da714..a5de274f 100644 --- a/benchmarks/results/epgpy-forward-1000.json +++ b/benchmarks/results/epgpy-forward-1000.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 2.510219899000731, "median": 2.5374647440003173, "atoms_per_second": 398.3714735103806, diff --git a/benchmarks/results/epgpy-forward-10000.json b/benchmarks/results/epgpy-forward-10000.json index b464c4bf..51633db4 100644 --- a/benchmarks/results/epgpy-forward-10000.json +++ b/benchmarks/results/epgpy-forward-10000.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 34.74142441699951, "median": 36.0643737399987, "atoms_per_second": 287.84081734734076, diff --git a/benchmarks/results/epgpy-jacobian-1.json b/benchmarks/results/epgpy-jacobian-1.json index 8cd5f126..735f7424 100644 --- a/benchmarks/results/epgpy-jacobian-1.json +++ b/benchmarks/results/epgpy-jacobian-1.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 0.08616435799922328, "median": 0.08839201599948865, "atoms_per_second": 11.605726813504655, diff --git a/benchmarks/results/epgpy-jacobian-10.json b/benchmarks/results/epgpy-jacobian-10.json index 5477d3e7..7028732b 100644 --- a/benchmarks/results/epgpy-jacobian-10.json +++ b/benchmarks/results/epgpy-jacobian-10.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 0.1852214129994536, "median": 0.18672170799982268, "atoms_per_second": 53.9894380355985, diff --git a/benchmarks/results/epgpy-jacobian-100.json b/benchmarks/results/epgpy-jacobian-100.json index 29a30a84..ec47107b 100644 --- a/benchmarks/results/epgpy-jacobian-100.json +++ b/benchmarks/results/epgpy-jacobian-100.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 0.9521177429996897, "median": 0.9590095800012932, "atoms_per_second": 105.02902685643218, diff --git a/benchmarks/results/epgpy-jacobian-1000.json b/benchmarks/results/epgpy-jacobian-1000.json index b365d5ab..36deeb2b 100644 --- a/benchmarks/results/epgpy-jacobian-1000.json +++ b/benchmarks/results/epgpy-jacobian-1000.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 10.551914194000346, "median": 12.37560925300022, "atoms_per_second": 94.76953485544684, diff --git a/benchmarks/results/epgpy-jacobian-10000.json b/benchmarks/results/epgpy-jacobian-10000.json index 6bea51fd..3a2ec9e7 100644 --- a/benchmarks/results/epgpy-jacobian-10000.json +++ b/benchmarks/results/epgpy-jacobian-10000.json @@ -25,7 +25,7 @@ "processor": "x86_64", "python": "3.11.5" }, - "note": "max_nstate matches the orders TorchSim keeps", + "note": "max_nstate matches the orders BlochSim keeps", "best": 155.95141346199853, "median": 164.54237790599836, "atoms_per_second": 64.12253520508649, diff --git a/benchmarks/results/table.md b/benchmarks/results/table.md index 66c7a5a9..be5695dc 100644 --- a/benchmarks/results/table.md +++ b/benchmarks/results/table.md @@ -50,45 +50,45 @@ | sycomore | forward | cpu | 1 | 100 | 0.2687 | 372 | 37 | 3.5 | 0 | | sycomore | forward | cpu | 1 | 1000 | 2.2262 | 449 | 57 | 23.8 | 0 | | sycomore | forward | cpu | 1 | 10000 | 28.3982 | 352 | 266 | 233.0 | 0 | -| torchsim | forward | cpu | 1 | 1000 | 0.0698 | 14,324 | 724 | 117.5 | 0 | -| torchsim | forward | cpu | 1 | 10000 | 0.7669 | 13,039 | 822 | 215.4 | 0 | -| torchsim | forward | cpu | 1 | 100000 | 7.1735 | 13,940 | 1861 | 1254.3 | 0 | -| torchsim | forward | cpu | 4 | 1 | 0.0009 | 1,132 | 708 | 101.6 | 0 | -| torchsim | forward | cpu | 4 | 10 | 0.0016 | 6,343 | 708 | 101.1 | 0 | -| torchsim | forward | cpu | 4 | 100 | 0.0037 | 27,010 | 708 | 101.2 | 0 | -| torchsim | forward | cpu | 4 | 1000 | 0.0233 | 42,972 | 720 | 113.5 | 0 | -| torchsim | forward | cpu | 4 | 10000 | 0.1866 | 53,578 | 860 | 252.6 | 0 | -| torchsim | forward | cpu | 4 | 100000 | 1.9028 | 52,554 | 1861 | 1254.0 | 0 | -| torchsim | forward | cuda | 4 | 1 | 0.0037 | 267 | 1152 | 551.3 | 54 | -| torchsim | forward | cuda | 4 | 10 | 0.0034 | 2,942 | 1180 | 582.2 | 54 | -| torchsim | forward | cuda | 4 | 100 | 0.0028 | 35,256 | 1240 | 639.7 | 54 | -| torchsim | forward | cuda | 4 | 1000 | 0.0033 | 301,734 | 1202 | 600.6 | 54 | -| torchsim | forward | cuda | 4 | 10000 | 0.0087 | 1,150,290 | 1194 | 592.4 | 196 | -| torchsim | forward | cuda | 4 | 100000 | 0.0687 | 1,454,714 | 1204 | 602.3 | 1586 | -| torchsim | jacobian(T1) | cpu | 4 | 1 | 0.0054 | 185 | 712 | 105.2 | 0 | -| torchsim | jacobian(T1) | cpu | 4 | 10 | 0.0081 | 1,234 | 712 | 105.4 | 0 | -| torchsim | jacobian(T1) | cpu | 4 | 100 | 0.0117 | 8,520 | 713 | 106.3 | 0 | -| torchsim | jacobian(T1) | cpu | 4 | 1000 | 0.0534 | 18,738 | 745 | 138.4 | 0 | -| torchsim | jacobian(T1) | cpu | 4 | 10000 | 0.5511 | 18,146 | 941 | 333.8 | 0 | -| torchsim | jacobian(T1) | cpu | 4 | 100000 | 5.4812 | 18,244 | 3007 | 2400.2 | 0 | -| torchsim | jacobian(T1) | cuda | 4 | 1 | 0.0084 | 119 | 1167 | 560.0 | 122 | -| torchsim | jacobian(T1) | cuda | 4 | 10 | 0.0081 | 1,242 | 1158 | 556.2 | 54 | -| torchsim | jacobian(T1) | cuda | 4 | 100 | 0.0079 | 12,640 | 1209 | 607.9 | 72 | -| torchsim | jacobian(T1) | cuda | 4 | 1000 | 0.0080 | 124,412 | 1211 | 609.8 | 56 | -| torchsim | jacobian(T1) | cuda | 4 | 10000 | 0.0237 | 421,684 | 1206 | 605.2 | 316 | -| torchsim | jacobian(T1) | cuda | 4 | 100000 | 0.1895 | 527,615 | 1209 | 607.7 | 2732 | -| torchsim | jacobian(T1,T2) | cpu | 4 | 1 | 0.0101 | 99 | 712 | 105.1 | 0 | -| torchsim | jacobian(T1,T2) | cpu | 4 | 10 | 0.0117 | 856 | 712 | 105.7 | 0 | -| torchsim | jacobian(T1,T2) | cpu | 4 | 100 | 0.0229 | 4,358 | 714 | 107.2 | 0 | -| torchsim | jacobian(T1,T2) | cpu | 4 | 1000 | 0.1285 | 7,783 | 757 | 150.1 | 0 | -| torchsim | jacobian(T1,T2) | cpu | 4 | 10000 | 1.0879 | 9,192 | 1055 | 448.1 | 0 | -| torchsim | jacobian(T1,T2) | cpu | 4 | 100000 | 9.6565 | 10,356 | 4147 | 3540.4 | 0 | -| torchsim | jacobian(T1,T2) | cuda | 4 | 1 | 0.0156 | 64 | 1164 | 563.0 | 72 | -| torchsim | jacobian(T1,T2) | cuda | 4 | 10 | 0.0156 | 640 | 1197 | 595.8 | 54 | -| torchsim | jacobian(T1,T2) | cuda | 4 | 100 | 0.0159 | 6,282 | 1253 | 651.3 | 54 | -| torchsim | jacobian(T1,T2) | cuda | 4 | 1000 | 0.0158 | 63,392 | 1206 | 605.0 | 56 | -| torchsim | jacobian(T1,T2) | cuda | 4 | 10000 | 0.0459 | 217,764 | 1206 | 605.3 | 512 | -| torchsim | jacobian(T1,T2) | cuda | 4 | 100000 | 0.3813 | 262,295 | 1208 | 606.0 | 4642 | +| blochsim | forward | cpu | 1 | 1000 | 0.0698 | 14,324 | 724 | 117.5 | 0 | +| blochsim | forward | cpu | 1 | 10000 | 0.7669 | 13,039 | 822 | 215.4 | 0 | +| blochsim | forward | cpu | 1 | 100000 | 7.1735 | 13,940 | 1861 | 1254.3 | 0 | +| blochsim | forward | cpu | 4 | 1 | 0.0009 | 1,132 | 708 | 101.6 | 0 | +| blochsim | forward | cpu | 4 | 10 | 0.0016 | 6,343 | 708 | 101.1 | 0 | +| blochsim | forward | cpu | 4 | 100 | 0.0037 | 27,010 | 708 | 101.2 | 0 | +| blochsim | forward | cpu | 4 | 1000 | 0.0233 | 42,972 | 720 | 113.5 | 0 | +| blochsim | forward | cpu | 4 | 10000 | 0.1866 | 53,578 | 860 | 252.6 | 0 | +| blochsim | forward | cpu | 4 | 100000 | 1.9028 | 52,554 | 1861 | 1254.0 | 0 | +| blochsim | forward | cuda | 4 | 1 | 0.0037 | 267 | 1152 | 551.3 | 54 | +| blochsim | forward | cuda | 4 | 10 | 0.0034 | 2,942 | 1180 | 582.2 | 54 | +| blochsim | forward | cuda | 4 | 100 | 0.0028 | 35,256 | 1240 | 639.7 | 54 | +| blochsim | forward | cuda | 4 | 1000 | 0.0033 | 301,734 | 1202 | 600.6 | 54 | +| blochsim | forward | cuda | 4 | 10000 | 0.0087 | 1,150,290 | 1194 | 592.4 | 196 | +| blochsim | forward | cuda | 4 | 100000 | 0.0687 | 1,454,714 | 1204 | 602.3 | 1586 | +| blochsim | jacobian(T1) | cpu | 4 | 1 | 0.0054 | 185 | 712 | 105.2 | 0 | +| blochsim | jacobian(T1) | cpu | 4 | 10 | 0.0081 | 1,234 | 712 | 105.4 | 0 | +| blochsim | jacobian(T1) | cpu | 4 | 100 | 0.0117 | 8,520 | 713 | 106.3 | 0 | +| blochsim | jacobian(T1) | cpu | 4 | 1000 | 0.0534 | 18,738 | 745 | 138.4 | 0 | +| blochsim | jacobian(T1) | cpu | 4 | 10000 | 0.5511 | 18,146 | 941 | 333.8 | 0 | +| blochsim | jacobian(T1) | cpu | 4 | 100000 | 5.4812 | 18,244 | 3007 | 2400.2 | 0 | +| blochsim | jacobian(T1) | cuda | 4 | 1 | 0.0084 | 119 | 1167 | 560.0 | 122 | +| blochsim | jacobian(T1) | cuda | 4 | 10 | 0.0081 | 1,242 | 1158 | 556.2 | 54 | +| blochsim | jacobian(T1) | cuda | 4 | 100 | 0.0079 | 12,640 | 1209 | 607.9 | 72 | +| blochsim | jacobian(T1) | cuda | 4 | 1000 | 0.0080 | 124,412 | 1211 | 609.8 | 56 | +| blochsim | jacobian(T1) | cuda | 4 | 10000 | 0.0237 | 421,684 | 1206 | 605.2 | 316 | +| blochsim | jacobian(T1) | cuda | 4 | 100000 | 0.1895 | 527,615 | 1209 | 607.7 | 2732 | +| blochsim | jacobian(T1,T2) | cpu | 4 | 1 | 0.0101 | 99 | 712 | 105.1 | 0 | +| blochsim | jacobian(T1,T2) | cpu | 4 | 10 | 0.0117 | 856 | 712 | 105.7 | 0 | +| blochsim | jacobian(T1,T2) | cpu | 4 | 100 | 0.0229 | 4,358 | 714 | 107.2 | 0 | +| blochsim | jacobian(T1,T2) | cpu | 4 | 1000 | 0.1285 | 7,783 | 757 | 150.1 | 0 | +| blochsim | jacobian(T1,T2) | cpu | 4 | 10000 | 1.0879 | 9,192 | 1055 | 448.1 | 0 | +| blochsim | jacobian(T1,T2) | cpu | 4 | 100000 | 9.6565 | 10,356 | 4147 | 3540.4 | 0 | +| blochsim | jacobian(T1,T2) | cuda | 4 | 1 | 0.0156 | 64 | 1164 | 563.0 | 72 | +| blochsim | jacobian(T1,T2) | cuda | 4 | 10 | 0.0156 | 640 | 1197 | 595.8 | 54 | +| blochsim | jacobian(T1,T2) | cuda | 4 | 100 | 0.0159 | 6,282 | 1253 | 651.3 | 54 | +| blochsim | jacobian(T1,T2) | cuda | 4 | 1000 | 0.0158 | 63,392 | 1206 | 605.0 | 56 | +| blochsim | jacobian(T1,T2) | cuda | 4 | 10000 | 0.0459 | 217,764 | 1206 | 605.3 | 512 | +| blochsim | jacobian(T1,T2) | cuda | 4 | 100000 | 0.3813 | 262,295 | 1208 | 606.0 | 4642 | - BlochSimulators.jl n=1000: float32; real RF train, so the states stay real; max_state is a multiple of 32 by construction - BlochSimulators.jl n=10000: float32; real RF train, so the states stay real; max_state is a multiple of 32 by construction @@ -125,16 +125,16 @@ - KomaMRI.jl n=10: isochromat: 64 spins per tissue through one spoiler cycle, 640 spins in all - KomaMRI.jl n=100: isochromat: 64 spins per tissue through one spoiler cycle, 6400 spins in all - KomaMRI.jl n=1000: isochromat: 64 spins per tissue through one spoiler cycle, 64000 spins in all -- epgpy n=1: max_nstate matches the orders TorchSim keeps -- epgpy n=10: max_nstate matches the orders TorchSim keeps -- epgpy n=100: max_nstate matches the orders TorchSim keeps -- epgpy n=1000: max_nstate matches the orders TorchSim keeps -- epgpy n=10000: max_nstate matches the orders TorchSim keeps -- epgpy n=1: max_nstate matches the orders TorchSim keeps -- epgpy n=10: max_nstate matches the orders TorchSim keeps -- epgpy n=100: max_nstate matches the orders TorchSim keeps -- epgpy n=1000: max_nstate matches the orders TorchSim keeps -- epgpy n=10000: max_nstate matches the orders TorchSim keeps +- epgpy n=1: max_nstate matches the orders BlochSim keeps +- epgpy n=10: max_nstate matches the orders BlochSim keeps +- epgpy n=100: max_nstate matches the orders BlochSim keeps +- epgpy n=1000: max_nstate matches the orders BlochSim keeps +- epgpy n=10000: max_nstate matches the orders BlochSim keeps +- epgpy n=1: max_nstate matches the orders BlochSim keeps +- epgpy n=10: max_nstate matches the orders BlochSim keeps +- epgpy n=100: max_nstate matches the orders BlochSim keeps +- epgpy n=1000: max_nstate matches the orders BlochSim keeps +- epgpy n=10000: max_nstate matches the orders BlochSim keeps - sycomore n=1: threshold=1e-06; orders reached 11-11 - sycomore n=10: threshold=1e-06; orders reached 11-158 - sycomore n=100: threshold=1e-06; orders reached 11-158 diff --git a/benchmarks/results/validation.json b/benchmarks/results/validation.json index 8512a5c9..fe98e1c7 100644 --- a/benchmarks/results/validation.json +++ b/benchmarks/results/validation.json @@ -77,7 +77,7 @@ "nrmse": 0.03221091974573033 } ], - "TorchSim": [ + "BlochSim": [ { "T1_ms": 200.0, "T2_ms": 10.0, diff --git a/benchmarks/results/validation.txt b/benchmarks/results/validation.txt index 29306340..cbe27d04 100644 --- a/benchmarks/results/validation.txt +++ b/benchmarks/results/validation.txt @@ -8,9 +8,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 BlochSimulators.jl against sycomore, per tissue: T1 = 200 ms, T2 = 10 ms: max 1.019e-07, NRMSE 4.143e-07 @@ -28,7 +28,7 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json MRF-FISP, 500 repetitions, TR = 10.0 ms, inversion prepared. Reference: sycomore, every order kept, double precision. @@ -39,9 +39,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 KomaMRI.jl, 4 spins against sycomore, per tissue: T1 = 1000 ms, T2 = 80 ms: max 5.246e-02, NRMSE 1.115e-01 @@ -55,7 +55,7 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json MRF-FISP, 500 repetitions, TR = 10.0 ms, inversion prepared. Reference: sycomore, every order kept, double precision. @@ -66,9 +66,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 KomaMRI.jl, 8 spins against sycomore, per tissue: T1 = 1000 ms, T2 = 80 ms: max 3.116e-02, NRMSE 6.823e-02 @@ -82,7 +82,7 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json MRF-FISP, 500 repetitions, TR = 10.0 ms, inversion prepared. Reference: sycomore, every order kept, double precision. @@ -93,9 +93,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 KomaMRI.jl, 16 spins against sycomore, per tissue: T1 = 1000 ms, T2 = 80 ms: max 1.092e-02, NRMSE 2.211e-02 @@ -109,7 +109,7 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json MRF-FISP, 500 repetitions, TR = 10.0 ms, inversion prepared. Reference: sycomore, every order kept, double precision. @@ -120,9 +120,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 KomaMRI.jl, 32 spins against sycomore, per tissue: T1 = 1000 ms, T2 = 80 ms: max 1.225e-03, NRMSE 2.068e-03 @@ -136,7 +136,7 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json MRF-FISP, 500 repetitions, TR = 10.0 ms, inversion prepared. Reference: sycomore, every order kept, double precision. @@ -147,9 +147,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 KomaMRI.jl, 64 spins against sycomore, per tissue: T1 = 1000 ms, T2 = 80 ms: max 1.511e-05, NRMSE 1.352e-04 @@ -163,7 +163,7 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json MRF-FISP, 500 repetitions, TR = 10.0 ms, inversion prepared. Reference: sycomore, every order kept, double precision. @@ -174,9 +174,9 @@ Reference: sycomore, every order kept, double precision. 32 5.429e-03 3.10e-02 64 2.675e-03 1.53e-02 -TorchSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 +BlochSim on NVIDIA GeForce RTX 4060 Laptop GPU against its CPU kernel: max 1.006e-06, relative 5.75e-06 -TorchSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 +BlochSim against epgpy, both truncated to 32 orders: max 1.614e-03, relative 9.23e-03 KomaMRI.jl, 128 spins against sycomore, per tissue: T1 = 1000 ms, T2 = 80 ms: max 1.509e-05, NRMSE 1.321e-04 @@ -190,4 +190,4 @@ Per tissue, at 32 orders: T1 = 3000 ms, T2 = 300 ms: max 3.916e-04, NRMSE 2.288e-03 T1 = 4000 ms, T2 = 2000 ms: max 5.429e-03, NRMSE 3.221e-02 -written to /home/mcencini/pulserver-project/refcode/recon/torchsim/benchmarks/results/validation.json +written to /home/mcencini/pulserver-project/refcode/recon/blochsim/benchmarks/results/validation.json diff --git a/benchmarks/run_all.py b/benchmarks/run_all.py index 52b5ab4e..99ff16fe 100644 --- a/benchmarks/run_all.py +++ b/benchmarks/run_all.py @@ -39,7 +39,7 @@ # thousand atoms in seconds; KomaMRI carries `spins` isochromats per tissue and # is stopped a decade earlier because of it. SIZES = { - "torchsim": (1, 10, 100, 1_000, 10_000, 100_000), + "blochsim": (1, 10, 100, 1_000, 10_000, 100_000), "sycomore": (1, 10, 100, 1_000, 10_000), "epgpy": (1, 10, 100, 1_000, 10_000), "blochsimulators": (1, 10, 100, 1_000, 10_000, 100_000), @@ -52,7 +52,7 @@ # fit in the memory of a modest card, and past that what is measured is # whatever the run spills into rather than the kernel. DEVICE_SIZES = { - "torchsim": (1, 10, 100, 1_000, 10_000, 100_000), + "blochsim": (1, 10, 100, 1_000, 10_000, 100_000), "blochsimulators": (1, 10, 100, 1_000, 10_000, 100_000), "koma": (1, 10, 100, 1_000), } @@ -65,7 +65,7 @@ def available(backend: str, device: str) -> bool: return False if backend in ("blochsimulators", "koma"): return bool(julia()) - module = {"torchsim": "torchsim", "sycomore": "sycomore", "epgpy": "epgpy"}[backend] + module = {"blochsim": "blochsim", "sycomore": "sycomore", "epgpy": "epgpy"}[backend] from importlib.util import find_spec try: @@ -189,14 +189,14 @@ def main() -> None: default = DEVICE_SIZES[backend] if device != "cpu" else SIZES[backend] sizes = [n for n in (chosen or default) if n <= limit] - if backend == "torchsim": + if backend == "blochsim": for atoms in sizes: for mode in ( ("forward",) if args.forward_only else ("forward", "jacobian") ): python_run( - "bench_torchsim.py", - f"torchsim-{mode}-{atoms}{suffix}", + "bench_blochsim.py", + f"blochsim-{mode}-{atoms}{suffix}", atoms=atoms, mode=mode, device=device, @@ -206,8 +206,8 @@ def main() -> None: if args.forward_only: continue python_run( - "bench_torchsim.py", - f"torchsim-jacobian1-{atoms}{suffix}", + "bench_blochsim.py", + f"blochsim-jacobian1-{atoms}{suffix}", atoms=atoms, mode="jacobian", diff="T1", @@ -221,8 +221,8 @@ def main() -> None: # the pool; on a card there is no such pair to take. for atoms in [n for n in sizes if n >= 1_000]: python_run( - "bench_torchsim.py", - f"torchsim-forward-{atoms}-{device}-t1", + "bench_blochsim.py", + f"blochsim-forward-{atoms}-{device}-t1", atoms=atoms, mode="forward", device=device, diff --git a/benchmarks/setup.sh b/benchmarks/setup.sh index 742fab84..d78b8a31 100644 --- a/benchmarks/setup.sh +++ b/benchmarks/setup.sh @@ -8,7 +8,7 @@ # # What it makes, all under $PREFIX (benchmarks/.env by default): # -# .env/venv a virtual environment with PyTorch, TorchSim, epgpy and +# .env/venv a virtual environment with PyTorch, BlochSim, epgpy and # sycomore in it # .env/micromamba a package manager, only if conda-forge is needed # .env/julia a Julia, only if there is not one on the path already @@ -54,7 +54,7 @@ setup_python() { say "python environment in $VENV" # $PYTHON says which interpreter to build on. It matters: an interpreter that # ships a C++ runtime of its own -- a conda one does -- loads that runtime - # ahead of the system's, and TorchSim's kernels are compiled against the + # ahead of the system's, and BlochSim's kernels are compiled against the # system's. The extension then fails to load with a missing GLIBCXX version, # every fused kernel is reported absent, and nothing here can run. [ -d "$VENV" ] || "${PYTHON:-python3}" -m venv "$VENV" @@ -67,7 +67,7 @@ setup_python() { # pip install torch --index-url https://download.pytorch.org/whl/cpu "$VENV/bin/pip" install --quiet torch - say "TorchSim, from this checkout" + say "BlochSim, from this checkout" "$VENV/bin/pip" install --quiet -e "$ROOT" say "epgpy" @@ -91,7 +91,7 @@ setup_python() { "$VENV/bin/pip" install --quiet matplotlib "$VENV/bin/python" - <<'PY' -for name in ("torch", "torchsim", "epgpy", "sycomore", "matplotlib"): +for name in ("torch", "blochsim", "epgpy", "sycomore", "matplotlib"): try: __import__(name) print(f" {name}: ok") diff --git a/benchmarks/validate.py b/benchmarks/validate.py index 51bc13bc..ba49e81c 100644 --- a/benchmarks/validate.py +++ b/benchmarks/validate.py @@ -1,4 +1,4 @@ -"""Whether TorchSim, sycomore and epgpy compute the same fingerprint. +"""Whether BlochSim, sycomore and epgpy compute the same fingerprint. Three independent implementations of the same model -- a fused C++ or Triton state machine over a packed event stream, a C++ EPG library driven one tissue @@ -6,7 +6,7 @@ to the precision the coarsest of them carries. This says by how much they do, over a tissue grid, and shows what truncating the configuration orders costs. -Where there is a card, TorchSim's two kernels are held against each other as +Where there is a card, BlochSim's two kernels are held against each other as well: they are separate implementations of one recursion, and a run placed on a card has to answer what the same run on the CPU answers. @@ -67,7 +67,7 @@ def epgpy_signal( return np.asarray(epg.simulate(sequence, max_nstate=states)).T -def torchsim_signal( +def blochsim_signal( T1: np.ndarray, T2: np.ndarray, flip: np.ndarray, @@ -78,7 +78,7 @@ def torchsim_signal( """The same train on the fused state machine, at a given order count.""" import torch - from torchsim.simulators import MRFSimulator + from blochsim.simulators import MRFSimulator where = torch.device(device) sequence = MRFSimulator( @@ -152,7 +152,7 @@ def main() -> None: print("Reference: sycomore, every order kept, double precision.\n") print(f"{'states':>7s} {'max |difference|':>17s} {'relative':>10s}") for states in (4, 8, 16, 32, 64): - signal = torchsim_signal(T1, T2, flip, TR, states) + signal = blochsim_signal(T1, T2, flip, TR, states) # The two differ by the constant phase each writes its echo with. turn = np.exp(1j * np.angle(np.sum(reference * np.conj(signal)))) difference = np.abs(reference - signal * turn) @@ -168,11 +168,11 @@ def main() -> None: # The two kernels are separate implementations of the same recursion, # so this is a comparison of the C++ one against the Triton one rather # than a check that a tensor made the trip. - on_cpu = torchsim_signal(T1, T2, flip, TR, arguments.states) - on_card = torchsim_signal(T1, T2, flip, TR, arguments.states, device="cuda") + on_cpu = blochsim_signal(T1, T2, flip, TR, arguments.states) + on_card = blochsim_signal(T1, T2, flip, TR, arguments.states, device="cuda") difference = np.abs(on_cpu - on_card) print( - f"\nTorchSim on {torch.cuda.get_device_name(0)} against its CPU kernel: " + f"\nBlochSim on {torch.cuda.get_device_name(0)} against its CPU kernel: " f"max {difference.max():.3e}, " f"relative {difference.max() / np.abs(on_cpu).max():.2e}" ) @@ -187,11 +187,11 @@ def main() -> None: except ImportError: other = None if other is not None: - signal = torchsim_signal(T1, T2, flip, TR, arguments.states) + signal = blochsim_signal(T1, T2, flip, TR, arguments.states) turn = np.exp(1j * np.angle(np.sum(other * np.conj(signal)))) difference = np.abs(other - signal * turn) print( - f"\nTorchSim against epgpy, both truncated to {arguments.states} orders: " + f"\nBlochSim against epgpy, both truncated to {arguments.states} orders: " f"max {difference.max():.3e}, " f"relative {difference.max() / np.abs(other).max():.2e}" ) @@ -239,7 +239,7 @@ def main() -> None: ) print(f"\nPer tissue, at {arguments.states} orders:") - signal = torchsim_signal(T1, T2, flip, TR, arguments.states) + signal = blochsim_signal(T1, T2, flip, TR, arguments.states) turn = np.exp(1j * np.angle(np.sum(reference * np.conj(signal)))) for row, (a, b) in enumerate(TISSUES): difference = np.abs(reference[row] - signal[row] * turn) @@ -248,7 +248,7 @@ def main() -> None: f"max {difference.max():.3e}, " f"NRMSE {np.sqrt(np.mean(difference**2)) / np.sqrt(np.mean(np.abs(reference[row]) ** 2)):.3e}" ) - record.setdefault("against", {}).setdefault("TorchSim", []).append( + record.setdefault("against", {}).setdefault("BlochSim", []).append( { "T1_ms": a, "T2_ms": b, diff --git a/docs/_static/blochsim-logo-dark.svg b/docs/_static/blochsim-logo-dark.svg new file mode 100644 index 00000000..155c160e --- /dev/null +++ b/docs/_static/blochsim-logo-dark.svg @@ -0,0 +1,19 @@ + +blochsim +A Bloch sphere with a precessing magnetization vector, followed by the wordmark blochsim + + + + + + + + + + + + + + +blochsim + diff --git a/docs/_static/blochsim-logo.svg b/docs/_static/blochsim-logo.svg new file mode 100644 index 00000000..4428eb30 --- /dev/null +++ b/docs/_static/blochsim-logo.svg @@ -0,0 +1,19 @@ + +blochsim +A Bloch sphere with a precessing magnetization vector, followed by the wordmark blochsim + + + + + + + + + + + + + + +blochsim + diff --git a/docs/_static/blochsim-mark-dark.svg b/docs/_static/blochsim-mark-dark.svg new file mode 100644 index 00000000..3f9b3e12 --- /dev/null +++ b/docs/_static/blochsim-mark-dark.svg @@ -0,0 +1,18 @@ + +blochsim +A Bloch sphere with the magnetization vector at the end of its precessing excitation path + + + + + + + + + + + + + + + diff --git a/docs/_static/blochsim-mark.svg b/docs/_static/blochsim-mark.svg new file mode 100644 index 00000000..548e1d9d --- /dev/null +++ b/docs/_static/blochsim-mark.svg @@ -0,0 +1,18 @@ + +blochsim +A Bloch sphere with the magnetization vector at the end of its precessing excitation path + + + + + + + + + + + + + + + diff --git a/docs/api/estimators.md b/docs/api/estimators.md index a45a0c46..efcfedc5 100644 --- a/docs/api/estimators.md +++ b/docs/api/estimators.md @@ -1,7 +1,7 @@ # Parameter estimation ```{eval-rst} -.. currentmodule:: torchsim +.. currentmodule:: blochsim ``` Estimating tissue properties from a measured volume. An estimator is made from diff --git a/docs/api/execution.md b/docs/api/execution.md index e230a5eb..3f5e9445 100644 --- a/docs/api/execution.md +++ b/docs/api/execution.md @@ -1,7 +1,7 @@ # Running a simulation ```{eval-rst} -.. currentmodule:: torchsim +.. currentmodule:: blochsim ``` The differentiable state machine a sequence description is run on, what the @@ -18,7 +18,7 @@ cards as there are -- and it is stated once, around the call. The engine runs a {class}`SequenceDescription` directly, which is what you reach for when a builder or an interpreter handed you a description rather -than a simulator. A {class}`~torchsim.model.Simulator` wraps it and is how a +than a simulator. A {class}`~blochsim.model.Simulator` wraps it and is how a sequence is usually written; see {doc}`model`. ```{eval-rst} diff --git a/docs/api/index.md b/docs/api/index.md index f12a0317..5827b3d3 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,6 +1,6 @@ # API -TorchSim is organized around one idea: a **signal model** is the only thing a +BlochSim is organized around one idea: a **signal model** is the only thing a sequence has to supply, and everything else -- differentiation, execution across devices, estimation, reconstruction, design -- is written once against that interface. @@ -13,7 +13,7 @@ The pages below follow the subpackages. order they are played in. {doc}`simulators` -: The sequences that ship with TorchSim, as classes and as one-call +: The sequences that ship with BlochSim, as classes and as one-call functional wrappers. {doc}`sequence` diff --git a/docs/api/model.md b/docs/api/model.md index 89717b04..d5ef14c8 100644 --- a/docs/api/model.md +++ b/docs/api/model.md @@ -1,7 +1,7 @@ # Signal models ```{eval-rst} -.. currentmodule:: torchsim.model +.. currentmodule:: blochsim.model ``` **There is one user-facing base class: {class}`Simulator`.** @@ -56,7 +56,7 @@ Nothing is declared about the tissue. Every property a voxel can have may be given to any simulator, and giving one is what turns its term on. A sequence that came from somewhere else is read through those same handlers. -{func}`~torchsim.sequence.read_mrd_description` decodes the description +{func}`~blochsim.sequence.read_mrd_description` decodes the description carried ahead of the acquisitions on an MRD stream, and {meth}`~Simulator.from_description` turns one of those descriptions into the chosen simulator. {meth}`~Simulator.from_pulseq` does the same from a Pulseq @@ -68,8 +68,8 @@ Implement {meth}`~Simulator.evaluate` instead when the signal has a closed form -- a mono-exponential decay, an inversion-recovery curve, an Ernst steady state. There is nothing to play and no state to carry, so there is no `layout` and the `SpinPhysics` carries only the property declaration. -{class}`~torchsim.simulators.SPGRSimulator` is written this way, and -{class}`~torchsim.simulators.MP2RAGESimulator` carries both: the closed form a +{class}`~blochsim.simulators.SPGRSimulator` is written this way, and +{class}`~blochsim.simulators.MP2RAGESimulator` carries both: the closed form a lookup table is built from, and the layout a description arriving from a scanner is compared against. @@ -132,12 +132,12 @@ the slots itself: | {data}`REFOCUSED` | the sample at the echo centre | crushed | ```{eval-rst} -.. autodata:: torchsim.model.SPOILED +.. autodata:: blochsim.model.SPOILED :no-value: -.. autodata:: torchsim.model.UNBALANCED +.. autodata:: blochsim.model.UNBALANCED :no-value: -.. autodata:: torchsim.model.BALANCED +.. autodata:: blochsim.model.BALANCED :no-value: -.. autodata:: torchsim.model.REFOCUSED +.. autodata:: blochsim.model.REFOCUSED :no-value: ``` diff --git a/docs/api/optim.md b/docs/api/optim.md index af5b07fb..6259af72 100644 --- a/docs/api/optim.md +++ b/docs/api/optim.md @@ -1,7 +1,7 @@ # Sequence design ```{eval-rst} -.. currentmodule:: torchsim +.. currentmodule:: blochsim ``` Choosing a sequence's parameters by minimizing a cost you write. The diff --git a/docs/api/recon.md b/docs/api/recon.md index 25e63e5d..3e3f726e 100644 --- a/docs/api/recon.md +++ b/docs/api/recon.md @@ -1,7 +1,7 @@ # Model-based reconstruction ```{eval-rst} -.. currentmodule:: torchsim +.. currentmodule:: blochsim ``` Solving for parameter maps straight from k-space, with the signal model inside diff --git a/docs/api/sequence.md b/docs/api/sequence.md index 3a637463..1448999d 100644 --- a/docs/api/sequence.md +++ b/docs/api/sequence.md @@ -1,7 +1,7 @@ # Sequences ```{eval-rst} -.. currentmodule:: torchsim +.. currentmodule:: blochsim ``` Device-agnostic description of an acquisition, shared by the interpreter, the @@ -17,7 +17,7 @@ and the temporal basis a run spans is on {doc}`recon`. The modules a sequence is assembled from. An operator is a Python function returning the events it plays and how long it holds the timeline, and `@` -composes two into one -- so a preparation or a readout TorchSim does not ship +composes two into one -- so a preparation or a readout BlochSim does not ship reaches the fused kernels with no change to them. The five readouts differ only in what they play around the sample, which is @@ -78,10 +78,10 @@ arriving from a scanner is read into. {meth}`SequenceDescription.from_pulseq` reads one out of a Pulseq `.seq` file or a sequence a design holds in memory, {func}`read_mrd_description` reads the ones an MRD stream carries ahead of its first acquisition, -and {meth}`~torchsim.model.Simulator.from_description` runs any of them. +and {meth}`~blochsim.model.Simulator.from_description` runs any of them. Reading a `.seq` file needs pypulseq, which parses the format and computes the -gradient trajectory the echo is found on: `pip install torchsim[pulseq]`. +gradient trajectory the echo is found on: `pip install blochsim[pulseq]`. Nothing else in the package imports it. ```{eval-rst} diff --git a/docs/api/simulators.md b/docs/api/simulators.md index 1a63fb2f..c76ea769 100644 --- a/docs/api/simulators.md +++ b/docs/api/simulators.md @@ -1,7 +1,7 @@ # Simulators -The sequences that ship with TorchSim. A constructor takes the keywords -{meth}`~torchsim.model.Simulator.simulate` takes and fixes them, so a +The sequences that ship with BlochSim. A constructor takes the keywords +{meth}`~blochsim.model.Simulator.simulate` takes and fixes them, so a sequence and the tissue it is being asked about are written down together and what is left to give at the call is whatever is actually varying. @@ -14,22 +14,22 @@ Signals that have an analytic expression, evaluated in one pass. :toctree: ../generated :nosignatures: - torchsim.simulators.bSSFPSimulator - torchsim.simulators.SPGRSimulator - torchsim.simulators.MP2RAGESimulator - torchsim.simulators.InversionRecoverySimulator - torchsim.simulators.MultiEchoSimulator - torchsim.simulators.DoubleAngleSimulator - torchsim.simulators.SpinEchoSimulator - torchsim.simulators.LookLockerSimulator - torchsim.simulators.MOLLISimulator - torchsim.simulators.IRbSSFPSimulator - torchsim.simulators.HSFPSimulator - torchsim.simulators.MultiGradientEchoSimulator - torchsim.simulators.IRMultiGradientEchoSimulator - torchsim.simulators.DiffusionSimulator - torchsim.simulators.LorentzianSimulator - torchsim.simulators.ASLSimulator + blochsim.simulators.bSSFPSimulator + blochsim.simulators.SPGRSimulator + blochsim.simulators.MP2RAGESimulator + blochsim.simulators.InversionRecoverySimulator + blochsim.simulators.MultiEchoSimulator + blochsim.simulators.DoubleAngleSimulator + blochsim.simulators.SpinEchoSimulator + blochsim.simulators.LookLockerSimulator + blochsim.simulators.MOLLISimulator + blochsim.simulators.IRbSSFPSimulator + blochsim.simulators.HSFPSimulator + blochsim.simulators.MultiGradientEchoSimulator + blochsim.simulators.IRMultiGradientEchoSimulator + blochsim.simulators.DiffusionSimulator + blochsim.simulators.LorentzianSimulator + blochsim.simulators.ASLSimulator ``` ## State machine @@ -41,16 +41,16 @@ Trains that have to be played out, run on the extended phase graph engine. :toctree: ../generated :nosignatures: - torchsim.simulators.FSESimulator - torchsim.simulators.HyperechoSimulator - torchsim.simulators.StimulatedEchoSimulator - torchsim.simulators.MPRAGESimulator - torchsim.simulators.MPnRAGESimulator - torchsim.simulators.MRFSimulator - torchsim.simulators.FLASHSimulator - torchsim.simulators.TrueFISPSimulator - torchsim.simulators.fmSSFPSimulator - torchsim.simulators.CESTSimulator + blochsim.simulators.FSESimulator + blochsim.simulators.HyperechoSimulator + blochsim.simulators.StimulatedEchoSimulator + blochsim.simulators.MPRAGESimulator + blochsim.simulators.MPnRAGESimulator + blochsim.simulators.MRFSimulator + blochsim.simulators.FLASHSimulator + blochsim.simulators.TrueFISPSimulator + blochsim.simulators.fmSSFPSimulator + blochsim.simulators.CESTSimulator ``` ## Functional wrappers @@ -58,8 +58,8 @@ Trains that have to be played out, run on the extended phase graph engine. The `*_sim` functions are convenience calls for a subset of the shipped simulators: protocol and tissue go into one function call, with an optional Jacobian. They are not a second extension API. New sequence families are -implemented as {class}`~torchsim.model.Simulator` subclasses, so they can use -both an offline {meth}`~torchsim.model.Simulator.layout` and the same handlers +implemented as {class}`~blochsim.model.Simulator` subclasses, so they can use +both an offline {meth}`~blochsim.model.Simulator.layout` and the same handlers when a Pulseq/MRD description arrives from a scanner. Prefer the simulator classes whenever the same sequence is reused, bound to a @@ -73,8 +73,8 @@ from a description. The wrappers remain useful for compact one-off calls. :toctree: ../generated :nosignatures: - torchsim.bssfp_sim - torchsim.spgr_sim + blochsim.bssfp_sim + blochsim.spgr_sim ``` ### Iterative @@ -84,9 +84,9 @@ from a description. The wrappers remain useful for compact one-off calls. :toctree: ../generated :nosignatures: - torchsim.fse_sim - torchsim.mprage_sim - torchsim.mp2rage_sim - torchsim.mpnrage_sim - torchsim.mrf_sim + blochsim.fse_sim + blochsim.mprage_sim + blochsim.mp2rage_sim + blochsim.mpnrage_sim + blochsim.mrf_sim ``` diff --git a/docs/binder/environment.yml b/docs/binder/environment.yml index 297f0188..14b4beb8 100644 --- a/docs/binder/environment.yml +++ b/docs/binder/environment.yml @@ -1,4 +1,4 @@ -name: torchsim +name: blochsim channels: - conda-forge dependencies: @@ -7,4 +7,4 @@ dependencies: - pip: # The gallery notebooks import the phantom, coil, Fourier-encoding and # solver packages while they run, which is the ``examples`` extra. - - torchsim[examples] + - blochsim[examples] diff --git a/docs/conf.py b/docs/conf.py index c66b7510..655b4e45 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ def _cores_allowed() -> int: # -- Project information ----------------------------------------------------- -project = "torchsim" -copyright = "2024, TorchSim Contributors" -author = "TorchSim Contributors" +project = "blochsim" +copyright = "2024, BlochSim Contributors" +author = "BlochSim Contributors" # -- General configuration --------------------------------------------------- @@ -142,10 +142,10 @@ def _cores_allowed() -> int: #: Which of the published versions this build is: ``latest`` for the #: development branch, the tag for a release. One directory of the site per #: version, and the switcher marks the one being read. -DOCS_VERSION = os.environ.get("TORCHSIM_DOCS_VERSION", "latest") +DOCS_VERSION = os.environ.get("BLOCHSIM_DOCS_VERSION", "latest") #: Where the pages are served from. -PAGES_URL = "https://pulserver.github.io/torchsim" +PAGES_URL = "https://pulserver.github.io/blochsim" # -- Options for Sphinx Gallery ---------------------------------------------- @@ -189,7 +189,7 @@ def _missing_imports(script: Path) -> list[str]: #: example runs where everything it imports is installed, so an environment #: holding the ``examples`` extra executes the whole gallery -- which is what #: the published pages are built with -- and one holding ``doc`` alone -#: executes what needs nothing but TorchSim, which is what a branch is +#: executes what needs nothing but BlochSim, which is what a branch is #: checked with while it waits. UNRUNNABLE = { script: missing @@ -207,9 +207,9 @@ def _missing_imports(script: Path) -> list[str]: ) sphinx_gallery_conf = { - "doc_module": "torchsim", + "doc_module": "blochsim", "backreferences_dir": "generated/gallery_backreferences", - "reference_url": {"torchsim": None}, + "reference_url": {"blochsim": None}, "examples_dirs": ["../examples/"], "gallery_dirs": ["generated/autoexamples"], "filename_pattern": EXECUTED_PATTERN, @@ -223,7 +223,7 @@ def _missing_imports(script: Path) -> list[str]: "reset_modules": ("figure_style.reset", "seaborn"), "binder": { "org": "pulserver", - "repo": "torchsim", + "repo": "blochsim", "branch": "gh-pages", "binderhub_url": "https://mybinder.org", "dependencies": [ @@ -258,7 +258,7 @@ def _missing_imports(script: Path) -> list[str]: html_static_path = ["_static"] html_css_files = ["custom.css"] html_theme_options = { - "repository_url": "https://github.com/pulserver/torchsim", + "repository_url": "https://github.com/pulserver/blochsim", "use_repository_button": True, "use_issues_button": True, "use_edit_page_button": True, @@ -275,6 +275,11 @@ def _missing_imports(script: Path) -> list[str]: "show_navbar_depth": 1, "max_navbar_depth": 3, "navbar_persistent": [], + "logo": { + "image_light": "_static/blochsim-mark.svg", + "image_dark": "_static/blochsim-mark-dark.svg", + "alt_text": "blochsim", + }, } #: The theme's own sidebar, with the version switcher under the title. @@ -291,9 +296,8 @@ def _missing_imports(script: Path) -> list[str]: #: One canonical address per page, under the version it belongs to. html_baseurl = f"{PAGES_URL}/{DOCS_VERSION}/" -# html_logo = "_static/logos/mri-nufft.png" -# html_favicon = "_static/logos/mri-nufft-icon.png" -html_title = "TorchSim Documentation" +html_favicon = "_static/blochsim-mark.svg" +html_title = "BlochSim Documentation" def _skip_undocumented_specials(app, what, name, obj, skip, options): @@ -374,7 +378,7 @@ def _unflagged(content: str) -> str: def _draw_explanation_figures(app) -> None: - """Render the explanation pages' figures with the TorchSim being built. + """Render the explanation pages' figures with the BlochSim being built. They are simulated rather than drawn once and checked in, so a figure on those pages cannot outlive the behaviour it shows. diff --git a/docs/developer_guide.md b/docs/developer_guide.md index 49367cbb..ae089677 100644 --- a/docs/developer_guide.md +++ b/docs/developer_guide.md @@ -1,6 +1,6 @@ # Developer Guide -How to set up to change TorchSim: what has to be on the machine, how to +How to set up to change BlochSim: what has to be on the machine, how to install the source so your edits take effect, the style the code is written in, how to run the tests and build these pages, and how a pull request is opened. @@ -16,8 +16,8 @@ Beyond what the {doc}`user_guide` asks of everyone -- an isolated environment and a PyTorch build chosen for your hardware -- working on the source needs: **A C++17 compiler.** The CPU state machine and the PERK kernel are C++ -extensions built from `src/torchsim/_epg_cpu.cpp` and -`src/torchsim/_perk_cpu.cpp`. An install from source compiles them, and +extensions built from `src/blochsim/_epg_cpu.cpp` and +`src/blochsim/_perk_cpu.cpp`. An install from source compiles them, and there is no Python fallback to fall back to. ::::{tab-set} @@ -49,7 +49,7 @@ the compiler is on the path. :::: -**Git**, and a fork of https://github.com/pulserver/torchsim if you intend to +**Git**, and a fork of https://github.com/pulserver/blochsim if you intend to open a pull request. **An NVIDIA card, if you want to touch the GPU kernels.** They are Triton, @@ -60,8 +60,8 @@ card runs it for real. ## Installing for development ```sh -git clone https://github.com/pulserver/torchsim -cd torchsim +git clone https://github.com/pulserver/blochsim +cd blochsim pip install -e ".[dev]" pre-commit install ``` @@ -77,7 +77,7 @@ puts the hooks *available*, and this is what puts them *in effect*. See {ref}`dev-style` for what they check and how to step around one. An editable install puts the Python sources on the path, so an edit under -`src/torchsim` takes effect on the next import with nothing to rebuild. The +`src/blochsim` takes effect on the next import with nothing to rebuild. The C++ extensions are different -- they are compiled artifacts: ```sh @@ -92,29 +92,29 @@ the one you just built: ```sh pip install -e ".[dev]" ; echo "exit: $?" -python -c "import torchsim._epg_cpu as k; print(k.__file__)" +python -c "import blochsim._epg_cpu as k; print(k.__file__)" ``` ### Where the source lives -`src/torchsim/sequence/` +`src/blochsim/sequence/` : The description an acquisition is assembled from -- events, operators, builders -- and the dispatch that turns one into a kernel launch. The Triton kernels are `_epg_triton.py`; the shared parameter ABI, which the Python dispatch, the C++ extension and the Triton kernels all read, is `_parameters.py`. -`src/torchsim/model/` +`src/blochsim/model/` : What a signal model is: the physics, the simulator that orders its events, and the binding that resolves a protocol's structure once and rebinds its values per call. -`src/torchsim/_epg_cpu.cpp`, `src/torchsim/_perk_cpu.cpp` +`src/blochsim/_epg_cpu.cpp`, `src/blochsim/_perk_cpu.cpp` : The CPU kernels. Every path the GPU has -- forward, forward-mode, adjoint, forward-over-reverse, the real-subspace specialization, the pool models -- exists here too, and the two agree to float32 round-off. -`src/torchsim/simulators/`, `estimators/`, `recon/`, `optim/` +`src/blochsim/simulators/`, `estimators/`, `recon/`, `optim/` : The sequences that ship, and what is built on top of them. `tests/`, `examples/`, `docs/` @@ -151,7 +151,7 @@ is a second or two. ```sh pre-commit run --all-files # the whole tree, which is what CI does pre-commit run ruff-format # one hook, on the staged files -pre-commit run --files src/torchsim/model/_signal.py +pre-commit run --files src/blochsim/model/_signal.py pre-commit autoupdate # move the pinned hook versions forward ``` @@ -221,7 +221,7 @@ test whose name states the invariant -- those cannot go stale silently, and a stale comment actively misleads the next reader. When you are tempted to explain *why not the other way*, write a test instead. -The same applies to these pages. The documentation describes what TorchSim +The same applies to these pages. The documentation describes what BlochSim does and why that is right on its own terms; it is not a changelog. **Units are public at the edges and internal underneath.** A caller writes @@ -268,7 +268,7 @@ number entirely. Run the whole suite at natural boundaries rather than after every edit. When you change physics, add the test that pins it against something outside -TorchSim: a closed form, a published figure, or an isochromat summation you +BlochSim: a closed form, a published figure, or an isochromat summation you write in the test itself. The `tests/epg` files are written that way, and each states its invariant in its module docstring. @@ -277,15 +277,15 @@ each states its invariant in its module docstring. ```sh bash scripts/build_docs.sh # incremental bash scripts/build_docs.sh --clean # re-execute every example -PYTHON_BIN=~/envs/torchsim/bin/python bash scripts/build_docs.sh +PYTHON_BIN=~/envs/blochsim/bin/python bash scripts/build_docs.sh ``` -The script checks that the interpreter it is given can import TorchSim and the +The script checks that the interpreter it is given can import BlochSim and the documentation extensions, then builds into `docs/build/html`. Two things happen during the build that make it slower than a plain Sphinx run and are the point of it: **sphinx-gallery executes every example**, and **the figures of the explanation pages are re-rendered** by -`docs/explanation_figures.py`, which simulates them with the TorchSim in +`docs/explanation_figures.py`, which simulates them with the BlochSim in your working tree. A figure on those pages is therefore never older than the code it illustrates. Read them: @@ -308,7 +308,7 @@ docstring becomes the page's introduction. refactor and a fix in the same diff cost the reviewer more than they save you. 2. **Make the change, with a test that fails without it.** For a physics - change that test is against something outside TorchSim, as above. + change that test is against something outside BlochSim, as above. 3. **Run what CI runs**, so you find out here rather than there: ```sh @@ -330,6 +330,6 @@ docstring becomes the page's introduction. changed. Everything here happens under the {doc}`misc/code_of_conduct`, which applies -to issues, discussions, pull requests and reviews alike. TorchSim is released +to issues, discussions, pull requests and reviews alike. BlochSim is released under the {doc}`misc/license`, and a contribution is released under the same terms; {doc}`misc/contributors` is generated from the repository history. diff --git a/docs/explanation_figures.py b/docs/explanation_figures.py index b2e25d3c..9d7da181 100644 --- a/docs/explanation_figures.py +++ b/docs/explanation_figures.py @@ -1,4 +1,4 @@ -"""Figures for the explanation pages, drawn from the TorchSim in the tree. +"""Figures for the explanation pages, drawn from the BlochSim in the tree. Every figure is a function returning a Matplotlib figure, registered in :data:`FIGURES` under the file stem the pages reference it by. The Sphinx @@ -26,9 +26,9 @@ import torch from figure_style import FOREGROUND, STYLE -from torchsim.model import Simulator -from torchsim.sequence import Delay, EventAction, Excitation, SPGRReadout -from torchsim.simulators import FSESimulator, MRFSimulator, SPGRSimulator +from blochsim.model import Simulator +from blochsim.sequence import Delay, EventAction, Excitation, SPGRReadout +from blochsim.simulators import FSESimulator, MRFSimulator, SPGRSimulator #: The example sequence the description figures are read from. SEQ_FILE = Path(__file__).resolve().parent.parent / "examples/01-framework/fse.seq" @@ -383,7 +383,7 @@ def phase_graph(): xlabel="time [ms]", ylabel="|signal|", ylim=(0, 1.05 * signal.max()), - title="what TorchSim records there", + title="what BlochSim records there", ) return figure @@ -1227,7 +1227,7 @@ def _example_sequence(): def description_blocks(): """A Pulseq block table above the event stream it is read as.""" - from torchsim.sequence import EventType, SequenceDescription + from blochsim.sequence import EventType, SequenceDescription sequence = _example_sequence() described = SequenceDescription.from_pulseq(SEQ_FILE) @@ -1311,7 +1311,7 @@ def description_blocks(): def description_echo(): """Where each readout sits in k-space, and which one bears the echo.""" - from torchsim.sequence import SequenceDescription + from blochsim.sequence import SequenceDescription sequence = _example_sequence() described = SequenceDescription.from_pulseq(SEQ_FILE) diff --git a/docs/explanations/description.md b/docs/explanations/description.md index 526dc405..1cbc4b2c 100644 --- a/docs/explanations/description.md +++ b/docs/explanations/description.md @@ -3,15 +3,15 @@ ```{admonition} TL;DR :class: tldr -- TorchSim consumes a compact sequence description made of RF, ADC and wait +- BlochSim consumes a compact sequence description made of RF, ADC and wait events plus reusable RF definitions. - Pulseq files and scanner MRD streams are both decoded into this same description. -- The description states what was played; a {class}`~torchsim.model.Simulator` +- The description states what was played; a {class}`~blochsim.model.Simulator` supplies the handlers that decide how those commands affect the EPG state. ``` -A sequence reaches TorchSim as an **event stream**: one repetition's worth of +A sequence reaches BlochSim as an **event stream**: one repetition's worth of events, each with a timestamp and the few numbers its kind carries. This page is what that stream holds, how it is read out of a Pulseq file, and how a scanner carries it. The physics the events drive is {doc}`epg`; how the kernels @@ -80,7 +80,7 @@ simulation would otherwise silently stand for whichever came first. Such a file says which with a `TRRef` definition, or the caller passes `tr_index`. ```python -from torchsim.simulators import FSESimulator +from blochsim.simulators import FSESimulator # The tissue is given; the echo spacing, the train length, the refocusing # angle and the pulse shapes are read. @@ -89,7 +89,7 @@ signal = train.simulate(T1=830.0, T2=80.0) ``` Reading a file needs `pypulseq`, which parses the format and computes the -gradient trajectory: `pip install torchsim[pulseq]`. +gradient trajectory: `pip install blochsim[pulseq]`. ## Pulse shapes @@ -157,8 +157,8 @@ A scanner does not send a file. Pulserver's reconstruction proxy reads the sequence file a series was played from and sends its description on the MRD stream, after the XML header and before the first acquisition, as one `TEXT` message holding a description per file of the sequence chain. -{func}`~torchsim.sequence.read_mrd_description` decodes it into the same object -{meth}`~torchsim.SequenceDescription.from_pulseq` builds, so a simulation driven +{func}`~blochsim.sequence.read_mrd_description` decodes it into the same object +{meth}`~blochsim.SequenceDescription.from_pulseq` builds, so a simulation driven from a design script and one driven from a running scan read one derivation, not two. Which model the rows drive is chosen by the reader. @@ -187,7 +187,7 @@ they are. Naming the simulator is what says which of those the arriving events are to be read as, and it is the only thing a caller chooses: ```python -from torchsim.simulators import FSESimulator, MRFSimulator +from blochsim.simulators import FSESimulator, MRFSimulator # The same file, read as a refocused train and as an unbalanced one. refocused = FSESimulator.from_pulseq("scan.seq") diff --git a/docs/explanations/epg.md b/docs/explanations/epg.md index 7f87bd52..70ee5fb7 100644 --- a/docs/explanations/epg.md +++ b/docs/explanations/epg.md @@ -7,7 +7,7 @@ indexed by dephasing order. - RF pulses mix state families, gradients shift transverse orders, and relaxation/evolution acts between events. -- TorchSim extends that state machine with off-resonance, diffusion, flow and +- BlochSim extends that state machine with off-resonance, diffusion, flow and exchange while retaining the same sequence-level abstraction. ``` @@ -17,7 +17,7 @@ fingerprinting schedule: in each, what you sample is not one decaying magnetization but a sum over routes through the pulses and the gradients, each of which has spent a different amount of time transverse and a different amount parked along the field. The extended phase graph (EPG) is the accounting that -keeps those routes straight, and it is what TorchSim evaluates. Weigel [^2] is +keeps those routes straight, and it is what BlochSim evaluates. Weigel [^2] is the introduction to read alongside this one. The magnetization is decomposed into configuration states, and three operators @@ -172,7 +172,7 @@ makes rephasing come out right and is the single place a shift is not just a memory move. ``` -In TorchSim the shift is what an operator such as `Dephase` plays, and one +In BlochSim the shift is what an operator such as `Dephase` plays, and one shift is one crusher or one unbalanced readout gradient. Ideal spoiling -- `Spoil` -- is the other option: discard every transverse order instead of winding it on, which is what a spoiled gradient echo assumes its spoiler and @@ -208,11 +208,11 @@ crossing at that moment. ```{figure} /generated/figures/phase_graph.png :width: 100% -:alt: A phase graph of a refocused train above the echo amplitudes TorchSim computes. +:alt: A phase graph of a refocused train above the echo amplitudes BlochSim computes. Above, the phase graph of six refocusing pulses: solid lines are transverse pathways, dotted lines are magnetization parked along $z$ between -pulses, dashed verticals are the pulses. Below, what TorchSim records at +pulses, dashed verticals are the pulses. Below, what BlochSim records at those crossings. From the fourth pulse on, the sampled signal contains direct spin echoes and stimulated echoes at once -- pathways that spent one or more intervals longitudinal, and so were spared $T_2$ decay while @@ -248,7 +248,7 @@ assuming the decay. A sequence populates a bounded set of orders: each shift moves the ladder by one, so after $n$ shifts nothing beyond order $n$ exists, and attenuation makes the far orders negligible long before that. You choose how -many to carry with `states=` (or `nstates=` per call), and TorchSim sizes it +many to carry with `states=` (or `nstates=` per call), and BlochSim sizes it from the winding the sequence declares when you do not. ```{figure} /generated/figures/truncation.png @@ -292,7 +292,7 @@ once you know the gradient behind it. ## Second pool -Tissue is not one proton pool. TorchSim carries the two extensions of the EPG +Tissue is not one proton pool. BlochSim carries the two extensions of the EPG formalism that matter in practice, both from Malik et al [^3]: a **semisolid pool** that RF saturates through its absorption lineshape and that exchanges @@ -327,7 +327,7 @@ orders to be interpreted with care. It treats an isochromat's off-resonance as a constant precession rather than as a distribution, so a spread of field across the voxel is not something the states carry. -TorchSim applies that spread to the samples instead. `t2_prime_ms` sets a +BlochSim applies that spread to the samples instead. `t2_prime_ms` sets a Lorentzian population of frequencies, half-width $1 / T_2'$ in angular frequency, whose average over the voxel is $e^{-|\tau| / T_2'}$ in the time $\tau$ a sample has gone @@ -339,7 +339,7 @@ train does not, and needs a spread of `b0_hz` across voxels as before. Pulses are instantaneous rotations unless you say otherwise. A real slice-selective pulse acts under its gradient, so what it does depends on -position within the slice; TorchSim can carry that exactly by integrating the +position within the slice; BlochSim can carry that exactly by integrating the pulse ahead of time, which is the hybrid Bloch-EPG picture of Guenthner et al [^4] and is described in {doc}`implementation`. diff --git a/docs/explanations/implementation.md b/docs/explanations/implementation.md index bf8bd8a8..a3b68025 100644 --- a/docs/explanations/implementation.md +++ b/docs/explanations/implementation.md @@ -1,9 +1,9 @@ -# How TorchSim runs it +# How BlochSim runs it ```{admonition} TL;DR :class: tldr -- {class}`~torchsim.model.Simulator` is the public sequence abstraction. +- {class}`~blochsim.model.Simulator` is the public sequence abstraction. Offline it builds a description from `layout()`; scanner-driven use starts from an incoming description and applies the simulator's handlers. - The resulting event stream is packed once and executed by fused CPU or @@ -16,14 +16,14 @@ of the code is what it is: one description of a sequence, one fused kernel per voxel, and derivatives taken in whichever direction the question asks for. -You do not need any of it to use TorchSim. You need it to know what a run costs +You do not need any of it to use BlochSim. You need it to know what a run costs before you launch it, to read a number that surprises you, and to change something without breaking the pieces underneath. ## One path, from the sequence you write to the signal Everything a sequence can say is said once, in a -{class}`~torchsim.SequenceDescription`: a list of events, each with a +{class}`~blochsim.SequenceDescription`: a list of events, each with a timestamp, a payload, and an **action word** saying what the sequence plays around it. Whatever assembled that description -- operators you composed, a builder that ships here, a stream that arrived from a scanner -- what runs @@ -51,14 +51,14 @@ in it can be the variable an optimizer moves. An event is a wait, an RF pulse, or an ADC. Gradients are not events. A Pulseq file has no gradient *use* field, so a crusher cannot be told from a phase encode by looking at waveforms without tracking the k-space moment through the -whole TR -- and instead of doing that, TorchSim has each event declare its own +whole TR -- and instead of doing that, BlochSim has each event declare its own role: `CRUSH_BEFORE`, `CRUSH_AFTER`, `SHIFT_AFTER`, `SPOIL_AFTER`. ```{figure} /generated/figures/event_stream.png :width: 100% :alt: A timeline of RF, ADC and wait events for a four-echo refocused train. -A four-echo refocused train, as {meth}`~torchsim.model.Simulator.describe` +A four-echo refocused train, as {meth}`~blochsim.model.Simulator.describe` emits it. The crushers ride on the refocusing pulses that sit between them, which is why `Refocusing` is one operator rather than three; a spoiled readout would instead carry `SPOIL_AFTER`, and an unbalanced one @@ -92,7 +92,7 @@ voxels a card holds rather than by the sequence. ``` You set the order count with `states=` on a simulator, or `nstates=` on a -call. Left alone, TorchSim counts the winding the description declares -- +call. Left alone, BlochSim counts the winding the description declares -- every `CRUSH`, every `SHIFT_AFTER`, over every repetition -- and sizes the state from that, clamped into a sensible range. Measuring convergence for your own sequence is two calls, as in {doc}`epg`. @@ -174,7 +174,7 @@ off-resonance declared. Only the first is confined to the axis, and it is several times faster on this machine. ``` -TorchSim decides this per run, in one device round trip, and refuses two +BlochSim decides this per run, in one device round trip, and refuses two arrangements that look real but are not: off-resonance is refocused *at* the echo centres but not between them, and an excitation a quarter turn from the refocusing pulses gives samples that are real while the states fill the plane. @@ -190,7 +190,7 @@ direction depends on what you are differentiating. **With respect to tissue**, forward mode wins. Voxels are independent, so one directional derivative covers every voxel at once, and the cost is one pass per *property* -- not per voxel, not per echo. That is -{meth}`~torchsim.model.Simulator.jacobian`, and it is what a dictionary fit, a +{meth}`~blochsim.model.Simulator.jacobian`, and it is what a dictionary fit, a Cramer-Rao bound and a model-based reconstruction all consume. Inside the kernels it is dual arithmetic: each quantity carries a tangent beside it, and every operator differentiates itself. @@ -236,7 +236,7 @@ recover the map exactly; afterwards a call rebuilds them with whole-tensor arithmetic and leaves the structure alone. ``` -{meth}`~torchsim.model.Simulator.resolved` asks for that. It checks itself: +{meth}`~blochsim.model.Simulator.resolved` asks for that. It checks itself: where the map does not hold -- an entry drawing on more than one element, a rebuild disagreeing with a fresh packing at a point the map never saw -- the binding is refused and the ordinary path runs instead, same answer, slower. @@ -257,7 +257,7 @@ match or a kernel regression. What a run does with a problem. Streaming sizes its chunks to a memory budget and overlaps one chunk's transfer with another's arithmetic; sharding splits the voxels across cards and gathers the signal back. Say it around -the call with {func}`~torchsim.execution` and {func}`~torchsim.offload`, or +the call with {func}`~blochsim.execution` and {func}`~blochsim.offload`, or leave it alone and let each call decide against what the devices have free. ``` diff --git a/docs/explanations/index.md b/docs/explanations/index.md index 8eb37ea8..b41864cd 100644 --- a/docs/explanations/index.md +++ b/docs/explanations/index.md @@ -1,10 +1,10 @@ # Explanations -The concepts behind TorchSim, separate from the step-by-step examples and the +The concepts behind BlochSim, separate from the step-by-step examples and the API reference. {doc}`description` -: The sequence representation TorchSim consumes: events, RF definitions, +: The sequence representation BlochSim consumes: events, RF definitions, readout roles, Pulseq input, and the MRD description a running scanner can send. @@ -14,7 +14,7 @@ API reference. the model. {doc}`implementation` -: How a {class}`~torchsim.model.Simulator` turns either an offline layout or +: How a {class}`~blochsim.model.Simulator` turns either an offline layout or an incoming sequence description into the fused CPU/GPU state machine, and how differentiation and execution are arranged. diff --git a/docs/misc/contributors.md b/docs/misc/contributors.md index ddb28084..e6af192a 100644 --- a/docs/misc/contributors.md +++ b/docs/misc/contributors.md @@ -1,9 +1,9 @@ # Contributors -Contributions to the project can be seen [here](https://github.com/pulserver/torchsim/graphs/contributors) +Contributions to the project can be seen [here](https://github.com/pulserver/blochsim/graphs/contributors) ```{raw} html - - + + ``` diff --git a/docs/misc/related.md b/docs/misc/related.md index dab32fe7..5aacb04c 100644 --- a/docs/misc/related.md +++ b/docs/misc/related.md @@ -17,10 +17,10 @@ of events and the answer is a curve per tissue. That is what a dictionary, a fit, a Cramer-Rao bound and a model-based reconstruction are built from, and it is what an extended phase graph computes in a fraction of the states an isochromat ensemble needs. sycomore [^6], epgpy [^7], EpyG [^8], -mri-sim-py [^9], snapMRF [^10] and TorchSim answer this one. +mri-sim-py [^9], snapMRF [^10] and BlochSim answer this one. BlochSimulators.jl [^11] answers both, from the same sequence description. -**TorchSim is the second kind.** It has no gradient waveforms, no phantom +**BlochSim is the second kind.** It has no gradient waveforms, no phantom coordinates and no encoding operator of its own -- {doc}`../explanations/implementation` lists what that rules out. What it adds instead is the derivative: the same kernels that produce a signal produce its Jacobian with respect to tissue, and @@ -46,23 +46,23 @@ and a signal-model simulator has no reason to carry a coil. | [EpyG](https://github.com/brennerd11/EpyG) | EPG | Python | CPU | -- | Python | | [mri-sim-py](https://github.com/utcsilab/mri-sim-py.epg) | EPG | Python, PyTorch | CPU, CUDA | Automatic, reverse mode | Python | | [snapMRF](https://github.com/dongwang881107/snapMRF) | EPG, with matching | CUDA C | CUDA | -- | Command line | -| **TorchSim** | EPG, and closed forms | Python, PyTorch, C++ and Triton kernels | CPU threads, CUDA, several cards | Automatic: forward, reverse, and forward over reverse | Python, or a description | +| **BlochSim** | EPG, and closed forms | Python, PyTorch, C++ and Triton kernels | CPU threads, CUDA, several cards | Automatic: forward, reverse, and forward over reverse | Python, or a description | ## Where each one is the better tool **A sequence you are developing, and the image it makes.** KomaMRI, JEMRIS, CMRsim or MRzero-Core. They read the waveforms you will play, carry the spins -through them at their coordinates, and hand back k-space. TorchSim reads a +through them at their coordinates, and hand back k-space. BlochSim reads a sequence as events rather than waveforms, and stops at the signal. **One curve, read interactively, with the model in front of you.** sycomore or epgpy. Both are a few milliseconds for one tissue with nothing to warm up, and -sycomore's units make an expression read like the paper it came from. TorchSim +sycomore's units make an expression read like the paper it came from. BlochSim resolves the structure of a sequence before it runs one, which is seconds it does not repay until there is a dictionary to sweep or a loop to run. **A dictionary, a fit, a design loop, or a map solved from k-space.** -TorchSim. The batching across tissues, the derivative, and the estimators and +BlochSim. The batching across tissues, the derivative, and the estimators and reconstruction that consume it are the point of the package; `benchmarks/` in the repository measures the first two against the alternatives above and states the agreement between them. diff --git a/docs/sphinx_add_colab_link.py b/docs/sphinx_add_colab_link.py index 0878f38b..7a85cdd1 100644 --- a/docs/sphinx_add_colab_link.py +++ b/docs/sphinx_add_colab_link.py @@ -55,7 +55,7 @@ def run(self): ) # Generate the Colab URL based on GitHub repo information - self.colab_url = f"https://colab.research.google.com/github/pulserver/torchsim/blob/{binder['branch']}/{on_the_branch}" + self.colab_url = f"https://colab.research.google.com/github/pulserver/blochsim/blob/{binder['branch']}/{on_the_branch}" # Create the HTML button or link self.html = f"""
@@ -106,7 +106,7 @@ def notebook_modifier(self, notebook_path, commands): ) message_class = "warning" message = ( - "Running this torchsim example requires a GPU, and hence is NOT " + "Running this blochsim example requires a GPU, and hence is NOT " "possible on binder currently We request you to kindly run this notebook " "on Google Colab by clicking the link below. Additionally, please make " "sure to set the runtime on Colab to use a GPU and install the below " diff --git a/docs/user_guide.md b/docs/user_guide.md index f3dc2e12..463999ce 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -10,7 +10,7 @@ and a map of where the rest of this documentation is. ## Install -TorchSim is a PyTorch package with two compiled kernels behind it. `pip` +BlochSim is a PyTorch package with two compiled kernels behind it. `pip` installs the Python side and its NumPy and SciPy dependencies for you. Two things it cannot decide on your behalf, and which you therefore settle first: **which build of PyTorch you want**, and **where you want the whole thing to @@ -34,8 +34,8 @@ Any of these works. Pick the one you already use. Ships with Python, nothing to install first: ```sh -python -m venv ~/envs/torchsim -source ~/envs/torchsim/bin/activate # Windows: ~\envs\torchsim\Scripts\activate +python -m venv ~/envs/blochsim +source ~/envs/blochsim/bin/activate # Windows: ~\envs\blochsim\Scripts\activate python -m pip install --upgrade pip ``` ::: @@ -47,11 +47,11 @@ Useful when you also want a specific Python, or non-Python libraries beside it: ```sh -conda create -n torchsim python=3.12 -conda activate torchsim +conda create -n blochsim python=3.12 +conda activate blochsim ``` -Install TorchSim itself with `pip` inside that environment; there is no +Install BlochSim itself with `pip` inside that environment; there is no conda package. ::: @@ -61,8 +61,8 @@ conda package. The fastest of the three, and it resolves the whole set at once: ```sh -uv venv --python 3.12 ~/envs/torchsim -source ~/envs/torchsim/bin/activate +uv venv --python 3.12 ~/envs/blochsim +source ~/envs/blochsim/bin/activate ``` Read `uv pip install` for `pip install` in everything that follows. @@ -72,9 +72,9 @@ Read `uv pip install` for `pip install` in everything that follows. ### PyTorch first -**Install PyTorch before TorchSim.** The wheel you want depends on hardware +**Install PyTorch before BlochSim.** The wheel you want depends on hardware `pip` cannot see: a CPU-only build and a CUDA build have the same name and -version, and differ only in which index they came from. Install TorchSim first +version, and differ only in which index they came from. Install BlochSim first and you get whichever build the default index happens to serve, which is usually not the one you meant. @@ -92,7 +92,7 @@ the smallest download by a wide margin: pip install torch --index-url https://download.pytorch.org/whl/cpu ``` -Everything in TorchSim runs here. The state machine has a threaded, +Everything in BlochSim runs here. The state machine has a threaded, vectorized C++ kernel behind it, so a dictionary of a few thousand atoms is seconds rather than minutes. ::: @@ -140,10 +140,10 @@ python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda A CUDA build prints a version and `True`; a CPU build prints `None` and `False`. -### TorchSim +### BlochSim ```sh -pip install torchsim +pip install blochsim ``` Where a wheel exists for your platform this is the whole of it. Where one does @@ -159,7 +159,7 @@ once: ```python import torch -from torchsim.simulators import FSESimulator +from blochsim.simulators import FSESimulator acquisition = FSESimulator( ESP=5.0, @@ -184,14 +184,14 @@ seems to hang is almost always that compile. ## Your first simulation -The central public object in TorchSim is a -{class}`~torchsim.model.Simulator`. A shipped simulator fixes the sequence; +The central public object in BlochSim is a +{class}`~blochsim.model.Simulator`. A shipped simulator fixes the sequence; `simulate` receives the tissue and anything you want to vary. Asking for a Jacobian is the same model and the same protocol: ```python import numpy as np -from torchsim.simulators import MRFSimulator +from blochsim.simulators import MRFSimulator flip = np.concatenate( (np.linspace(5.0, 60.0, 300), np.linspace(60.0, 2.0, 300), np.full(280, 2.0)) @@ -205,9 +205,9 @@ respect to T1 and T2. Dictionary fitting, nonlinear least squares, model-based reconstruction and sequence design all consume the same simulator interface. -Functional calls such as {func}`torchsim.mrf_sim` are convenience wrappers +Functional calls such as {func}`blochsim.mrf_sim` are convenience wrappers around the shipped simulator classes. They are useful for a one-off call, but -{class}`~torchsim.model.Simulator` is the interface to learn and the class to +{class}`~blochsim.model.Simulator` is the interface to learn and the class to subclass when you implement a sequence. Arrays go in and come back in whatever library you wrote them in -- NumPy here, @@ -222,7 +222,7 @@ CuPy or PyTorch elsewhere -- over the same memory rather than a copy. {doc}`generated/autoexamples/index` : The executable Course and Tours. The Course starts with a shipped simulator, then shows how to implement a new sequence by subclassing - {class}`~torchsim.model.Simulator`; the Tours cover inference, design and + {class}`~blochsim.model.Simulator`; the Tours cover inference, design and model-based reconstruction. {doc}`api/index` @@ -231,19 +231,19 @@ CuPy or PyTorch elsewhere -- over the same memory rather than a copy. placing a run across devices. {doc}`developer_guide` -: Setting up to change TorchSim: the editable install, the style the code is +: Setting up to change BlochSim: the editable install, the style the code is written in, the tests, and how a pull request is opened. ## Getting help, and reporting what breaks **Ask a question** in -[Discussions](https://github.com/pulserver/torchsim/discussions). How to model +[Discussions](https://github.com/pulserver/blochsim/discussions). How to model a sequence, whether a signal you got is expected, which estimator suits a problem -- these belong there, and the answer is then findable by whoever asks next. **Report a bug** in -[Issues](https://github.com/pulserver/torchsim/issues/new/choose), where a +[Issues](https://github.com/pulserver/blochsim/issues/new/choose), where a form asks for what a fix needs: - the shortest script that reproduces it, pasted whole -- a sequence is enough @@ -252,7 +252,7 @@ form asks for what a fix needs: bug: say which analytic case, published figure or alternative simulator you are comparing against; - the full traceback, if it raises; -- the environment, as the form's command prints it -- TorchSim, PyTorch, CUDA, +- the environment, as the form's command prints it -- BlochSim, PyTorch, CUDA, Python; - whether you have seen it on the CPU kernels, the CUDA kernels, or both. That difference is often the whole diagnosis. @@ -262,8 +262,8 @@ the same form chooser. Name the paper the model comes from and the figure it would have to reproduce; that is what makes it implementable. **Report a vulnerability** privately instead: open a draft advisory from the -repository's [Security tab](https://github.com/pulserver/torchsim/security/advisories/new), -or email the address in the [security policy](https://github.com/pulserver/torchsim/blob/main/.github/SECURITY.md). +repository's [Security tab](https://github.com/pulserver/blochsim/security/advisories/new), +or email the address in the [security policy](https://github.com/pulserver/blochsim/blob/main/.github/SECURITY.md). The kernels index raw pointers, so anything reachable from ordinary arguments that reads or writes out of bounds is worth reporting that way rather than in a public issue. Wrong physics is a bug report, not a vulnerability. diff --git a/examples/01-framework/01-getting-started.py b/examples/01-framework/01-getting-started.py index 2e056053..19ec304b 100644 --- a/examples/01-framework/01-getting-started.py +++ b/examples/01-framework/01-getting-started.py @@ -3,17 +3,17 @@ Basic Usage =========== -The scope of this notebook is to showcase the basic functionalities of Torchsim, +The scope of this notebook is to showcase the basic functionalities of Blochsim, including how to simulate a signal, calculating derivatives etc. -This example uses a fast spin echo that ships with TorchSim. +This example uses a fast spin echo that ships with BlochSim. """ # %% # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim +# !pip install blochsim # sphinx_gallery_start_ignore import warnings @@ -95,8 +95,8 @@ def key(axes, ncols=1): import numpy as np import torch -import torchsim -from torchsim.simulators import FSESimulator +import blochsim +from blochsim.simulators import FSESimulator # sphinx_gallery_start_ignore from pathlib import Path @@ -159,8 +159,8 @@ def key(axes, ncols=1): # Derivative with respect to tissue parameters # -------------------------------------------- # -# Torchsim allows to efficiently evaluate the derivative -# of the signal wrt input parameters, via :meth:`~torchsim.model.Simulator.jacobian`. +# Blochsim allows to efficiently evaluate the derivative +# of the signal wrt input parameters, via :meth:`~blochsim.model.Simulator.jacobian`. # The desired derivatives can be specified by string: # signal, dT2 = simulator.jacobian("T2", flip=flip) # dT2 is (3, 48) too @@ -231,7 +231,7 @@ def key(axes, ncols=1): # the object above with the construction folded in, so the answer is the same # to the bit. # -signal, dT2 = torchsim.fse_sim( +signal, dT2 = blochsim.fse_sim( flip=flip, ESP=ESP_MS, TR=3000.0, T1=T1_MS, T2=T2_MS, diff="T2" ) @@ -327,7 +327,7 @@ def key(axes, ncols=1): # through in chunks. Naming a device insists on it, and a block settles it for # everything inside: # -with torchsim.execution("cpu"): +with blochsim.execution("cpu"): on_the_host = simulator.simulate(flip=flip) print( @@ -342,7 +342,7 @@ def key(axes, ncols=1): # thousand voxels is the case they exist for, and it is written like this: # if torch.cuda.is_available(): - with torchsim.execution("cuda", stream=True, budget_bytes=1 << 28): + with blochsim.execution("cuda", stream=True, budget_bytes=1 << 28): streamed = simulator.simulate(flip=flip) agreement = float((streamed.cpu() - signal_ref).abs().max()) print(f" streamed through a card in 256 MiB chunks, agrees to {agreement:.1e}") @@ -381,7 +381,7 @@ def key(axes, ncols=1): # %% # # You do not have to read it. ``describe`` here only shows what a stream looks -# like; :meth:`~torchsim.SequenceDescription.plot` draws one, which is the way +# like; :meth:`~blochsim.SequenceDescription.plot` draws one, which is the way # to check that a layout laid down what you meant. # @@ -458,18 +458,18 @@ def _arrow(start_at, end_at, label=None, side="right"): # # A description normally arrives; it is not typed. Writing one by hand once is # worth it only to see that there is nothing else in it -- -# :meth:`~torchsim.SequenceDescription.from_operators` lays them out. +# :meth:`~blochsim.SequenceDescription.from_operators` lays them out. # -by_hand = torchsim.SequenceDescription.from_operators( - torchsim.Excitation(math.pi / 2, math.pi / 2), +by_hand = blochsim.SequenceDescription.from_operators( + blochsim.Excitation(math.pi / 2, math.pi / 2), *[ part for _ in range(ECHOES) for part in ( - torchsim.Delay(0.5 * ESP_MS * 1e-3), - torchsim.Refocusing(math.radians(60.0), 0.0), - torchsim.Delay(0.5 * ESP_MS * 1e-3), - torchsim.Readout(0.0), + blochsim.Delay(0.5 * ESP_MS * 1e-3), + blochsim.Refocusing(math.radians(60.0), 0.0), + blochsim.Delay(0.5 * ESP_MS * 1e-3), + blochsim.Readout(0.0), ) ], ) @@ -481,7 +481,7 @@ def _arrow(start_at, end_at, label=None, side="right"): # %% # -# :meth:`~torchsim.model.Simulator.from_description` runs one, and the only +# :meth:`~blochsim.model.Simulator.from_description` runs one, and the only # thing given to it is the tissue. The events are already concrete -- each # carries the action word saying whether it winds, spoils or records -- so no # layout is walked and no sequence parameter is inferred. @@ -514,7 +514,7 @@ def _arrow(start_at, end_at, label=None, side="right"): # # One thing to know when comparing it against the shipped simulator: a # description carries the events, and a simulator may carry physics *around* -# them. :class:`~torchsim.simulators.FSESimulator` folds in the recovery +# them. :class:`~blochsim.simulators.FSESimulator` folds in the recovery # between one train and the next in closed form, which is not an event and so # is not in the stream. The shape of the train is the same; the driven # equilibrium the shipped object adds does not come along. @@ -540,7 +540,7 @@ def _arrow(start_at, end_at, label=None, side="right"): # The stream a scanner sends is read off the Pulseq sequence it is running, so # the same events can be read from the ``.seq`` file directly -- which is what # to do when the scan has not been run yet. The file is parsed by pypulseq, -# which also computes its trajectory: ``pip install torchsim[pulseq]``. +# which also computes its trajectory: ``pip install blochsim[pulseq]``. # # The file states how many blocks one repetition holds, in its ``TRSize`` # definition, so nothing is searched for. What is read off the trajectory is diff --git a/examples/01-framework/02-expanded-physics.py b/examples/01-framework/02-expanded-physics.py index 2cc9344c..ee3c44c3 100644 --- a/examples/01-framework/02-expanded-physics.py +++ b/examples/01-framework/02-expanded-physics.py @@ -16,7 +16,7 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim +# !pip install blochsim # %% # @@ -103,9 +103,9 @@ def key(axes, ncols=1): import numpy as np import torch -import torchsim -from torchsim import SPGRReadout, ShimDefinition, bSSFPReadout -from torchsim.simulators import FSESimulator, MRFSimulator +import blochsim +from blochsim import SPGRReadout, ShimDefinition, bSSFPReadout +from blochsim.simulators import FSESimulator, MRFSimulator # %% # Test sequence @@ -167,7 +167,7 @@ def key(axes, ncols=1): # # On a parallel-transmit system the field is the complex sum of what several # channels put on the voxel. ``B1`` and ``B1phase`` then carry one row per -# channel, and a :class:`~torchsim.ShimDefinition` gives the amplitude and +# channel, and a :class:`~blochsim.ShimDefinition` gives the amplitude and # phase each channel is driven at. # # Four channels whose sensitivities sit a quarter turn apart, driven alike, @@ -250,7 +250,7 @@ def first_echo(step_rad): # response rather than a scaling, it cannot be folded into the flip angle: the # pulse must be integrated. # -# TorchSim takes the complex envelope, one row per transmit channel, as it +# BlochSim takes the complex envelope, one row per transmit channel, as it # comes off a Pulseq block or an MRD sequence description. This one is an SLR # 90 degree pulse, 2 ms long over a 5 mm slice, saved beside this file. # @@ -260,7 +260,7 @@ def first_echo(step_rad): DATA = Path("data") if Path("data").is_dir() else Path(__file__).parent / "data" # sphinx_gallery_end_ignore waveform = np.load(DATA / "slr90.npz") -excitation = torchsim.rf_definition( +excitation = blochsim.rf_definition( waveform["samples"], dwell_s=float(waveform["dwell_s"]), bandwidth_hz=float(waveform["bandwidth_hz"]), @@ -288,7 +288,7 @@ def first_echo(step_rad): # # sphinx_gallery_start_ignore -from torchsim.sequence._transition import transition_table # noqa: E402 +from blochsim.sequence._transition import transition_table # noqa: E402 positions = torch.linspace(-1.0, 1.0, 121, dtype=torch.float64) table = transition_table(excitation, positions, bins=64, rf_raster_time_s=1e-6) diff --git a/examples/01-framework/03-writing-a-simulator.py b/examples/01-framework/03-writing-a-simulator.py index b9266d52..487e78ff 100644 --- a/examples/01-framework/03-writing-a-simulator.py +++ b/examples/01-framework/03-writing-a-simulator.py @@ -3,7 +3,7 @@ Writing a Signal Model ====================== -The scope of this notebook is to write a simulator TorchSim does not ship. +The scope of this notebook is to write a simulator BlochSim does not ship. There are two things to say. **Which operator plays each kind of event** -- what an excitation is, what a sample is -- and **what order they are played @@ -16,7 +16,7 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim +# !pip install blochsim # %% # @@ -101,7 +101,7 @@ def key(axes, ncols=1): import numpy as np import torch -from torchsim import ( +from blochsim import ( Delay, Excitation, Inversion, @@ -109,17 +109,17 @@ def key(axes, ncols=1): SSFPFidReadout, Spoil, ) -from torchsim.model import Simulator +from blochsim.model import Simulator # %% # Handlers # -------- # A simulator says what plays each kind of event by naming it. The five # readouts are what distinguishes one steady-state family from another: an -# :func:`~torchsim.SSFPFidReadout` winds one configuration order after every -# sample, an :func:`~torchsim.SSFPEchoReadout` winds it before, an -# :func:`~torchsim.SPGRReadout` spoils, a :func:`~torchsim.bSSFPReadout` leaves -# the states where they were, and an :func:`~torchsim.FSEReadout` samples a +# :func:`~blochsim.SSFPFidReadout` winds one configuration order after every +# sample, an :func:`~blochsim.SSFPEchoReadout` winds it before, an +# :func:`~blochsim.SPGRReadout` spoils, a :func:`~blochsim.bSSFPReadout` leaves +# the states where they were, and an :func:`~blochsim.FSEReadout` samples a # spin echo the refocusing pulses have already crushed around. # # Naming one is the whole of choosing between them, and it is also what says @@ -133,7 +133,7 @@ def key(axes, ncols=1): # %% # Layout # ------ -# An :class:`~torchsim.model.Simulator` is the protocol. You do not +# An :class:`~blochsim.model.Simulator` is the protocol. You do not # write timestamps: ``layout`` returns the *operators* of one repetition in # order, and the simulator turns the span each one holds into the timestamps a # description carries. @@ -205,7 +205,7 @@ def layout(self, *, flip, TR, TI=0.0): # derivative per property yields every voxel's derivative at once, and the cost # is one pass per property rather than per voxel. # -# That is what :meth:`~torchsim.model.Simulator.jacobian` does. A single name +# That is what :meth:`~blochsim.model.Simulator.jacobian` does. A single name # collapses the parameter axis; a sequence of names keeps it. # signal, jacobian = sequence.jacobian(("T1", "T2"), T1=1000.0, T2=100.0) @@ -362,7 +362,7 @@ def layout(self, *, TS, flip, phases=0.0): # %% # Inspecting the event stream # --------------------------- -# :meth:`~torchsim.model.Simulator.describe` returns the event stream, which is +# :meth:`~blochsim.model.Simulator.describe` returns the event stream, which is # the same object a sequence arriving from a scanner is read into: a timestamp # and an action word on every event. It is worth looking at once, because a # sequence that plays the wrong thing is far easier to see here than in the @@ -387,7 +387,7 @@ def layout(self, *, TS, flip, phases=0.0): # --------------------------- # A description that came from somewhere else -- an MRD file, a Pulseq export, # a scanner's own stream -- becomes a simulator through -# :meth:`~torchsim.model.Simulator.from_description`. No layout is walked: the +# :meth:`~blochsim.model.Simulator.from_description`. No layout is walked: the # events are re-emitted through this model's handlers, which is what puts the # gradients back that the transport does not carry. # diff --git a/examples/01-framework/04-custom-operator.py b/examples/01-framework/04-custom-operator.py index 7fd94cb4..c1f3249b 100644 --- a/examples/01-framework/04-custom-operator.py +++ b/examples/01-framework/04-custom-operator.py @@ -3,7 +3,7 @@ Writing a New Operator ====================== -The scope of this notebook is to show how to add a sequence module TorchSim +The scope of this notebook is to show how to add a sequence module BlochSim does not ship -- a preparation, or a readout -- without touching a kernel. An operator is a Python function that returns events and says how long it @@ -16,7 +16,7 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim +# !pip install blochsim # %% # @@ -100,7 +100,7 @@ def key(axes, ncols=1): # sphinx_gallery_end_ignore import torch -from torchsim import ( +from blochsim import ( Delay, Dephase, Excitation, @@ -110,7 +110,7 @@ def key(axes, ncols=1): SSFPFidReadout, Spoil, ) -from torchsim.model import Simulator +from blochsim.model import Simulator # %% # Composing existing operators @@ -124,7 +124,7 @@ def key(axes, ncols=1): # that is what an operator is. # # The Refocusing pulse is asked for uncrushed: a T2 preparation refocuses -# rather than dephases, and the crusher pair :func:`~torchsim.Refocusing` adds +# rather than dephases, and the crusher pair :func:`~blochsim.Refocusing` adds # by default would spoil the echo it exists to form. @@ -247,7 +247,7 @@ def layout(self, *, TE_prep, ESP, ETL): # and a sample taken *after* it sits where the next pulse will refocus the # previous excitation -- an echo, and far more strongly T2-weighted. # -# TorchSim ships each of those separately. Taking both in one repetition is a +# BlochSim ships each of those separately. Taking both in one repetition is a # double-echo steady state, and writing it is putting the winding between two # samples rather than on one side of them. diff --git a/examples/01-framework/README.rst b/examples/01-framework/README.rst index 4a4e34e2..a47aea1e 100644 --- a/examples/01-framework/README.rst +++ b/examples/01-framework/README.rst @@ -1,14 +1,14 @@ Framework --------- -How to use TorchSim, and how to extend it. +How to use BlochSim, and how to extend it. The first notebook covers basic usage: simulating a signal, taking its derivatives, tuning the run, and reading a sequence back from the description a scanner streams. The second covers the physics a simulator can carry beyond T1 and T2. -The last two are for sequences TorchSim does not ship. A **signal model** of +The last two are for sequences BlochSim does not ship. A **signal model** of your own is two pieces -- a physics saying what each kind of event does, and a simulator saying what order they are played in. An **operator** of your own -- a preparation, a readout -- is a Python function that returns events. diff --git a/examples/02-parameter-inference/01-dictionary-matching.py b/examples/02-parameter-inference/01-dictionary-matching.py index 9213b6e7..a008af30 100644 --- a/examples/02-parameter-inference/01-dictionary-matching.py +++ b/examples/02-parameter-inference/01-dictionary-matching.py @@ -18,12 +18,12 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim brainweb-dl cmap +# !pip install blochsim brainweb-dl cmap # %% # # The problem is stated over a simulator carrying the sequence and filled in -# by an estimator. :func:`~torchsim.execution` decides where that work runs, +# by an estimator. :func:`~blochsim.execution` decides where that work runs, # and the timings below are taken inside it. # @@ -165,12 +165,12 @@ def key(axes, ncols=1): import numpy as np import torch -import torchsim -from torchsim import ( +import blochsim +from blochsim import ( Subspace, ) -from torchsim.estimators import DictionaryMatcher -from torchsim.simulators import MRFSimulator +from blochsim.estimators import DictionaryMatcher +from blochsim.simulators import MRFSimulator # %% # @@ -291,7 +291,7 @@ def footprint(problem): def mapped(problem, passes=3): """Map the slice a few times: the quickest pass, and what it held.""" on_device = torch.cuda.is_available() - with torchsim.execution(): + with blochsim.execution(): problem(measured[:64]) if on_device: torch.cuda.reset_peak_memory_stats() diff --git a/examples/02-parameter-inference/02-lookup-table.py b/examples/02-parameter-inference/02-lookup-table.py index e2bff627..ce00128d 100644 --- a/examples/02-parameter-inference/02-lookup-table.py +++ b/examples/02-parameter-inference/02-lookup-table.py @@ -10,19 +10,19 @@ With a single unknown a dictionary degenerates: the atoms lie on a curve rather than filling a space. Interpolating between the two nearest then costs nothing and takes the grid spacing out of the answer, which is what -:class:`~torchsim.LookupTable` does. +:class:`~blochsim.LookupTable` does. """ # %% # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim brainweb-dl cmap +# !pip install blochsim brainweb-dl cmap # %% # # The problem is stated over a simulator carrying the sequence and filled in -# by an estimator. :func:`~torchsim.execution` decides where that work runs, +# by an estimator. :func:`~blochsim.execution` decides where that work runs, # and the timings below are taken inside it. # @@ -164,9 +164,9 @@ def key(axes, ncols=1): import numpy as np import torch -import torchsim -from torchsim.estimators import DictionaryMatcher, LookupTable -from torchsim.simulators import MP2RAGESimulator +import blochsim +from blochsim.estimators import DictionaryMatcher, LookupTable +from blochsim.simulators import MP2RAGESimulator # %% # @@ -312,7 +312,7 @@ def footprint(problem): def mapped(problem, passes=3): """Map the slice a few times: the quickest pass, and what it held.""" on_device = torch.cuda.is_available() - with torchsim.execution(): + with blochsim.execution(): problem(measured[:64]) if on_device: torch.cuda.reset_peak_memory_stats() diff --git a/examples/02-parameter-inference/03-nonlinear-least-squares.py b/examples/02-parameter-inference/03-nonlinear-least-squares.py index c1c2e6e6..dd1822d1 100644 --- a/examples/02-parameter-inference/03-nonlinear-least-squares.py +++ b/examples/02-parameter-inference/03-nonlinear-least-squares.py @@ -19,12 +19,12 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim brainweb-dl cmap +# !pip install blochsim brainweb-dl cmap # %% # # The problem is stated over a simulator carrying the sequence and filled in -# by an estimator. :func:`~torchsim.execution` decides where that work runs, +# by an estimator. :func:`~blochsim.execution` decides where that work runs, # and the timings below are taken inside it. # @@ -166,9 +166,9 @@ def key(axes, ncols=1): import numpy as np import torch -import torchsim -from torchsim.estimators import DictionaryMatcher, NonlinearLeastSquares -from torchsim.simulators import MultiEchoSimulator +import blochsim +from blochsim.estimators import DictionaryMatcher, NonlinearLeastSquares +from blochsim.simulators import MultiEchoSimulator # %% # @@ -266,7 +266,7 @@ def footprint(problem): def mapped(problem, passes=3): """Map the slice a few times: the quickest pass, and what it held.""" on_device = torch.cuda.is_available() - with torchsim.execution(): + with blochsim.execution(): problem(measured[:64]) if on_device: torch.cuda.reset_peak_memory_stats() diff --git a/examples/02-parameter-inference/04-perk.py b/examples/02-parameter-inference/04-perk.py index bf490d08..3b36dcf6 100644 --- a/examples/02-parameter-inference/04-perk.py +++ b/examples/02-parameter-inference/04-perk.py @@ -17,12 +17,12 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim brainweb-dl cmap +# !pip install blochsim brainweb-dl cmap # %% # # The problem is stated over a simulator carrying the sequence and filled in -# by an estimator. :func:`~torchsim.execution` decides where that work runs, +# by an estimator. :func:`~blochsim.execution` decides where that work runs, # and the timings below are taken inside it. # @@ -164,12 +164,12 @@ def key(axes, ncols=1): import numpy as np import torch -import torchsim -from torchsim import ( +import blochsim +from blochsim import ( Subspace, ) -from torchsim.estimators import PERK, DictionaryMatcher -from torchsim.simulators import MRFSimulator +from blochsim.estimators import PERK, DictionaryMatcher +from blochsim.simulators import MRFSimulator # %% # @@ -292,7 +292,7 @@ def footprint(problem): def mapped(problem, passes=3): """Map the slice a few times: the quickest pass, and what it held.""" on_device = torch.cuda.is_available() - with torchsim.execution(): + with blochsim.execution(): problem(measured[:64]) if on_device: torch.cuda.reset_peak_memory_stats() @@ -579,7 +579,7 @@ def painted(values): # _signal, sensitivity = simulator.jacobian("T1 T2".split(), **truth) sensitivity = sensitivity.real * density[:, None, None] -floor = torchsim.crlb(sensitivity, noise_variance=NOISE_STD**2, singular="infinite") +floor = blochsim.crlb(sensitivity, noise_variance=NOISE_STD**2, singular="infinite") bound = {"T1": floor[:, 0].sqrt(), "T2": floor[:, 1].sqrt()} # sphinx_gallery_start_ignore diff --git a/examples/03-sequence-optimization/01-echo-train-design.py b/examples/03-sequence-optimization/01-echo-train-design.py index c36135bc..e51d02da 100644 --- a/examples/03-sequence-optimization/01-echo-train-design.py +++ b/examples/03-sequence-optimization/01-echo-train-design.py @@ -19,12 +19,12 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim +# !pip install blochsim # %% # # A design is a simulator with its tissue fixed on it, a cost written on what -# it records, and the bounded parameters :class:`~torchsim.SequenceDesign` +# it records, and the bounded parameters :class:`~blochsim.SequenceDesign` # drives. # @@ -107,8 +107,8 @@ def key(axes, ncols=1): import torch -from torchsim.optim import Bounded, SequenceDesign -from torchsim.simulators import FSESimulator +from blochsim.optim import Bounded, SequenceDesign +from blochsim.simulators import FSESimulator # %% # @@ -273,7 +273,7 @@ def single_train(control): # # A conventional prescription to start from: 50 degree minimum, 90 degree # centre-of-k-space angle, 150 degree maximum. The limits are what the scanner -# will play, and :class:`~torchsim.Bounded` holds them exactly. +# will play, and :class:`~blochsim.Bounded` holds them exactly. # design = SequenceDesign(single_train, control=Bounded(PRESCRIBED, LOWEST, HIGHEST)) diff --git a/examples/03-sequence-optimization/02-joint-relaxometry.py b/examples/03-sequence-optimization/02-joint-relaxometry.py index 75d28263..107f3360 100644 --- a/examples/03-sequence-optimization/02-joint-relaxometry.py +++ b/examples/03-sequence-optimization/02-joint-relaxometry.py @@ -18,12 +18,12 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim brainweb-dl cmap +# !pip install blochsim brainweb-dl cmap # %% # # The two closed-form sequences being designed, the three pieces a design is -# stated in, and :func:`~torchsim.crlb`, which is the cost. +# stated in, and :func:`~blochsim.crlb`, which is the cost. # # sphinx_gallery_start_ignore @@ -159,9 +159,9 @@ def key(axes, ncols=1): import torch -import torchsim -from torchsim.optim import Bounded, SequenceDesign -from torchsim.simulators import SPGRSimulator, bSSFPSimulator +import blochsim +from blochsim.optim import Bounded, SequenceDesign +from blochsim.simulators import SPGRSimulator, bSSFPSimulator # %% # @@ -213,7 +213,7 @@ def bounds(spgr_flip, ssfp_flip): together = torch.cat( (rows(spgr, flip=spgr_flip), rows(ssfp, flip=ssfp_flip)), dim=-1 ) - return torchsim.crlb(together, noise_variance=NOISE**2) + return blochsim.crlb(together, noise_variance=NOISE**2) # %% @@ -415,13 +415,13 @@ def precision(spgr_flip, ssfp_flip): # %% # # The two blocks are one experiment and are fitted as one: a -# :class:`~torchsim.model.Simulator` that plays each and concatenates what +# :class:`~blochsim.model.Simulator` that plays each and concatenates what # they record. The fit is the one thing held fixed between the protocols -- # the same nonlinear least squares over the same four unknowns, from the same # guess. # -from torchsim.estimators import NonlinearLeastSquares -from torchsim.model import Simulator +from blochsim.estimators import NonlinearLeastSquares +from blochsim.model import Simulator class JointRelaxometry(Simulator): @@ -521,7 +521,7 @@ def predicted_sigma(spgr_flip, ssfp_flip): together = torch.cat( (rows(at_voxel[0], flip=spgr_flip), rows(at_voxel[1], flip=ssfp_flip)), dim=-1 ) - bound = torchsim.crlb(together, noise_variance=NOISE**2) + bound = blochsim.crlb(together, noise_variance=NOISE**2) return { "T1": bound[..., 0].sqrt() / truth["T1"], "T2": bound[..., 1].sqrt() / truth["T2"], diff --git a/examples/03-sequence-optimization/03-rf-pulse-design.py b/examples/03-sequence-optimization/03-rf-pulse-design.py index e6563a2e..c2ff4430 100644 --- a/examples/03-sequence-optimization/03-rf-pulse-design.py +++ b/examples/03-sequence-optimization/03-rf-pulse-design.py @@ -20,7 +20,7 @@ # .. colab-link:: # :needs_gpu: 0 # -# !pip install torchsim +# !pip install blochsim # sphinx_gallery_start_ignore import warnings @@ -52,7 +52,7 @@ import torch -from torchsim import SequenceDesign, compose_spinor +from blochsim import SequenceDesign, compose_spinor # %% # @@ -125,7 +125,7 @@ def cost(real, imag): # --------------- # # The real and imaginary parts of every sample are the designed parameters, -# free of limits: the scanner's peak B1 would be a :class:`~torchsim.Bounded` +# free of limits: the scanner's peak B1 would be a :class:`~blochsim.Bounded` # on them. # diff --git a/examples/03-sequence-optimization/README.rst b/examples/03-sequence-optimization/README.rst index 3ebc9892..0e6c46eb 100644 --- a/examples/03-sequence-optimization/README.rst +++ b/examples/03-sequence-optimization/README.rst @@ -6,7 +6,7 @@ given. A design problem is three pieces: a simulator with the tissue it is designed for already fixed on it; a cost, which is a plain function of what that -simulator records; and a :class:`~torchsim.SequenceDesign`, which holds the +simulator records; and a :class:`~blochsim.SequenceDesign`, which holds the parameters inside the limits the scanner will play and runs the loop. Only the cost changes between the first two examples here. One asks for a diff --git a/examples/04-model-based-imaging/01-linear-subspace.py b/examples/04-model-based-imaging/01-linear-subspace.py index 847686a7..afd67a52 100644 --- a/examples/04-model-based-imaging/01-linear-subspace.py +++ b/examples/04-model-based-imaging/01-linear-subspace.py @@ -19,7 +19,7 @@ # .. colab-link:: # :needs_gpu: 1 # -# !pip install torchsim brainweb-dl cmap mri-nufft[finufft,cufinufft] deepinv +# !pip install blochsim brainweb-dl cmap mri-nufft[finufft,cufinufft] deepinv # %% # @@ -122,14 +122,14 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): # %% # -# The Fourier encoding is not TorchSim's and never will be. ``mri-nufft`` +# The Fourier encoding is not BlochSim's and never will be. ``mri-nufft`` # supplies the radial trajectory and the non-uniform transform that plays it; # ``deepinv`` supplies the linear solver a Gauss-Newton step hands its # linearized problem to, and the :class:`~deepinv.physics.LinearPhysics` base # class the encoding operator below is written against. # # That base class is the whole of the adapter: anything exposing ``A`` and -# ``A_adjoint`` composes with what TorchSim supplies, so the operator built a +# ``A_adjoint`` composes with what BlochSim supplies, so the operator built a # few cells down is the only glue this integration needs. # import mrinufft @@ -140,8 +140,8 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): # %% # -# From TorchSim: the sequence, the estimator the contrast-then-fit routes -# need, and :attr:`~torchsim.Subspace.modes`, which hands the temporal +# From BlochSim: the sequence, the estimator the contrast-then-fit routes +# need, and :attr:`~blochsim.Subspace.modes`, which hands the temporal # basis to mri-nufft in the layout its subspace operator reads. # import time @@ -149,8 +149,8 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): import numpy as np import torch -from torchsim.estimators import DictionaryMatcher -from torchsim.simulators import MultiEchoSimulator +from blochsim.estimators import DictionaryMatcher +from blochsim.simulators import MultiEchoSimulator # %% @@ -258,7 +258,7 @@ class RadialEncoding(LinearPhysics): """``(batch, echoes, x, y)`` images to k-space, one trajectory per echo. This is the whole of ``P F C`` for this experiment, and none of it is - TorchSim's: it wraps mri-nufft, which is what a real pipeline would do + BlochSim's: it wraps mri-nufft, which is what a real pipeline would do with its own trajectory, its own density compensation and its own coils. """ @@ -335,7 +335,7 @@ def A_adjoint(self, y, **kwargs): # Estimator and subspace basis # ---------------------------- # -# One :class:`~torchsim.DictionaryMatcher` states the problem and serves every +# One :class:`~blochsim.DictionaryMatcher` states the problem and serves every # route. Asking it for a rank fits a temporal basis to the training signals; # that basis is what the subspace reconstruction is given, and the coefficients # it returns come back to the same mapping. Three directions hold essentially @@ -503,6 +503,6 @@ def report(name, seconds, found): # because the basis must span the product of the ranges. Both cases put the # model inside the operator, which is the nonlinear route. # -# The rank is not a guess: :attr:`~torchsim.Subspace.retained` says what a +# The rank is not a guess: :attr:`~blochsim.Subspace.retained` says what a # basis keeps before anything is projected through it. # diff --git a/examples/04-model-based-imaging/02-nonlinear-inversion.py b/examples/04-model-based-imaging/02-nonlinear-inversion.py index 33e1587d..da26b708 100644 --- a/examples/04-model-based-imaging/02-nonlinear-inversion.py +++ b/examples/04-model-based-imaging/02-nonlinear-inversion.py @@ -17,7 +17,7 @@ -- sampling, Fourier encoding, coil sensitivities, and the **signal model** -- and the parameter maps are solved for directly against the k-space that was measured. Only the last factor changes with the sequence, and it is the only -one TorchSim supplies: :class:`~torchsim.recon.ModelOperator` turns any +one BlochSim supplies: :class:`~blochsim.recon.ModelOperator` turns any simulator into it, and the encoding comes from mri-nufft. Unlike a subspace this stays nonlinear, so it needs a starting guess and a loop @@ -34,7 +34,7 @@ # .. colab-link:: # :needs_gpu: 1 # -# !pip install torchsim brainweb-dl cmap mri-nufft[finufft,cufinufft] deepinv +# !pip install blochsim brainweb-dl cmap mri-nufft[finufft,cufinufft] deepinv # %% # @@ -137,12 +137,12 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): # %% # -# The Fourier encoding is not TorchSim's and never will be. ``mri-nufft`` +# The Fourier encoding is not BlochSim's and never will be. ``mri-nufft`` # supplies the radial trajectory and the non-uniform transform that plays it; # ``deepinv`` supplies the :class:`~deepinv.physics.LinearPhysics` base class # the encoding operator is written against, and the linear solver a # Gauss-Newton step hands its linearized problem to. Anything exposing ``A`` -# and ``A_adjoint`` composes with what TorchSim supplies. +# and ``A_adjoint`` composes with what BlochSim supplies. # import mrinufft from deepinv.physics import LinearPhysics @@ -150,8 +150,8 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): # %% # -# From TorchSim: the sequence, the estimator the contrast-then-fit routes -# need, and :class:`~torchsim.recon.ModelOperator`, which is the signal +# From BlochSim: the sequence, the estimator the contrast-then-fit routes +# need, and :class:`~blochsim.recon.ModelOperator`, which is the signal # model as a factor of the forward operator. # import time @@ -159,9 +159,9 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): import numpy as np import torch -from torchsim.estimators import DictionaryMatcher -from torchsim.recon import GaussNewton, ModelOperator, Schedule, iterative -from torchsim.simulators import MultiEchoSimulator +from blochsim.estimators import DictionaryMatcher +from blochsim.recon import GaussNewton, ModelOperator, Schedule, iterative +from blochsim.simulators import MultiEchoSimulator # %% @@ -236,7 +236,7 @@ def resampled(values): # A multi-echo spin echo on a golden-angle radial trajectory that rotates # between echoes. Sixteen spokes per echo across a 96-sample matrix is roughly # ninefold undersampled. The protocol stays on the host; -# :class:`~torchsim.recon.ModelOperator` takes it wherever the maps are. +# :class:`~blochsim.recon.ModelOperator` takes it wherever the maps are. # TE = torch.linspace(10.0, 150.0, ECHOES) simulator = MultiEchoSimulator(TE=TE) @@ -263,7 +263,7 @@ class RadialEncoding(LinearPhysics): """``(batch, echoes, x, y)`` images to k-space, one trajectory per echo. This is the whole of ``P F C`` for this experiment, and none of it is - TorchSim's: it wraps mri-nufft, which is what a real pipeline would do + BlochSim's: it wraps mri-nufft, which is what a real pipeline would do with its own trajectory, its own density compensation and its own coils. """ @@ -425,8 +425,8 @@ def report(name, seconds, found): # %% # # An iteratively regularized Gauss-Newton: linearize, solve, step, lower the -# damping. TorchSim supplies the loop and the derivative but not the linear -# solver -- :func:`~torchsim.recon.iterative` hands the linearized problem to +# damping. BlochSim supplies the loop and the derivative but not the linear +# solver -- :func:`~blochsim.recon.iterative` hands the linearized problem to # the same deepinv routine the baseline called. A proximal solver under a # wavelet prior is a change to that one argument. # @@ -538,7 +538,7 @@ def timed(call, repeats=5): # ------------------------- # # The model is the only thing above that names a relaxation time, and it is an -# ordinary :class:`~torchsim.model.Simulator` -- the same object the fitting +# ordinary :class:`~blochsim.model.Simulator` -- the same object the fitting # and sequence-design notebooks use. Water-fat separation, T2* with a field # map, a Look-Locker inversion recovery: each is a different ``evaluate``, and # the operator, the loop and the encoding are unchanged. diff --git a/examples/05-misc/01-synthetic-data.py b/examples/05-misc/01-synthetic-data.py index bf839f0b..3c0e967c 100644 --- a/examples/05-misc/01-synthetic-data.py +++ b/examples/05-misc/01-synthetic-data.py @@ -14,7 +14,7 @@ fully sampled one are the pair, with the ground-truth maps and the segmentation. -Only the simulation is TorchSim's. The phantom, the coils and the encoding come +Only the simulation is BlochSim's. The phantom, the coils and the encoding come from torchio, deepmriprep, SigPy and mri-nufft. """ @@ -22,11 +22,11 @@ # .. colab-link:: # :needs_gpu: 1 # -# !pip install torchsim torchio deepmriprep sigpy cmap mri-nufft[finufft,cufinufft] +# !pip install blochsim torchio deepmriprep sigpy cmap mri-nufft[finufft,cufinufft] # %% # -# Only one step of this pipeline is TorchSim's. Four other packages do the +# Only one step of this pipeline is BlochSim's. Four other packages do the # rest, and each has exactly one job here: # # * ``torchio`` fetches a T1-weighted IXI subject and gives it as tensors, @@ -152,7 +152,7 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): # %% # -# TorchSim's part is the third step: one fingerprinting simulation per tissue +# BlochSim's part is the third step: one fingerprinting simulation per tissue # class, rather than one per voxel. # import tempfile @@ -162,7 +162,7 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): import numpy as np import torch -from torchsim.simulators import MRFSimulator +from blochsim.simulators import MRFSimulator # %% @@ -194,7 +194,7 @@ def canvas(rows, columns, shape, *, bars=1, extra=0.6): # ``download=True`` fetches the archive once, a few hundred MB, into a cache # under the home directory. # -CACHE = Path.home() / ".cache" / "torchsim" / "ixi-tiny" +CACHE = Path.home() / ".cache" / "blochsim" / "ixi-tiny" subject = tio.datasets.IXITiny(str(CACHE), download=True)[0] @@ -311,7 +311,7 @@ def slab(image): # -------------------- # # An inversion, then four hundred repetitions whose flip angle sweeps. -# :class:`~torchsim.simulators.MRFSimulator` takes arrays of tissue properties, +# :class:`~blochsim.simulators.MRFSimulator` takes arrays of tissue properties, # so the whole table is one call: three extended phase graph runs rather than # sixteen thousand. # diff --git a/examples/_gallery_header.md b/examples/_gallery_header.md index 537c7f58..29e1f5b1 100644 --- a/examples/_gallery_header.md +++ b/examples/_gallery_header.md @@ -11,16 +11,16 @@ and uses the same public interfaces documented in the API reference. Read the four **Framework** lessons in order. They are the shortest path from a shipped sequence to one of your own: -1. **Getting started** — run a shipped {class}`torchsim.model.Simulator`, +1. **Getting started** — run a shipped {class}`blochsim.model.Simulator`, inspect its sequence description, differentiate the signal, and see where execution happens. 2. **Expanded physics** — turn on off-resonance, transmit variation, diffusion, flow, exchange and magnetization transfer without changing the sequence abstraction. 3. **Writing a simulator** — the extension point of the framework: - subclass {class}`torchsim.model.Simulator`, choose the command handlers + subclass {class}`blochsim.model.Simulator`, choose the command handlers that interpret an incoming Pulseq/MRD event stream, and implement - {meth}`torchsim.model.Simulator.layout` for offline construction. + {meth}`blochsim.model.Simulator.layout` for offline construction. 4. **Custom operator** — package a preparation or readout as an operator while leaving the state-machine kernels untouched. diff --git a/pyproject.toml b/pyproject.toml index 0cf97ded..10c0508c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ["scikit-build-core>=0.10", "setuptools-scm>=8"] [project] -name = "torchsim" +name = "blochsim" description = "A feature-rich MR simulator supporting massive parallelization on GPU and automatic differentiation." readme = "README.md" @@ -62,7 +62,7 @@ pulseq = [ ] # What it takes to run the suite. Nothing here is imported by the package. test = [ -"torchsim[pulseq]", +"blochsim[pulseq]", "pytest>=7.0", "pytest-cov", "pytest-sugar", @@ -75,7 +75,7 @@ test = [ doc = [ # The explanation figures and the getting-started example read the shipped # ``.seq`` file, which pypulseq parses. -"torchsim[pulseq]", +"blochsim[pulseq]", "sphinx>=7.0", "sphinx_book_theme>=1.1", "sphinx-design>=0.6", @@ -92,7 +92,7 @@ doc = [ # phantoms, the segmentation, the coils, the Fourier encoding and the linear # solvers all come from elsewhere, which is the point several examples make. examples = [ -"torchsim[pulseq]", +"blochsim[pulseq]", "brainweb-dl>=0.3", "deepinv>=0.2", "deepmriprep>=0.3", @@ -102,7 +102,7 @@ examples = [ ] # The whole toolchain in one command, which is what a contributor installs. dev = [ -"torchsim[test,doc,examples]", +"blochsim[test,doc,examples]", "ruff>=0.14", "mypy", "pre-commit", @@ -111,23 +111,23 @@ dev = [ # List URLs that are relevant to your project # This field corresponds to the "Project-URL" and "Home-Page" metadata fields: [project.urls] # Optional -"Homepage" = "https://github.com/pulserver/torchsim" -"Documentation" = "https://pulserver.github.io/torchsim/" -"Bug Reports" = "https://github.com/pulserver/torchsim/issues" -"Source" = "https://github.com/pulserver/torchsim" +"Homepage" = "https://github.com/pulserver/blochsim" +"Documentation" = "https://pulserver.github.io/blochsim/" +"Bug Reports" = "https://github.com/pulserver/blochsim/issues" +"Source" = "https://github.com/pulserver/blochsim" [tool.scikit-build] minimum-version = "build-system.requires" build-dir = "build/{wheel_tag}" cmake.version = ">=3.26" -wheel.packages = ["src/torchsim"] +wheel.packages = ["src/blochsim"] # One wheel per platform rather than one per interpreter: the kernels are built # against the CPython stable ABI from 3.10 on, so the tag is cp310-abi3. wheel.py-api = "cp310" # What ships is the compiled kernel, not the source it was compiled from. wheel.exclude = ["*.cpp", "*.hpp"] metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" -sdist.include = ["src/torchsim/_version.py"] +sdist.include = ["src/blochsim/_version.py"] # One wheel per platform, built by cibuildwheel in .github/workflows/wheels.yml. [tool.cibuildwheel] @@ -159,7 +159,7 @@ archs = ["arm64", "x86_64"] archs = ["AMD64"] [tool.setuptools_scm] -write_to = "src/torchsim/_version.py" +write_to = "src/blochsim/_version.py" version_scheme = "python-simplified-semver" local_scheme="no-local-version" fallback_version="v99-dev" @@ -167,7 +167,7 @@ fallback_version="v99-dev" [tool.pytest.ini_options] minversion = "6.0" addopts = [ - "--cov=torchsim", + "--cov=blochsim", "--cov-report=term-missing", "--cov-report=xml", "-m", "not interpreted" @@ -186,7 +186,7 @@ markers = [ required-version = ">=0.14" src = ["src", "tests"] line-length = 88 -extend-exclude = ["docs/generated", "src/torchsim/_version.py"] +extend-exclude = ["docs/generated", "src/blochsim/_version.py"] [tool.ruff.format] quote-style = "double" @@ -206,7 +206,7 @@ ignore = [ "D401", "D404", # Imperative mode is not necessary everywhere, trust me. "E501", # line length is the formatter's to decide, not the linter's - # Every collaborator TorchSim takes at a boundary is duck-typed on purpose: + # Every collaborator BlochSim takes at a boundary is duck-typed on purpose: # an acquisition is anything with ``simulate``, an encoding anything with # ``A`` and ``A_adjoint``, and naming a class there would close the door # this package exists to hold open. The docstring says what is expected. @@ -227,10 +227,10 @@ ignore = [ "scripts/**" = ["ANN", "D1"] # A Triton kernel's parameters are compile-time constants and tensors the # compiler types itself; an annotation on one says nothing Python will read. -"src/torchsim/**/*_triton.py" = ["ANN"] +"src/blochsim/**/*_triton.py" = ["ANN"] [tool.ruff.lint.isort] -known-first-party = ["torchsim"] +known-first-party = ["blochsim"] [tool.ruff.lint.pydocstyle] convention = "numpy" diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh index cc07ca04..48e66604 100755 --- a/scripts/build_docs.sh +++ b/scripts/build_docs.sh @@ -6,7 +6,7 @@ # bash scripts/build_docs.sh --clean # re-run every example from scratch # # The examples are executed by sphinx-gallery, so this needs an interpreter -# that can import torchsim as well as the documentation extensions. Set +# that can import blochsim as well as the documentation extensions. Set # PYTHON_BIN to choose one. # set -euo pipefail @@ -34,7 +34,7 @@ def absent(name): missing = [ name - for name in ("torchsim", "torchsim._epg_cpu", "torch", "matplotlib", + for name in ("blochsim", "blochsim._epg_cpu", "torch", "matplotlib", "sphinx", "sphinx_gallery", "sphinx_copybutton", "sphinx_exec_directive", "sphinx_book_theme", "myst_parser", "linkify_it", "pypulseq") @@ -53,7 +53,7 @@ if [ "$clean" -eq 1 ]; then rm -rf "$root/docs/build" "$root/docs/generated" fi -# The pages are built against the installed TorchSim, kernels and all. Put +# The pages are built against the installed BlochSim, kernels and all. Put # ``src`` on the path instead and the compiled halves of the package are gone: # a figure the fused kernel draws then fails the build. cd "$root/docs" diff --git a/scripts/check_wheel.py b/scripts/check_wheel.py index 6637cc9c..48ef33ce 100644 --- a/scripts/check_wheel.py +++ b/scripts/check_wheel.py @@ -2,7 +2,7 @@ Run by cibuildwheel against every wheel it builds, on the interpreter that wheel claims to support. It loads each extension by file path rather than by -importing :mod:`torchsim`, so the check needs no PyTorch and says something +importing :mod:`blochsim`, so the check needs no PyTorch and says something about the binary alone: that the stable-ABI module initialises on this interpreter, and that it carries no vendored library. @@ -22,9 +22,9 @@ def kernel_directory() -> pathlib.Path: """Where the installed package lives, without executing it.""" - spec = importlib.util.find_spec("torchsim") + spec = importlib.util.find_spec("blochsim") if spec is None or not spec.submodule_search_locations: - raise SystemExit("torchsim is not installed") + raise SystemExit("blochsim is not installed") return pathlib.Path(next(iter(spec.submodule_search_locations))) @@ -38,7 +38,7 @@ def main() -> int: for path in kernels: name = path.name.split(".")[0] - spec = importlib.util.spec_from_file_location(f"torchsim.{name}", path) + spec = importlib.util.spec_from_file_location(f"blochsim.{name}", path) if spec is None or spec.loader is None: raise SystemExit(f"no loader for {path}") module = importlib.util.module_from_spec(spec) diff --git a/scripts/make_artwork.py b/scripts/make_artwork.py new file mode 100644 index 00000000..e33de43e --- /dev/null +++ b/scripts/make_artwork.py @@ -0,0 +1,161 @@ +"""Write the blochsim logo and its mark into ``docs/_static``. + + python scripts/make_artwork.py + +The mark is a Bloch sphere: the magnetization vector, tipped onto the +transverse plane, at the end of the precessing path an excitation traces from +the longitudinal axis. The logo is the mark followed by the wordmark. Each +image is written twice, ``.svg`` for a light background and +``-dark.svg`` for a dark one. The wordmark is SVG text in the reader's +sans-serif font; no font is embedded. +""" + +from __future__ import annotations + +import math +from pathlib import Path + +OUT = Path(__file__).resolve().parent.parent / "docs" / "_static" + +#: The colours of each theme, by role: the pulserver family's blue and amber. +PALETTE = { + "light": { + "ink": "#12212b", + "muted": "#8a9aa8", + "blue": "#2b76ad", + "amber": "#f0a500", + }, + "dark": { + "ink": "#e8eef3", + "muted": "#6f8394", + "blue": "#5b9bd0", + "amber": "#ffbe2e", + }, +} + +#: Sphere radius, and the elevation the sphere is seen from, in radians. +RADIUS = 46.0 +ELEVATION = math.radians(22) + +#: Precession turns of the excitation path, and the azimuth it ends at. +TURNS = 2.75 +END_AZIMUTH = math.radians(-35) + + +def _project(x: float, y: float, z: float) -> tuple[float, float, bool]: + """Screen position of a point on the sphere, and whether it faces the viewer.""" + up = z * math.cos(ELEVATION) + y * math.sin(ELEVATION) + toward = -y * math.cos(ELEVATION) + z * math.sin(ELEVATION) + return x, -up, toward >= 0 + + +def _path() -> list[tuple[float, float, bool]]: + """The tip of the magnetization during excitation: tip angle 0 to 90 degrees.""" + points = [] + n = 400 + for i in range(n + 1): + t = i / n + tip = 0.5 * math.pi * t + azimuth = END_AZIMUTH - 2 * math.pi * TURNS * (1 - t) + r = RADIUS * math.sin(tip) + points.append( + _project( + r * math.cos(azimuth), r * math.sin(azimuth), RADIUS * math.cos(tip) + ) + ) + return points + + +def _runs(points: list[tuple[float, float, bool]]) -> list[tuple[bool, str]]: + """The path split into runs in front of and behind the sphere's centre.""" + runs: list[tuple[bool, list[tuple[float, float]]]] = [] + for x, y, front in points: + if not runs or runs[-1][0] != front: + runs.append((front, [runs[-1][1][-1]] if runs else [])) + runs[-1][1].append((x, y)) + return [ + (front, "M" + " L".join(f"{x:.2f} {y:.2f}" for x, y in pts)) + for front, pts in runs + ] + + +def _mark(colours: dict[str, str]) -> str: + """The Bloch sphere, centred on the origin, as SVG elements.""" + ry = RADIUS * math.sin(ELEVATION) + tip_x, tip_y, _ = _path()[-1] + length = math.hypot(tip_x, tip_y) + ux, uy = tip_x / length, tip_y / length + shaft_x, shaft_y = tip_x - 9 * ux, tip_y - 9 * uy + head = ( + f"{tip_x:.2f},{tip_y:.2f} " + f"{shaft_x - 6 * uy:.2f},{shaft_y + 6 * ux:.2f} " + f"{shaft_x + 6 * uy:.2f},{shaft_y - 6 * ux:.2f}" + ) + parts = [ + f'', + f'', + f'', + f'', + ] + for front, d in _runs(_path()): + opacity = "1" if front else "0.4" + parts.append( + f'' + ) + parts += [ + f'', + f'', + f'', + ] + return "\n".join(parts) + + +def _svg(view_box: str, title: str, desc: str, body: str) -> str: + return ( + f'\n' + f'{title}\n{desc}\n' + f"{body}\n\n" + ) + + +def mark(colours: dict[str, str]) -> str: + """The compact mark, used in the sidebar and as the favicon.""" + return _svg( + "0 0 112 112", + "blochsim", + "A Bloch sphere with the magnetization vector at the end of its precessing excitation path", + f'\n{_mark(colours)}\n', + ) + + +def logo(colours: dict[str, str]) -> str: + """The mark followed by the wordmark.""" + word = ( + '' + f'bloch' + f'sim' + ) + return _svg( + "0 0 520 128", + "blochsim", + "A Bloch sphere with a precessing magnetization vector, followed by the wordmark blochsim", + f'\n{_mark(colours)}\n\n{word}', + ) + + +def main() -> None: + OUT.mkdir(parents=True, exist_ok=True) + for theme, colours in PALETTE.items(): + suffix = "" if theme == "light" else "-dark" + (OUT / f"blochsim-mark{suffix}.svg").write_text(mark(colours)) + (OUT / f"blochsim-logo{suffix}.svg").write_text(logo(colours)) + + +if __name__ == "__main__": + main() diff --git a/scripts/publish_docs.py b/scripts/publish_docs.py index 958335f0..34bad259 100755 --- a/scripts/publish_docs.py +++ b/scripts/publish_docs.py @@ -34,7 +34,7 @@ - TorchSim documentation + BlochSim documentation @@ -130,7 +130,7 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("site", type=Path, help="a checkout of the site branch") parser.add_argument( "--url", - default="https://pulserver.github.io/torchsim", + default="https://pulserver.github.io/blochsim", help="where the site is served from", ) arguments = parser.parse_args(argv) diff --git a/src/torchsim/__init__.py b/src/blochsim/__init__.py similarity index 96% rename from src/torchsim/__init__.py rename to src/blochsim/__init__.py index d7cc2ed1..2041b0ec 100644 --- a/src/torchsim/__init__.py +++ b/src/blochsim/__init__.py @@ -1,4 +1,4 @@ -"""Main TorchSim API.""" +"""Main BlochSim API.""" __all__ = [] diff --git a/src/torchsim/_bounds.py b/src/blochsim/_bounds.py similarity index 100% rename from src/torchsim/_bounds.py rename to src/blochsim/_bounds.py diff --git a/src/torchsim/_epg_cpu.cpp b/src/blochsim/_epg_cpu.cpp similarity index 99% rename from src/torchsim/_epg_cpu.cpp rename to src/blochsim/_epg_cpu.cpp index 4aa7fbae..da2fe903 100644 --- a/src/torchsim/_epg_cpu.cpp +++ b/src/blochsim/_epg_cpu.cpp @@ -24,11 +24,11 @@ // MSVC rejects the GNU spelling of both of these outright, and the kernel is // built by whichever compiler the platform ships. #if defined(_MSC_VER) -#define TORCHSIM_ALWAYS_INLINE __forceinline -#define TORCHSIM_RESTRICT __restrict +#define BLOCHSIM_ALWAYS_INLINE __forceinline +#define BLOCHSIM_RESTRICT __restrict #else -#define TORCHSIM_ALWAYS_INLINE __attribute__((always_inline)) inline -#define TORCHSIM_RESTRICT __restrict__ +#define BLOCHSIM_ALWAYS_INLINE __attribute__((always_inline)) inline +#define BLOCHSIM_RESTRICT __restrict__ #endif namespace { @@ -82,10 +82,10 @@ struct TissueLayout { } }; -using torchsim::MIN_WORK_PER_THREAD; -using torchsim::usable_processors; -using torchsim::WorkerPool; -using torchsim::worker_count; +using blochsim::MIN_WORK_PER_THREAD; +using blochsim::usable_processors; +using blochsim::WorkerPool; +using blochsim::worker_count; // Which optional terms a launch carries. A property the caller never passed // is not an input, so its term is out and its gradient is zero -- the same @@ -2467,7 +2467,7 @@ inline void rotate( // clone is a copy of the caller, so a body reached through a call is // compiled once for the baseline instruction set and no more. template -TORCHSIM_ALWAYS_INLINE void simulate_jvp_range( +BLOCHSIM_ALWAYS_INLINE void simulate_jvp_range( const JvpBuffers& buffers, const std::int64_t work_begin, const std::int64_t work_end, @@ -3004,7 +3004,7 @@ TORCHSIM_ALWAYS_INLINE void simulate_jvp_range( } } -#if TORCHSIM_MULTIVERSIONED +#if BLOCHSIM_MULTIVERSIONED __attribute__((target_clones("default", "sse4.2", "avx2", "avx512f"))) #endif inline void shift_real( @@ -3301,13 +3301,13 @@ void simulate_real_jvp_range( constexpr std::size_t REAL_LANES = 8; -// Setting TORCHSIM_REAL_SCALAR=1 routes the real-subspace path through the +// Setting BLOCHSIM_REAL_SCALAR=1 routes the real-subspace path through the // one-train-at-a-time kernels instead. They compute the same thing, so this // selects between two implementations of one contract: useful for timing one // against the other, and as a way out if a host miscompiles the lane arithmetic. inline bool lane_kernels_enabled() { static const bool enabled = [] { - const char* const value = std::getenv("TORCHSIM_REAL_SCALAR"); + const char* const value = std::getenv("BLOCHSIM_REAL_SCALAR"); return value == nullptr || value[0] != '1'; }(); return enabled; @@ -3512,12 +3512,12 @@ void simulate_real_jvp_atom_lane_range( const std::size_t states = static_cast(state_count); const std::size_t width = states * REAL_LANES; std::vector storage(6U * width); - float* const TORCHSIM_RESTRICT plus = storage.data(); - float* const TORCHSIM_RESTRICT minus = plus + width; - float* const TORCHSIM_RESTRICT longitudinal = minus + width; - float* const TORCHSIM_RESTRICT dot_plus = longitudinal + width; - float* const TORCHSIM_RESTRICT dot_minus = dot_plus + width; - float* const TORCHSIM_RESTRICT dot_longitudinal = dot_minus + width; + float* const BLOCHSIM_RESTRICT plus = storage.data(); + float* const BLOCHSIM_RESTRICT minus = plus + width; + float* const BLOCHSIM_RESTRICT longitudinal = minus + width; + float* const BLOCHSIM_RESTRICT dot_plus = longitudinal + width; + float* const BLOCHSIM_RESTRICT dot_minus = dot_plus + width; + float* const BLOCHSIM_RESTRICT dot_longitudinal = dot_minus + width; float t1[REAL_LANES], t2[REAL_LANES], dot_t1[REAL_LANES], dot_t2[REAL_LANES]; float rate1[REAL_LANES], rate2[REAL_LANES]; @@ -3706,12 +3706,12 @@ void simulate_real_jvp_lane_range( const std::size_t states = static_cast(state_count); const std::size_t width = states * REAL_LANES; std::vector storage(6U * width); - float* const TORCHSIM_RESTRICT plus = storage.data(); - float* const TORCHSIM_RESTRICT minus = plus + width; - float* const TORCHSIM_RESTRICT longitudinal = minus + width; - float* const TORCHSIM_RESTRICT dot_plus = longitudinal + width; - float* const TORCHSIM_RESTRICT dot_minus = dot_plus + width; - float* const TORCHSIM_RESTRICT dot_longitudinal = dot_minus + width; + float* const BLOCHSIM_RESTRICT plus = storage.data(); + float* const BLOCHSIM_RESTRICT minus = plus + width; + float* const BLOCHSIM_RESTRICT longitudinal = minus + width; + float* const BLOCHSIM_RESTRICT dot_plus = longitudinal + width; + float* const BLOCHSIM_RESTRICT dot_minus = dot_plus + width; + float* const BLOCHSIM_RESTRICT dot_longitudinal = dot_minus + width; float dt[REAL_LANES], dt_dot[REAL_LANES]; float flip[REAL_LANES], flip_dot[REAL_LANES]; @@ -4837,7 +4837,7 @@ inline void shift_adjoint(DualState& fplus_bar, DualState& fminus_bar) { // clone is a copy of the caller, so a body reached through a call is // compiled once for the baseline instruction set and no more. template -TORCHSIM_ALWAYS_INLINE void simulate_vjp_range( +BLOCHSIM_ALWAYS_INLINE void simulate_vjp_range( const VjpBuffers& buffers, const std::int64_t work_begin, const std::int64_t work_end, @@ -6304,12 +6304,12 @@ void simulate_real_vjp_atom_lane_range( static_cast(event_count) * stride ); std::vector storage(6U * width); - float* const TORCHSIM_RESTRICT plus = storage.data(); - float* const TORCHSIM_RESTRICT minus = plus + width; - float* const TORCHSIM_RESTRICT longitudinal = minus + width; - float* const TORCHSIM_RESTRICT plus_bar = longitudinal + width; - float* const TORCHSIM_RESTRICT minus_bar = plus_bar + width; - float* const TORCHSIM_RESTRICT longitudinal_bar = minus_bar + width; + float* const BLOCHSIM_RESTRICT plus = storage.data(); + float* const BLOCHSIM_RESTRICT minus = plus + width; + float* const BLOCHSIM_RESTRICT longitudinal = minus + width; + float* const BLOCHSIM_RESTRICT plus_bar = longitudinal + width; + float* const BLOCHSIM_RESTRICT minus_bar = plus_bar + width; + float* const BLOCHSIM_RESTRICT longitudinal_bar = minus_bar + width; float t1[REAL_LANES], t2[REAL_LANES], m0[REAL_LANES], b1[REAL_LANES]; float efficiency[REAL_LANES]; @@ -7757,7 +7757,7 @@ inline DualFloat shim_dual( // clone is a copy of the caller, so a body reached through a call is // compiled once for the baseline instruction set and no more. template -TORCHSIM_ALWAYS_INLINE void simulate_vjp_jvp_range( +BLOCHSIM_ALWAYS_INLINE void simulate_vjp_jvp_range( const VjpJvpBuffers& buffers, const std::int64_t work_begin, const std::int64_t work_end, @@ -10505,7 +10505,7 @@ void simulate_pooled_vjp_jvp_range( // that is what the ``always_inline`` on the range kernels is for. Taking the // address of one still emits an out-of-line copy, which is what the two-pool // dispatch selects. -#if TORCHSIM_MULTIVERSIONED +#if BLOCHSIM_MULTIVERSIONED #define CLONED_KERNEL \ __attribute__((target_clones("default", "sse4.2", "avx2", "avx512f"))) #else @@ -10835,12 +10835,12 @@ PyObject* simulate(PyObject*, PyObject* arguments) { return nullptr; } - // TORCHSIM_LANES=1 selects a lane-vectorized forward that walks the + // BLOCHSIM_LANES=1 selects a lane-vectorized forward that walks the // (atom, train block) product instead of (atom, train), putting a block of // trains in the SIMD lanes. It is opt-in: it is measurably faster only for // very wide batches, and it does not agree bit for bit with the scalar // kernel, so enabling it changes results in the last place. - const char* const lane_override = std::getenv("TORCHSIM_LANES"); + const char* const lane_override = std::getenv("BLOCHSIM_LANES"); const bool lanes_enabled = lane_override != nullptr && lane_override[0] == '1'; const bool vectorize = lanes_enabled && train_count >= 4 && buffers.shim_count == 1 @@ -11781,7 +11781,7 @@ PyMODINIT_FUNC PyInit__epg_cpu() { // What a caller cannot see from the outside: whether this build has // several instruction sets to pick a kernel from, and so whether the // vector state a pool job leaves behind is a thing that exists here. - if (PyModule_AddIntConstant(created, "multiversioned", TORCHSIM_MULTIVERSIONED) + if (PyModule_AddIntConstant(created, "multiversioned", BLOCHSIM_MULTIVERSIONED) < 0) { Py_DECREF(created); return nullptr; diff --git a/src/torchsim/_execution.py b/src/blochsim/_execution.py similarity index 100% rename from src/torchsim/_execution.py rename to src/blochsim/_execution.py diff --git a/src/torchsim/_functional/__init__.py b/src/blochsim/_functional/__init__.py similarity index 100% rename from src/torchsim/_functional/__init__.py rename to src/blochsim/_functional/__init__.py diff --git a/src/torchsim/_functional/_bssfp.py b/src/blochsim/_functional/_bssfp.py similarity index 100% rename from src/torchsim/_functional/_bssfp.py rename to src/blochsim/_functional/_bssfp.py diff --git a/src/torchsim/_functional/_fse.py b/src/blochsim/_functional/_fse.py similarity index 100% rename from src/torchsim/_functional/_fse.py rename to src/blochsim/_functional/_fse.py diff --git a/src/torchsim/_functional/_mp2rage.py b/src/blochsim/_functional/_mp2rage.py similarity index 100% rename from src/torchsim/_functional/_mp2rage.py rename to src/blochsim/_functional/_mp2rage.py diff --git a/src/torchsim/_functional/_mpnrage.py b/src/blochsim/_functional/_mpnrage.py similarity index 100% rename from src/torchsim/_functional/_mpnrage.py rename to src/blochsim/_functional/_mpnrage.py diff --git a/src/torchsim/_functional/_mprage.py b/src/blochsim/_functional/_mprage.py similarity index 100% rename from src/torchsim/_functional/_mprage.py rename to src/blochsim/_functional/_mprage.py diff --git a/src/torchsim/_functional/_mrf.py b/src/blochsim/_functional/_mrf.py similarity index 100% rename from src/torchsim/_functional/_mrf.py rename to src/blochsim/_functional/_mrf.py diff --git a/src/torchsim/_functional/_run.py b/src/blochsim/_functional/_run.py similarity index 100% rename from src/torchsim/_functional/_run.py rename to src/blochsim/_functional/_run.py diff --git a/src/torchsim/_functional/_spgr.py b/src/blochsim/_functional/_spgr.py similarity index 100% rename from src/torchsim/_functional/_spgr.py rename to src/blochsim/_functional/_spgr.py diff --git a/src/torchsim/_ifunc.hpp b/src/blochsim/_ifunc.hpp similarity index 83% rename from src/torchsim/_ifunc.hpp rename to src/blochsim/_ifunc.hpp index a4b057e3..0359bbc5 100644 --- a/src/torchsim/_ifunc.hpp +++ b/src/blochsim/_ifunc.hpp @@ -1,5 +1,5 @@ -#ifndef TORCHSIM_IFUNC_HPP -#define TORCHSIM_IFUNC_HPP +#ifndef BLOCHSIM_IFUNC_HPP +#define BLOCHSIM_IFUNC_HPP // ``__GLIBC__`` comes from a libc header rather than from the compiler, so one // has to have been included before the test below can read it. @@ -13,9 +13,9 @@ // announces itself with no macro of its own -- so the test is for the glibc // whose absence on Linux leaves only musl. #if defined(__linux__) && !defined(__GLIBC__) -#define TORCHSIM_HAS_IFUNC 0 +#define BLOCHSIM_HAS_IFUNC 0 #else -#define TORCHSIM_HAS_IFUNC 1 +#define BLOCHSIM_HAS_IFUNC 1 #endif // Whether the EPG kernels are compiled once per instruction set. It takes the @@ -29,11 +29,11 @@ // build that does not has one code path and nothing to clear, so the module // reports this and a caller measuring the difference knows whether there is // one to measure. -#if defined(__GNUC__) && !defined(__clang__) && TORCHSIM_HAS_IFUNC \ +#if defined(__GNUC__) && !defined(__clang__) && BLOCHSIM_HAS_IFUNC \ && (defined(__x86_64__) || defined(__i386__)) -#define TORCHSIM_MULTIVERSIONED 1 +#define BLOCHSIM_MULTIVERSIONED 1 #else -#define TORCHSIM_MULTIVERSIONED 0 +#define BLOCHSIM_MULTIVERSIONED 0 #endif -#endif // TORCHSIM_IFUNC_HPP +#endif // BLOCHSIM_IFUNC_HPP diff --git a/src/torchsim/_perk_cpu.cpp b/src/blochsim/_perk_cpu.cpp similarity index 97% rename from src/torchsim/_perk_cpu.cpp rename to src/blochsim/_perk_cpu.cpp index f7cf1ff1..e9138f11 100644 --- a/src/torchsim/_perk_cpu.cpp +++ b/src/blochsim/_perk_cpu.cpp @@ -30,16 +30,16 @@ // the function per instruction set and the dynamic linker picks the copy this // processor can run. #if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__)) \ - && TORCHSIM_HAS_IFUNC -#define TORCHSIM_VECTORIZED __attribute__((target_clones("avx2", "default"))) + && BLOCHSIM_HAS_IFUNC +#define BLOCHSIM_VECTORIZED __attribute__((target_clones("avx2", "default"))) #else -#define TORCHSIM_VECTORIZED +#define BLOCHSIM_VECTORIZED #endif namespace { -using torchsim::WorkerPool; -using torchsim::worker_count; +using blochsim::WorkerPool; +using blochsim::worker_count; // Voxels per block, and features per block. The feature block is what has to // stay in cache across the contrast loop. @@ -101,7 +101,7 @@ struct Problem { // and the feature innermost, the inner statement is an AXPY along contiguous // memory that a compiler vectorizes, where the natural order -- a dot product // per feature -- is a reduction it cannot. -TORCHSIM_VECTORIZED inline void angles( +BLOCHSIM_VECTORIZED inline void angles( const Problem& problem, const std::int64_t begin, const std::int64_t width, @@ -130,7 +130,7 @@ TORCHSIM_VECTORIZED inline void angles( } // One block of voxels, from signal to parameters. -TORCHSIM_VECTORIZED void regress_block( +BLOCHSIM_VECTORIZED void regress_block( const Problem& problem, const std::int64_t begin, const std::int64_t end, @@ -186,7 +186,7 @@ TORCHSIM_VECTORIZED void regress_block( // The derivative of one block with respect to its signals. The angle is // rebuilt rather than kept, which is the same trade the forward pass makes. -TORCHSIM_VECTORIZED void regress_vjp_block( +BLOCHSIM_VECTORIZED void regress_vjp_block( const Problem& problem, const std::int64_t begin, const std::int64_t end, @@ -392,7 +392,7 @@ PyMethodDef methods[] = { }; PyModuleDef module = { - PyModuleDef_HEAD_INIT, "torchsim._perk_cpu", nullptr, -1, methods, + PyModuleDef_HEAD_INIT, "blochsim._perk_cpu", nullptr, -1, methods, nullptr, nullptr, nullptr, nullptr, }; diff --git a/src/torchsim/_subspace.py b/src/blochsim/_subspace.py similarity index 98% rename from src/torchsim/_subspace.py rename to src/blochsim/_subspace.py index 830ccabf..3b06cf3d 100644 --- a/src/torchsim/_subspace.py +++ b/src/blochsim/_subspace.py @@ -34,7 +34,7 @@ class Subspace: dictionary : torch.Tensor, optional ``(atoms, contrasts)`` -- the signals the basis was fitted to, where they were simulated for the purpose. - :func:`~torchsim.simulate_subspace` fills this in; + :func:`~blochsim.simulate_subspace` fills this in; :meth:`fit` leaves it out, since the caller already holds them. simulation : SimulationResult, optional What that simulation recorded, with the labels that say which sample diff --git a/src/torchsim/_threads.hpp b/src/blochsim/_threads.hpp similarity index 96% rename from src/torchsim/_threads.hpp rename to src/blochsim/_threads.hpp index bf4548b5..6dce0df6 100644 --- a/src/torchsim/_threads.hpp +++ b/src/blochsim/_threads.hpp @@ -1,8 +1,8 @@ -// Shared by every TorchSim CPU kernel: a worker pool that outlives the calls +// Shared by every BlochSim CPU kernel: a worker pool that outlives the calls // that use it, and the two questions of how many workers a piece of work can // actually feed. -#ifndef TORCHSIM_THREADS_HPP -#define TORCHSIM_THREADS_HPP +#ifndef BLOCHSIM_THREADS_HPP +#define BLOCHSIM_THREADS_HPP #include #include @@ -16,7 +16,7 @@ #include #endif -namespace torchsim { +namespace blochsim { // Leave the vector registers in the state a legacy-encoded caller expects. // @@ -164,6 +164,6 @@ inline unsigned int worker_count( return static_cast(std::max(1, count)); } -} // namespace torchsim +} // namespace blochsim -#endif // TORCHSIM_THREADS_HPP +#endif // BLOCHSIM_THREADS_HPP diff --git a/src/torchsim/estimators/__init__.py b/src/blochsim/estimators/__init__.py similarity index 95% rename from src/torchsim/estimators/__init__.py rename to src/blochsim/estimators/__init__.py index 017315b6..16d9b1eb 100644 --- a/src/torchsim/estimators/__init__.py +++ b/src/blochsim/estimators/__init__.py @@ -1,4 +1,4 @@ -"""Quantitative-MRI estimators built around TorchSim signal models. +"""Quantitative-MRI estimators built around BlochSim signal models. An estimator is made from the acquisition it inverts and whatever settings the method itself has. ``fit`` states which tissue properties are unknown and over diff --git a/src/torchsim/estimators/_dictionary.py b/src/blochsim/estimators/_dictionary.py similarity index 98% rename from src/torchsim/estimators/_dictionary.py rename to src/blochsim/estimators/_dictionary.py index 6736c1e3..42a114f7 100644 --- a/src/torchsim/estimators/_dictionary.py +++ b/src/blochsim/estimators/_dictionary.py @@ -55,11 +55,11 @@ class DictionaryMatcher(Estimator): Parameters ---------- acquisition : Simulator, optional - The sequence being inverted: a simulator that ships with TorchSim, one - written by subclassing :class:`~torchsim.model.Simulator`, or any other - :class:`~torchsim.model.Simulator`. Every tissue property that is + The sequence being inverted: a simulator that ships with BlochSim, one + written by subclassing :class:`~blochsim.model.Simulator`, or any other + :class:`~blochsim.model.Simulator`. Every tissue property that is neither unknown nor measured separately is fixed on it beforehand, with - the constructor or :meth:`~torchsim.model.Simulator.bind`. Leave it + the constructor or :meth:`~blochsim.model.Simulator.bind`. Leave it out to fit from signals handed to :meth:`fit` directly. dictionary : torch.Tensor, optional Simulated atoms shaped ``(n_atoms, n_contrasts)``. Leave it out and diff --git a/src/torchsim/estimators/_grouped.py b/src/blochsim/estimators/_grouped.py similarity index 100% rename from src/torchsim/estimators/_grouped.py rename to src/blochsim/estimators/_grouped.py diff --git a/src/torchsim/estimators/_lookup.py b/src/blochsim/estimators/_lookup.py similarity index 97% rename from src/torchsim/estimators/_lookup.py rename to src/blochsim/estimators/_lookup.py index 9f3f0c63..9354d45f 100644 --- a/src/torchsim/estimators/_lookup.py +++ b/src/blochsim/estimators/_lookup.py @@ -29,11 +29,11 @@ class LookupTable(Estimator): Parameters ---------- acquisition : Simulator, optional - The sequence being inverted: a simulator that ships with TorchSim, one - written by subclassing :class:`~torchsim.model.Simulator`, or any other - :class:`~torchsim.model.Simulator`. Every tissue property that is + The sequence being inverted: a simulator that ships with BlochSim, one + written by subclassing :class:`~blochsim.model.Simulator`, or any other + :class:`~blochsim.model.Simulator`. Every tissue property that is neither unknown nor measured separately is fixed on it beforehand, with - the constructor or :meth:`~torchsim.model.Simulator.bind`. Leave it + the constructor or :meth:`~blochsim.model.Simulator.bind`. Leave it out to fit from signals handed to :meth:`fit` directly. combine : callable, optional Reduce a model's contrasts to the one number the curve is in, called @@ -66,8 +66,8 @@ class LookupTable(Estimator): .. exec:: import torch - from torchsim.estimators import LookupTable - from torchsim.simulators import MP2RAGESimulator + from blochsim.estimators import LookupTable + from blochsim.simulators import MP2RAGESimulator protocol = dict( TI=(800.0, 2700.0), flip=(4.0, 5.0), diff --git a/src/torchsim/estimators/_mapping.py b/src/blochsim/estimators/_mapping.py similarity index 98% rename from src/torchsim/estimators/_mapping.py rename to src/blochsim/estimators/_mapping.py index 3aa452f0..40fd3670 100644 --- a/src/torchsim/estimators/_mapping.py +++ b/src/blochsim/estimators/_mapping.py @@ -45,8 +45,8 @@ class Estimator(torch.nn.Module): """What every mapping method is, and the machinery all of them share. - :class:`~torchsim.DictionaryMatcher`, :class:`~torchsim.LookupTable`, - :class:`~torchsim.NonlinearLeastSquares` and :class:`~torchsim.PERK` are + :class:`~blochsim.DictionaryMatcher`, :class:`~blochsim.LookupTable`, + :class:`~blochsim.NonlinearLeastSquares` and :class:`~blochsim.PERK` are all one of these. What they inherit is the whole problem statement -- :meth:`fit` naming the unknowns and drawing the training set from :attr:`acquisition`, :meth:`map` returning one named map per unknown -- @@ -152,14 +152,14 @@ def fit( what teaches a method how far to trust a measurement, so it should be the noise the scan actually has. rank : int, optional - Fit a temporal :class:`~torchsim.Subspace` of this rank to the + Fit a temporal :class:`~blochsim.Subspace` of this rank to the training signals and work in it. Every contrast dropped is arithmetic neither training nor mapping has to do; :attr:`subspace` reports what the compression kept. subspace : Subspace, optional Work in a basis fitted elsewhere rather than fitting one here -- the one another estimator carries, or one - :func:`~torchsim.simulate_subspace` produced. This is what lets a + :func:`~blochsim.simulate_subspace` produced. This is what lets a reconstruction and the estimator that reads its coefficients agree on the basis by construction. Not with ``rank``, which asks for a basis to be fitted. @@ -426,7 +426,7 @@ def uncertainty_of(self, volume: Any, known: Any = None) -> Any: It is not the error. An estimator that is biased is biased the same way in every realization, so repeating the measurement never reveals that part and this number does not contain it. Read against - :func:`~torchsim.crlb`, which is the lowest standard deviation an + :func:`~blochsim.crlb`, which is the lowest standard deviation an unbiased estimate could have, it says how much of the distance to the truth the acquisition is responsible for. @@ -551,7 +551,7 @@ def _spread( f"whose answer is a grid point moves in steps rather than " f"smoothly, so propagating the noise through it gives zero " f"rather than an error bar; PERK and NonlinearLeastSquares do " - f"state one, and torchsim.crlb bounds what any unbiased method " + f"state one, and blochsim.crlb bounds what any unbiased method " f"could reach on this sequence." ) return spread diff --git a/src/torchsim/estimators/_nlls.py b/src/blochsim/estimators/_nlls.py similarity index 94% rename from src/torchsim/estimators/_nlls.py rename to src/blochsim/estimators/_nlls.py index 8e97aa39..5a15207d 100644 --- a/src/torchsim/estimators/_nlls.py +++ b/src/blochsim/estimators/_nlls.py @@ -24,10 +24,10 @@ class NonlinearLeastSquares(Estimator): it gives up is the guarantee: it finds a local minimum of the residual, and which one depends on where it started. - This is an :class:`~torchsim.Estimator` face on - :class:`~torchsim.recon.GaussNewton`, and holds no algorithm of its own. + This is an :class:`~blochsim.Estimator` face on + :class:`~blochsim.recon.GaussNewton`, and holds no algorithm of its own. Fitting images voxel by voxel and reconstructing maps from k-space are the - same loop over the same :class:`~torchsim.recon.ModelOperator` with + same loop over the same :class:`~blochsim.recon.ModelOperator` with nothing encoding the voxels together, so what is here is only the adaptation: where the fit starts, and what the training set is for. @@ -39,11 +39,11 @@ class NonlinearLeastSquares(Estimator): Parameters ---------- acquisition : Simulator, optional - The sequence being inverted: a simulator that ships with TorchSim, one - written by subclassing :class:`~torchsim.model.Simulator`, or any other - :class:`~torchsim.model.Simulator`. Every tissue property that is + The sequence being inverted: a simulator that ships with BlochSim, one + written by subclassing :class:`~blochsim.model.Simulator`, or any other + :class:`~blochsim.model.Simulator`. Every tissue property that is neither unknown nor measured separately is fixed on it beforehand, with - the constructor or :meth:`~torchsim.model.Simulator.bind`. Leave it + the constructor or :meth:`~blochsim.model.Simulator.bind`. Leave it out to fit from signals handed to :meth:`fit` directly. bounds : mapping, optional ``{name: (low, high)}``, either end ``None`` for unbounded. A bound is @@ -58,8 +58,8 @@ class NonlinearLeastSquares(Estimator): loop : GaussNewton, optional The solve to run, and where every knob it has lives -- how many steps, how the damping moves, which tolerance stops a voxel. The default is - Levenberg-Marquardt: a :class:`~torchsim.recon.TrustRegion` over - :func:`~torchsim.recon.direct`, twenty steps. + Levenberg-Marquardt: a :class:`~blochsim.recon.TrustRegion` over + :func:`~blochsim.recon.direct`, twenty steps. Notes ----- @@ -249,7 +249,7 @@ def _uncertainty_arrays( A least-squares solution moves with the noise by as much as the model is insensitive to the parameters there, which is the inverse Fisher - matrix at the solution -- the same quantity :func:`~torchsim.crlb` + matrix at the solution -- the same quantity :func:`~blochsim.crlb` bounds an unbiased estimate by, read at the answer rather than at a truth nobody has. It is a linearization about the solution, so it is the standard error a fit reports and holds where the residual is small @@ -287,9 +287,9 @@ def _solve( ) -> torch.Tensor: """Levenberg-Marquardt over every voxel, compacting as they finish. - The loop is :class:`~torchsim.recon.GaussNewton` under a per-voxel + The loop is :class:`~blochsim.recon.GaussNewton` under a per-voxel trust region, and the model, its bounds and its derivative are a - :class:`~torchsim.recon.ModelOperator` -- the same two pieces a + :class:`~blochsim.recon.ModelOperator` -- the same two pieces a model-based reconstruction is built from, with nothing encoding the voxels together. """ diff --git a/src/torchsim/estimators/_perk.py b/src/blochsim/estimators/_perk.py similarity index 99% rename from src/torchsim/estimators/_perk.py rename to src/blochsim/estimators/_perk.py index 4a9d9fd0..135abf3b 100644 --- a/src/torchsim/estimators/_perk.py +++ b/src/blochsim/estimators/_perk.py @@ -27,11 +27,11 @@ class PERK(Estimator): Parameters ---------- acquisition : Simulator, optional - The sequence being inverted: a simulator that ships with TorchSim, one - written by subclassing :class:`~torchsim.model.Simulator`, or any other - :class:`~torchsim.model.Simulator`. Every tissue property that is + The sequence being inverted: a simulator that ships with BlochSim, one + written by subclassing :class:`~blochsim.model.Simulator`, or any other + :class:`~blochsim.model.Simulator`. Every tissue property that is neither unknown nor measured separately is fixed on it beforehand, with - the constructor or :meth:`~torchsim.model.Simulator.bind`. Leave it + the constructor or :meth:`~blochsim.model.Simulator.bind`. Leave it out to fit from signals handed to :meth:`fit` directly. n_features : int, optional Number of random Fourier features. The default is ``1000``. @@ -721,7 +721,7 @@ def _given_length_scale(self, width: int, device: torch.device) -> torch.Tensor: def _loaded(name: str) -> Any: """One kernel backend, or ``None`` where it cannot be imported.""" try: - return __import__(f"torchsim.estimators.{name}", fromlist=[name]) + return __import__(f"blochsim.estimators.{name}", fromlist=[name]) except ImportError: return None diff --git a/src/torchsim/estimators/_perk_native.py b/src/blochsim/estimators/_perk_native.py similarity index 100% rename from src/torchsim/estimators/_perk_native.py rename to src/blochsim/estimators/_perk_native.py diff --git a/src/torchsim/estimators/_perk_triton.py b/src/blochsim/estimators/_perk_triton.py similarity index 100% rename from src/torchsim/estimators/_perk_triton.py rename to src/blochsim/estimators/_perk_triton.py diff --git a/src/torchsim/model/__init__.py b/src/blochsim/model/__init__.py similarity index 100% rename from src/torchsim/model/__init__.py rename to src/blochsim/model/__init__.py diff --git a/src/torchsim/model/_binding.py b/src/blochsim/model/_binding.py similarity index 99% rename from src/torchsim/model/_binding.py rename to src/blochsim/model/_binding.py index 7c009a67..4925481e 100644 --- a/src/torchsim/model/_binding.py +++ b/src/blochsim/model/_binding.py @@ -176,11 +176,11 @@ def bind( Parameters ---------- simulator: - The protocol, whose :meth:`~torchsim.model.Simulator.describe` + The protocol, whose :meth:`~blochsim.model.Simulator.describe` is walked here and, if this succeeds, not again. values: The protocol arguments as - :meth:`~torchsim.model.Simulator.played` returns them. The + :meth:`~blochsim.model.Simulator.played` returns them. The floating-point arrays among them are the ones rebound. repetitions, record, rf_raster_time_s, device: The run this is for. A call differing in any of them gets no binding. diff --git a/src/torchsim/model/_signal.py b/src/blochsim/model/_signal.py similarity index 98% rename from src/torchsim/model/_signal.py rename to src/blochsim/model/_signal.py index 023014ad..a83d4cbd 100644 --- a/src/torchsim/model/_signal.py +++ b/src/blochsim/model/_signal.py @@ -18,7 +18,7 @@ arguments arrive in whatever they were written in -- NumPy, CuPy, torch -- and are read through DLPack, over the same memory rather than a copy. The signal goes back the same way. Everything between is torch, which is what the kernels -and the autograd graph are written against; see :mod:`torchsim.sequence._array` +and the autograd graph are written against; see :mod:`blochsim.sequence._array` for what that costs, and for the one thing it cannot carry back. **Derivatives are taken in the mode that suits what is differentiated.** A @@ -53,7 +53,7 @@ class _SignalModel(ABC): """What every model owes, and the two derivative modes over it. - The interface :class:`~torchsim.model.Simulator` presents, and everything + The interface :class:`~blochsim.model.Simulator` presents, and everything downstream consumes: declared properties, and a signal. What a model has to supply is :attr:`properties` and :meth:`evaluate`. @@ -66,7 +66,7 @@ class _SignalModel(ABC): properties : mapping or sequence of str What the model exposes, as names. A mapping is read for its keys, so a subclass that also needs to say where each property goes -- as - :class:`~torchsim.model.SpinPhysics` does, naming the tissue + :class:`~blochsim.model.SpinPhysics` does, naming the tissue field each fills -- declares both at once. bound : mapping The arguments fixed on this model, under the names :meth:`simulate` diff --git a/src/torchsim/model/_state_machine.py b/src/blochsim/model/_state_machine.py similarity index 98% rename from src/torchsim/model/_state_machine.py rename to src/blochsim/model/_state_machine.py index cfb7388f..af8da69f 100644 --- a/src/torchsim/model/_state_machine.py +++ b/src/blochsim/model/_state_machine.py @@ -14,7 +14,7 @@ **The operators are resolved before a description exists.** A simulator binds each slot when it is constructed; what a protocol then produces is an ordinary -:class:`~torchsim.sequence.SequenceDescription`, whose events carry their own +:class:`~blochsim.sequence.SequenceDescription`, whose events carry their own action word. From there the path is the fused one -- packing, the feature mask, the real-subspace verdict, offload and sharding -- and nothing consults an operator slot again. There is no interpretation at run time and none per @@ -24,8 +24,8 @@ vocabulary at three sizes. :class:`EventOperators` has one slot per role a sequence is written in terms of, and its values are operator factories, read only while a description is being assembled. Each factory emits events tagged -with an :class:`~torchsim.sequence.RfUse`, which is what a Pulseq file -carries, and with an :class:`~torchsim.sequence.EventAction`, which is the bit +with an :class:`~blochsim.sequence.RfUse`, which is what a Pulseq file +carries, and with an :class:`~blochsim.sequence.EventAction`, which is the bit field the kernels read. The three do not line up one to one and are not meant to: the :attr:`~EventOperators.saturation` slot plays a pulse tagged ``RfUse.EXCITATION``, because what the scanner is told about a pulse and what @@ -105,7 +105,7 @@ "across_slice", ) -# The raster :class:`~torchsim.sequence.EpgEngine` reads a pulse's shape on, +# The raster :class:`~blochsim.sequence.EpgEngine` reads a pulse's shape on, # named here because a packing resolved against one is not valid against # another. _RF_RASTER_TIME_S = 1e-6 @@ -246,8 +246,8 @@ class EventOperators: These are roles a sequence is written in terms of, not the tags the events end up carrying: a factory here decides which - :class:`~torchsim.sequence.RfUse` and which - :class:`~torchsim.sequence.EventAction` its events are emitted with. + :class:`~blochsim.sequence.RfUse` and which + :class:`~blochsim.sequence.EventAction` its events are emitted with. """ #: What a pulse that tips magnetization into the transverse plane plays. @@ -496,7 +496,7 @@ def __init__( execution: Where to run -- ``"auto"`` to decide per call against what the devices have free, ``"cpu"``, or a device or list of devices. - ``None`` follows whatever :func:`~torchsim.sequence.execution` + ``None`` follows whatever :func:`~blochsim.sequence.execution` block is in scope, which is what lets a caller decide instead. pulse: The waveform the events drive, taking the place of the ideal hard @@ -832,9 +832,9 @@ def from_description( the use its designer gave it, and an ADC window -- and says nothing about the gradients between them, because the transport carries none. The dephasing lives in the handlers instead: a - :func:`~torchsim.SSFPFidReadout` winds one order after every sample, a - :func:`~torchsim.SSFPEchoReadout` winds it before, a - :func:`~torchsim.SPGRReadout` spoils, and a refocusing pulse is + :func:`~blochsim.SSFPFidReadout` winds one order after every sample, a + :func:`~blochsim.SSFPEchoReadout` winds it before, a + :func:`~blochsim.SPGRReadout` spoils, and a refocusing pulse is crushed either side. So the events are re-emitted through this model's own operators rather than taken as they arrive. diff --git a/src/torchsim/optim/__init__.py b/src/blochsim/optim/__init__.py similarity index 100% rename from src/torchsim/optim/__init__.py rename to src/blochsim/optim/__init__.py diff --git a/src/torchsim/optim/_design.py b/src/blochsim/optim/_design.py similarity index 100% rename from src/torchsim/optim/_design.py rename to src/blochsim/optim/_design.py diff --git a/src/torchsim/recon/__init__.py b/src/blochsim/recon/__init__.py similarity index 100% rename from src/torchsim/recon/__init__.py rename to src/blochsim/recon/__init__.py diff --git a/src/torchsim/recon/_gauss_newton.py b/src/blochsim/recon/_gauss_newton.py similarity index 98% rename from src/torchsim/recon/_gauss_newton.py rename to src/blochsim/recon/_gauss_newton.py index dd1060d1..fe7ea664 100644 --- a/src/torchsim/recon/_gauss_newton.py +++ b/src/blochsim/recon/_gauss_newton.py @@ -93,7 +93,7 @@ class Solution: ---------- x : torch.Tensor The maps, as the variables that were solved for. Pass them through - :meth:`~torchsim.recon.ModelOperator.split` for the properties. + :meth:`~blochsim.recon.ModelOperator.split` for the properties. cost : torch.Tensor The squared residual after each step, so convergence is read rather than assumed. @@ -199,7 +199,7 @@ def iterative( Raises ------ ImportError - If no solver is given and deepinv is not installed. TorchSim does not + If no solver is given and deepinv is not installed. BlochSim does not depend on it: pass a :class:`LeastSquares` of your own, or use :func:`direct` where the model stands alone. """ @@ -217,7 +217,7 @@ def solve( except ImportError as error: # pragma: no cover - depends on env raise ImportError( "iterative() falls back to deepinv's least_squares, and " - "TorchSim does not depend on deepinv. pip install " + "BlochSim does not depend on deepinv. pip install " "deepinv, pass a LeastSquares of your own, or use " "direct() where the model stands alone." ) from error @@ -485,14 +485,14 @@ def minimize( Parameters ---------- operator: - A :class:`~torchsim.recon.ModelOperator`. + A :class:`~blochsim.recon.ModelOperator`. measured: The data. Without an encoding, ``(..., contrasts)`` -- the contrast images themselves. With one, whatever the encoding produces. initial: ``(..., channels)``, where to start. Build it with - :meth:`~torchsim.recon.ModelOperator.initial`. + :meth:`~blochsim.recon.ModelOperator.initial`. encoding: Anything with ``A`` and ``A_adjoint`` -- a deepinv ``LinearPhysics``, an mri-nufft operator through its deepinv diff --git a/src/torchsim/recon/_operator.py b/src/blochsim/recon/_operator.py similarity index 98% rename from src/torchsim/recon/_operator.py rename to src/blochsim/recon/_operator.py index c4fbecb5..acced5ca 100644 --- a/src/torchsim/recon/_operator.py +++ b/src/blochsim/recon/_operator.py @@ -52,7 +52,7 @@ class ModelOperator(torch.nn.Module): """A signal model over parameter maps, with its derivatives. - Maps are stacked on the **last** axis, as everywhere else in TorchSim: + Maps are stacked on the **last** axis, as everywhere else in BlochSim: ``(..., channels)`` in, ``(..., contrasts)`` out, every leading axis a voxel axis. :meth:`physics` is where that flips to the channel-first convention deepinv and mri-nufft use, and it is the only place it flips. @@ -82,7 +82,7 @@ class ModelOperator(torch.nn.Module): On, which is what a reconstruction wants; off for a fit whose model already exposes its own proton density. subspace : Subspace, optional - A :class:`~torchsim.Subspace` the prediction is projected through, for + A :class:`~blochsim.Subspace` the prediction is projected through, for a reconstruction that solves in the temporal basis rather than in the contrasts. @@ -382,21 +382,21 @@ def physics(self, **kwargs: Any) -> Any: Raises ------ ImportError - If deepinv is not installed. TorchSim does not depend on it. + If deepinv is not installed. BlochSim does not depend on it. Notes ----- A ``ComposedPhysics`` takes its Jacobian products by automatic differentiation through the whole chain, so composing this way gives up the analytic derivative below. - :class:`~torchsim.recon.GaussNewton` chains the two operators' own + :class:`~blochsim.recon.GaussNewton` chains the two operators' own products instead, and keeps it. """ try: from deepinv.physics import Physics except ImportError as error: # pragma: no cover - depends on the env raise ImportError( - "physics() needs deepinv; TorchSim does not depend on it. " + "physics() needs deepinv; BlochSim does not depend on it. " "pip install deepinv" ) from error @@ -488,7 +488,7 @@ def _voxelwise( """Run a per-voxel body wherever the execution policy says to. Every axis but the last is a voxel axis, so the work is flattened to - one, handed to :func:`~torchsim._execution.per_voxel`, and folded + one, handed to :func:`~blochsim._execution.per_voxel`, and folded back. Where no policy is in force it runs as written. So does a call whose inputs carry a derivative, and that covers two diff --git a/src/torchsim/sequence/__init__.py b/src/blochsim/sequence/__init__.py similarity index 100% rename from src/torchsim/sequence/__init__.py rename to src/blochsim/sequence/__init__.py diff --git a/src/torchsim/sequence/_accelerators.py b/src/blochsim/sequence/_accelerators.py similarity index 99% rename from src/torchsim/sequence/_accelerators.py rename to src/blochsim/sequence/_accelerators.py index 564780d2..e9362678 100644 --- a/src/torchsim/sequence/_accelerators.py +++ b/src/blochsim/sequence/_accelerators.py @@ -613,7 +613,7 @@ def simulate_native( ``pools`` counts the chemically exchanging pools the tissue carries from pool B on, and ``extra_pools`` holds the prepared buffers of pools C to E. - From :data:`~torchsim.sequence._pools.TABULATE_FROM` pools on, the kernels + From :data:`~blochsim.sequence._pools.TABULATE_FROM` pools on, the kernels read their operators from a table rather than forming them. """ device = prepared_tissue[0].device @@ -702,7 +702,7 @@ def simulate_native( exchanging=pools, semisolid=lineshape is not None, ) - threads = int(os.environ.get("TORCHSIM_NUM_THREADS", str(torch.get_num_threads()))) + threads = int(os.environ.get("BLOCHSIM_NUM_THREADS", str(torch.get_num_threads()))) signal = _NativeEpg.apply( *tissue, packed.duration, @@ -888,7 +888,7 @@ def sample_times_us(interval_us: torch.Tensor, recorded: torch.Tensor) -> torch. own. That is what lets a rebinding produce the times from the intervals it already maps, where a repetition time reaching every later timestamp is not something one source element accounts for; see - :mod:`torchsim.model._binding`, which is where this is read. + :mod:`blochsim.model._binding`, which is where this is read. The sum is taken at double width. The intervals are float32 and a long train accumulates thousands of them, which is a rounding the samples' own @@ -1861,7 +1861,7 @@ def vmap( def _backend_available(device: torch.device) -> bool: try: if device.type == "cpu": - from torchsim import _epg_cpu # noqa: F401 + from blochsim import _epg_cpu # noqa: F401 else: from . import _epg_triton # noqa: F401 except ImportError: @@ -1997,7 +1997,7 @@ def real_subspace_axis( out. ``features`` names the terms the caller's tissue gives anything to do, as - :func:`torchsim.sequence._parameters.features_of` reads them off the values + :func:`blochsim.sequence._parameters.features_of` reads them off the values passed in. A term absent from that set is at its identity everywhere, which settles three of the four questions here without reading a buffer at all. """ @@ -2687,7 +2687,7 @@ def _choose( The policy is general; what an EPG pass costs is not. The work estimate and the per-voxel footprint are worked out here, and the decision itself is - :func:`torchsim._execution.choose`. + :func:`blochsim._execution.choose`. """ train_count = _train_count(events) voxels = int(tissue[0].numel()) @@ -3403,7 +3403,7 @@ def _run_packed( features=features, pools=pools, ) - from torchsim import _epg_cpu + from blochsim import _epg_cpu trains = _train_count(events) shape = ( @@ -3628,7 +3628,7 @@ def _run_packed_vjp( pools=pools, ) return adjoint - from torchsim import _epg_cpu + from blochsim import _epg_cpu grad_output = grad_output.resolve_conj().contiguous() grad_real = grad_output.real.contiguous() @@ -3856,7 +3856,7 @@ def _run_packed_vjp_jvp( features=features, pools=pools, ) - from torchsim import _epg_cpu + from blochsim import _epg_cpu grad_output = grad_output.resolve_conj().contiguous() grad_real = grad_output.real.contiguous() @@ -4074,7 +4074,7 @@ def _run_packed_jvp( features=features, pools=pools, ) - from torchsim import _epg_cpu + from blochsim import _epg_cpu trains = _train_count(events) shape = ( diff --git a/src/torchsim/sequence/_array.py b/src/blochsim/sequence/_array.py similarity index 99% rename from src/torchsim/sequence/_array.py rename to src/blochsim/sequence/_array.py index 3edb706f..0762da7d 100644 --- a/src/torchsim/sequence/_array.py +++ b/src/blochsim/sequence/_array.py @@ -14,7 +14,7 @@ **What does not survive the round trip is the autograd graph.** A gradient needs the tensor it was taken with respect to, so a cost differentiated with :meth:`torch.Tensor.backward` has to be built on torch inputs. Forward-mode -derivatives are unaffected -- :meth:`~torchsim.model.Simulator.jacobian` +derivatives are unaffected -- :meth:`~blochsim.model.Simulator.jacobian` takes them internally and hands back plain arrays in whatever the caller brought. """ diff --git a/src/torchsim/sequence/_builders.py b/src/blochsim/sequence/_builders.py similarity index 100% rename from src/torchsim/sequence/_builders.py rename to src/blochsim/sequence/_builders.py diff --git a/src/torchsim/sequence/_calibration.py b/src/blochsim/sequence/_calibration.py similarity index 100% rename from src/torchsim/sequence/_calibration.py rename to src/blochsim/sequence/_calibration.py diff --git a/src/torchsim/sequence/_description.py b/src/blochsim/sequence/_description.py similarity index 99% rename from src/torchsim/sequence/_description.py rename to src/blochsim/sequence/_description.py index 7b12de26..95d24ffa 100644 --- a/src/torchsim/sequence/_description.py +++ b/src/blochsim/sequence/_description.py @@ -745,7 +745,7 @@ def plot(self, axis: Any = None, *, upto_s: float | None = None) -> Any: import matplotlib.pyplot as plt except ImportError as reason: # pragma: no cover - depends on the env raise ImportError( - "plotting a description needs matplotlib, which torchsim does " + "plotting a description needs matplotlib, which blochsim does " "not depend on; pip install matplotlib" ) from reason diff --git a/src/torchsim/sequence/_epg_triton.py b/src/blochsim/sequence/_epg_triton.py similarity index 99% rename from src/torchsim/sequence/_epg_triton.py rename to src/blochsim/sequence/_epg_triton.py index ebb00780..f4a3ed3f 100644 --- a/src/torchsim/sequence/_epg_triton.py +++ b/src/blochsim/sequence/_epg_triton.py @@ -336,11 +336,11 @@ def _three_pool_step( of which is non-positive, so a long interval cannot overflow. ``narrow`` says the caller has bounded the spread below - :data:`torchsim.sequence._parameters.NARROW_SPREAD` for every voxel and + :data:`blochsim.sequence._parameters.NARROW_SPREAD` for every voxel and every interval it will pass, so only the series can be reached. The roots then cost nothing, and the series holds the answer to float32 without being carried in double -- - :func:`torchsim.sequence._parameters.narrow_three_pool` is what decides it. + :func:`blochsim.sequence._parameters.narrow_three_pool` is what decides it. """ work: tl.constexpr = tl.float32 if narrow else tl.float64 terms: tl.constexpr = 24 if narrow else 16 @@ -17633,7 +17633,7 @@ def simulate_vjp( property, then event duration, flip and phase, and the pair's cotangent where one is given. A shard takes this same kernel a level down and a streamed volume has chunked launchers of its own; - :func:`torchsim.sequence._accelerators` decides which route a run takes. + :func:`blochsim.sequence._accelerators` decides which route a run takes. Carrying no forward direction, this records two trajectory planes per recorded state where that pass records four, and holds one state where it diff --git a/src/torchsim/sequence/_fixed_point.py b/src/blochsim/sequence/_fixed_point.py similarity index 100% rename from src/torchsim/sequence/_fixed_point.py rename to src/blochsim/sequence/_fixed_point.py diff --git a/src/torchsim/sequence/_lineshape.py b/src/blochsim/sequence/_lineshape.py similarity index 100% rename from src/torchsim/sequence/_lineshape.py rename to src/blochsim/sequence/_lineshape.py diff --git a/src/torchsim/sequence/_mrd.py b/src/blochsim/sequence/_mrd.py similarity index 100% rename from src/torchsim/sequence/_mrd.py rename to src/blochsim/sequence/_mrd.py diff --git a/src/torchsim/sequence/_operators.py b/src/blochsim/sequence/_operators.py similarity index 100% rename from src/torchsim/sequence/_operators.py rename to src/blochsim/sequence/_operators.py diff --git a/src/torchsim/sequence/_parameters.py b/src/blochsim/sequence/_parameters.py similarity index 99% rename from src/torchsim/sequence/_parameters.py rename to src/blochsim/sequence/_parameters.py index 2717522c..dadc978b 100644 --- a/src/torchsim/sequence/_parameters.py +++ b/src/blochsim/sequence/_parameters.py @@ -137,7 +137,7 @@ class Geometry: # water alone. They are not in the ABI above. A tissue with more than one # exchanging pool reaches the kernels as its relaxation and exchange operators, # tabulated per interval from these and pool B's own properties -# (``torchsim.sequence._pools``), so no kernel reads them one by one. +# (``blochsim.sequence._pools``), so no kernel reads them one by one. EXTRA_POOLS: tuple[str, ...] = ("c", "d", "e") POOL_PARAMETERS: tuple[Parameter, ...] = tuple( @@ -357,7 +357,7 @@ def features_of(tissue: Any) -> frozenset[str]: def feature_flags(features: Any, geometry: Geometry) -> dict[str, bool]: """Which optional terms a launch is to carry. - ``features`` is the set :func:`torchsim.sequence._parameters.features_of` + ``features`` is the set :func:`blochsim.sequence._parameters.features_of` reads off the tissue; ``None`` is a caller who did not declare, and every term stays. diff --git a/src/torchsim/sequence/_pools.py b/src/blochsim/sequence/_pools.py similarity index 100% rename from src/torchsim/sequence/_pools.py rename to src/blochsim/sequence/_pools.py diff --git a/src/torchsim/sequence/_pools_triton.py b/src/blochsim/sequence/_pools_triton.py similarity index 99% rename from src/torchsim/sequence/_pools_triton.py rename to src/blochsim/sequence/_pools_triton.py index 08830659..b5fa16ee 100644 --- a/src/torchsim/sequence/_pools_triton.py +++ b/src/blochsim/sequence/_pools_triton.py @@ -1,7 +1,7 @@ """Triton kernels for a tissue whose pools are tabulated per interval. The device side of ``simulate_pooled`` and ``simulate_pooled_adjoint`` in -``_epg_cpu.cpp``, reading the tables ``torchsim.sequence._pools`` builds. One +``_epg_cpu.cpp``, reading the tables ``blochsim.sequence._pools`` builds. One program carries one (train, voxel) problem, and its states are tiles of pools by dephasing orders, so an interval's relaxation and exchange is a product of the tabulated operator with the tile and a pulse turns every exchanging pool at diff --git a/src/torchsim/sequence/_pulseq.py b/src/blochsim/sequence/_pulseq.py similarity index 98% rename from src/torchsim/sequence/_pulseq.py rename to src/blochsim/sequence/_pulseq.py index 95bcc1e3..7f4bb527 100644 --- a/src/torchsim/sequence/_pulseq.py +++ b/src/blochsim/sequence/_pulseq.py @@ -5,7 +5,7 @@ simulation needs and Pulseq does not write down: which pulse shapes the RF rows share, and which ADC sample each readout passes through k = 0 in. -``pypulseq`` is an optional dependency -- ``pip install torchsim[pulseq]``. +``pypulseq`` is an optional dependency -- ``pip install blochsim[pulseq]``. """ from __future__ import annotations @@ -49,8 +49,8 @@ def _read(source: Any) -> Any: import pypulseq except ImportError as missing: # pragma: no cover - exercised by the message raise ImportError( - "reading a Pulseq file needs pypulseq, which TorchSim does not " - "require otherwise: pip install torchsim[pulseq]" + "reading a Pulseq file needs pypulseq, which BlochSim does not " + "require otherwise: pip install blochsim[pulseq]" ) from missing sequence = pypulseq.Sequence() sequence.read(str(source)) diff --git a/src/torchsim/sequence/_settling.py b/src/blochsim/sequence/_settling.py similarity index 100% rename from src/torchsim/sequence/_settling.py rename to src/blochsim/sequence/_settling.py diff --git a/src/torchsim/sequence/_simulation.py b/src/blochsim/sequence/_simulation.py similarity index 99% rename from src/torchsim/sequence/_simulation.py rename to src/blochsim/sequence/_simulation.py index 2685cf31..20a51aa0 100644 --- a/src/torchsim/sequence/_simulation.py +++ b/src/blochsim/sequence/_simulation.py @@ -269,11 +269,11 @@ class EpgEngine: This is the direct route, for when what you hold is a description rather than a simulator: one a builder such as - :func:`~torchsim.fse_description` emitted, one assembled from operators by + :func:`~blochsim.fse_description` emitted, one assembled from operators by hand, or one that arrived from a scanner. A - :class:`~torchsim.model.Simulator` wraps this and is what most sequences + :class:`~blochsim.model.Simulator` wraps this and is what most sequences are written as -- it fixes tissue and protocol on itself, and adds - :meth:`~torchsim.model.Simulator.jacobian`, binding and device + :meth:`~blochsim.model.Simulator.jacobian`, binding and device placement. Nothing here does any of that: give it a description and a tissue, get a :class:`SimulationResult` back. @@ -381,7 +381,7 @@ def simulate( ``events`` is the description's stream already packed into buffers, for a caller that holds the structure fixed and rebuilds only what varies; - see :mod:`torchsim.optim`. It must be the packing of ``description``. + see :mod:`blochsim.optim`. It must be the packing of ``description``. Raises ------ @@ -667,7 +667,7 @@ def _prepare_tissue( """The tissue as one flat buffer per property, and how many voxels there are. ``features`` names the terms the run carries, as - :func:`torchsim.sequence._parameters.features_of` reads them off the + :func:`blochsim.sequence._parameters.features_of` reads them off the tissue. A property belonging to a term the run leaves out is kept as the one value it was given rather than laid out per voxel: the kernels take its pointer and the branch that would read it is compiled away. ``None`` is a diff --git a/src/torchsim/sequence/_transition.py b/src/blochsim/sequence/_transition.py similarity index 100% rename from src/torchsim/sequence/_transition.py rename to src/blochsim/sequence/_transition.py diff --git a/src/torchsim/sequence/_transmit.py b/src/blochsim/sequence/_transmit.py similarity index 100% rename from src/torchsim/sequence/_transmit.py rename to src/blochsim/sequence/_transmit.py diff --git a/src/torchsim/simulators/__init__.py b/src/blochsim/simulators/__init__.py similarity index 96% rename from src/torchsim/simulators/__init__.py rename to src/blochsim/simulators/__init__.py index d8f2b5a5..4266fc01 100644 --- a/src/torchsim/simulators/__init__.py +++ b/src/blochsim/simulators/__init__.py @@ -1,4 +1,4 @@ -"""The sequences that ship with TorchSim, as simulators. +"""The sequences that ship with BlochSim, as simulators. Each names its protocol arguments at construction and its tissue properties at the call, so parameter inference, sequence optimization and a diff --git a/src/torchsim/simulators/_contrast.py b/src/blochsim/simulators/_contrast.py similarity index 100% rename from src/torchsim/simulators/_contrast.py rename to src/blochsim/simulators/_contrast.py diff --git a/src/torchsim/simulators/_pulses.py b/src/blochsim/simulators/_pulses.py similarity index 98% rename from src/torchsim/simulators/_pulses.py rename to src/blochsim/simulators/_pulses.py index 309f2b35..153f88a3 100644 --- a/src/torchsim/simulators/_pulses.py +++ b/src/blochsim/simulators/_pulses.py @@ -1,4 +1,4 @@ -"""Pulse shapes sampled for :func:`~torchsim.sequence.SampledPulse`, and the preparations they build.""" +"""Pulse shapes sampled for :func:`~blochsim.sequence.SampledPulse`, and the preparations they build.""" from __future__ import annotations diff --git a/src/torchsim/simulators/asl.py b/src/blochsim/simulators/asl.py similarity index 99% rename from src/torchsim/simulators/asl.py rename to src/blochsim/simulators/asl.py index 097aadfb..d4053af5 100644 --- a/src/torchsim/simulators/asl.py +++ b/src/blochsim/simulators/asl.py @@ -45,7 +45,7 @@ class ASLSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import ASLSimulator + from blochsim.simulators import ASLSimulator sequence = ASLSimulator(t=torch.linspace(0.0, 5000.0, 51), tau=1800.0) signal = sequence.simulate(CBF=(20.0, 60.0), ATT=1200.0, T1=1400.0) diff --git a/src/torchsim/simulators/bssfp.py b/src/blochsim/simulators/bssfp.py similarity index 98% rename from src/torchsim/simulators/bssfp.py rename to src/blochsim/simulators/bssfp.py index b021ab66..ecfe559f 100644 --- a/src/torchsim/simulators/bssfp.py +++ b/src/blochsim/simulators/bssfp.py @@ -34,7 +34,7 @@ class bSSFPSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import bSSFPSimulator + from blochsim.simulators import bSSFPSimulator sequence = bSSFPSimulator(flip=60.0, TR=10.0, TE=5.0) signal = sequence.simulate(T1=1000.0, T2=100.0) diff --git a/src/torchsim/simulators/cest.py b/src/blochsim/simulators/cest.py similarity index 98% rename from src/torchsim/simulators/cest.py rename to src/blochsim/simulators/cest.py index 5923c4e3..43a3bff1 100644 --- a/src/torchsim/simulators/cest.py +++ b/src/blochsim/simulators/cest.py @@ -49,7 +49,7 @@ class LorentzianSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import LorentzianSimulator + from blochsim.simulators import LorentzianSimulator sequence = LorentzianSimulator(2, offsets=torch.linspace(-5.0, 5.0, 41)) signal = sequence.simulate( @@ -135,7 +135,7 @@ class CESTSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import CESTSimulator + from blochsim.simulators import CESTSimulator sequence = CESTSimulator(offsets=torch.linspace(-5.0, 5.0, 21)) spectrum = sequence.simulate( diff --git a/src/torchsim/simulators/diffusion.py b/src/blochsim/simulators/diffusion.py similarity index 98% rename from src/torchsim/simulators/diffusion.py rename to src/blochsim/simulators/diffusion.py index 721b2ce8..4dfc1157 100644 --- a/src/torchsim/simulators/diffusion.py +++ b/src/blochsim/simulators/diffusion.py @@ -38,7 +38,7 @@ class DiffusionSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import DiffusionSimulator + from blochsim.simulators import DiffusionSimulator sequence = DiffusionSimulator(b=(0.0, 500.0, 1000.0, 2000.0)) signal = sequence.simulate(D=(0.8, 3.0)) diff --git a/src/torchsim/simulators/flash.py b/src/blochsim/simulators/flash.py similarity index 99% rename from src/torchsim/simulators/flash.py rename to src/blochsim/simulators/flash.py index cb75cab1..1c2467fd 100644 --- a/src/torchsim/simulators/flash.py +++ b/src/blochsim/simulators/flash.py @@ -50,7 +50,7 @@ class FLASHSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import FLASHSimulator + from blochsim.simulators import FLASHSimulator sequence = FLASHSimulator( flip=8.0, TR=5.0, TE=2.0, nshots=100, inversion="adiabatic" diff --git a/src/torchsim/simulators/fmssfp.py b/src/blochsim/simulators/fmssfp.py similarity index 98% rename from src/torchsim/simulators/fmssfp.py rename to src/blochsim/simulators/fmssfp.py index 4b6e634d..78709f2f 100644 --- a/src/torchsim/simulators/fmssfp.py +++ b/src/blochsim/simulators/fmssfp.py @@ -44,7 +44,7 @@ class fmSSFPSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import fmSSFPSimulator + from blochsim.simulators import fmSSFPSimulator sequence = fmSSFPSimulator(flip=30.0, TR=5.0, nshots=100) signal = sequence.simulate(T1=1000.0, T2=100.0, B0=20.0) diff --git a/src/torchsim/simulators/fse.py b/src/blochsim/simulators/fse.py similarity index 98% rename from src/torchsim/simulators/fse.py rename to src/blochsim/simulators/fse.py index 9383777a..37aeaf5d 100644 --- a/src/torchsim/simulators/fse.py +++ b/src/blochsim/simulators/fse.py @@ -22,7 +22,7 @@ class FSESimulator(Simulator): .. exec:: import torch - from torchsim.simulators import FSESimulator + from blochsim.simulators import FSESimulator sequence = FSESimulator(flip=180.0 * torch.ones(128), ESP=2.0, TR=5000.0) signal = sequence.simulate(T1=1000.0, T2=80.0) @@ -156,7 +156,7 @@ class HyperechoSimulator(FSESimulator): .. exec:: import torch - from torchsim.simulators import HyperechoSimulator + from blochsim.simulators import HyperechoSimulator sequence = HyperechoSimulator(flip=60.0 * torch.ones(5), ESP=10.0) signal = sequence.simulate(T1=1000.0, T2=80.0) diff --git a/src/torchsim/simulators/gradient_echo.py b/src/blochsim/simulators/gradient_echo.py similarity index 97% rename from src/torchsim/simulators/gradient_echo.py rename to src/blochsim/simulators/gradient_echo.py index 76632d82..6a1fff6a 100644 --- a/src/torchsim/simulators/gradient_echo.py +++ b/src/blochsim/simulators/gradient_echo.py @@ -139,7 +139,7 @@ class MultiGradientEchoSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import MultiGradientEchoSimulator + from blochsim.simulators import MultiGradientEchoSimulator sequence = MultiGradientEchoSimulator(TE=(1.2, 2.4, 3.6, 4.8, 6.0, 7.2)) signal = sequence.simulate(fat_fraction=(0.0, 0.2), T2star=30.0, B0=15.0) @@ -183,7 +183,7 @@ def _signal( field_strength: Main field in tesla, which sets the fat peaks' frequencies. fat_spectrum: - A key of :data:`~torchsim.simulators.gradient_echo.FAT_SPECTRA`. + A key of :data:`~blochsim.simulators.gradient_echo.FAT_SPECTRA`. """ held = across_contrasts(properties, TE) return _water_and_fat(held, 1.0, 1.0, TE, field_strength, fat_spectrum) @@ -207,7 +207,7 @@ class IRMultiGradientEchoSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import IRMultiGradientEchoSimulator + from blochsim.simulators import IRMultiGradientEchoSimulator TI, TE = torch.meshgrid( torch.tensor([20.0, 200.0, 800.0, 2000.0]), @@ -264,7 +264,7 @@ def _signal( field_strength: Main field in tesla, which sets the fat peaks' frequencies. fat_spectrum: - A key of :data:`~torchsim.simulators.gradient_echo.FAT_SPECTRA`. + A key of :data:`~blochsim.simulators.gradient_echo.FAT_SPECTRA`. """ held = across_contrasts(properties, TI, TE) inversion_s = TI * 1e-3 diff --git a/src/torchsim/simulators/hsfp.py b/src/blochsim/simulators/hsfp.py similarity index 98% rename from src/torchsim/simulators/hsfp.py rename to src/blochsim/simulators/hsfp.py index e20fb6bd..0a7dd0c5 100644 --- a/src/torchsim/simulators/hsfp.py +++ b/src/blochsim/simulators/hsfp.py @@ -41,7 +41,7 @@ class HSFPSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import HSFPSimulator + from blochsim.simulators import HSFPSimulator train = 60.0 * torch.sin(torch.linspace(0.0, torch.pi, 400)) ** 2 sequence = HSFPSimulator(flip=train, TR=4.5) diff --git a/src/torchsim/simulators/looklocker.py b/src/blochsim/simulators/looklocker.py similarity index 98% rename from src/torchsim/simulators/looklocker.py rename to src/blochsim/simulators/looklocker.py index 20f2fd3a..f2c057c3 100644 --- a/src/torchsim/simulators/looklocker.py +++ b/src/blochsim/simulators/looklocker.py @@ -66,7 +66,7 @@ class LookLockerSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import LookLockerSimulator + from blochsim.simulators import LookLockerSimulator sequence = LookLockerSimulator(flip=6.0, TR=4.0, TI=4.0 * torch.arange(500)) signal = sequence.simulate(T1=(800.0, 1400.0)) @@ -149,7 +149,7 @@ class MOLLISimulator(Simulator): -------- .. exec:: - from torchsim.simulators import MOLLISimulator + from blochsim.simulators import MOLLISimulator sequence = MOLLISimulator(flip=35.0, TR=2.5, readouts=(40, 40, 40), recovery=800.0) signal = sequence.simulate(T1=(800.0, 1400.0)) @@ -244,7 +244,7 @@ class IRbSSFPSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import IRbSSFPSimulator + from blochsim.simulators import IRbSSFPSimulator sequence = IRbSSFPSimulator(flip=45.0, TI=4.5 * torch.arange(1000)) signal = sequence.simulate(T1=1000.0, T2=(50.0, 100.0)) diff --git a/src/torchsim/simulators/mp2rage.py b/src/blochsim/simulators/mp2rage.py similarity index 99% rename from src/torchsim/simulators/mp2rage.py rename to src/blochsim/simulators/mp2rage.py index 65c5cb37..a7219575 100644 --- a/src/torchsim/simulators/mp2rage.py +++ b/src/blochsim/simulators/mp2rage.py @@ -36,7 +36,7 @@ class MP2RAGESimulator(Simulator): -------- .. exec:: - from torchsim.simulators import MP2RAGESimulator + from blochsim.simulators import MP2RAGESimulator sequence = MP2RAGESimulator( TI=(500.0, 1500.0), diff --git a/src/torchsim/simulators/mpnrage.py b/src/blochsim/simulators/mpnrage.py similarity index 98% rename from src/torchsim/simulators/mpnrage.py rename to src/blochsim/simulators/mpnrage.py index 5fa945e1..705af150 100644 --- a/src/torchsim/simulators/mpnrage.py +++ b/src/blochsim/simulators/mpnrage.py @@ -21,7 +21,7 @@ class MPnRAGESimulator(Simulator): -------- .. exec:: - from torchsim.simulators import MPnRAGESimulator + from blochsim.simulators import MPnRAGESimulator sequence = MPnRAGESimulator(nshots=128, flip=5.0, TR=10.0) signal = sequence.simulate(T1=1000.0, inv_efficiency=0.95) diff --git a/src/torchsim/simulators/mprage.py b/src/blochsim/simulators/mprage.py similarity index 98% rename from src/torchsim/simulators/mprage.py rename to src/blochsim/simulators/mprage.py index dea5fbb7..73a72458 100644 --- a/src/torchsim/simulators/mprage.py +++ b/src/blochsim/simulators/mprage.py @@ -22,7 +22,7 @@ class MPRAGESimulator(Simulator): -------- .. exec:: - from torchsim.simulators import MPRAGESimulator + from blochsim.simulators import MPRAGESimulator sequence = MPRAGESimulator(TI=500.0, flip=5.0, TRspgr=5.0, nshots=128) signal = sequence.simulate(T1=(200.0, 1000.0), inv_efficiency=0.95) diff --git a/src/torchsim/simulators/mrf.py b/src/blochsim/simulators/mrf.py similarity index 97% rename from src/torchsim/simulators/mrf.py rename to src/blochsim/simulators/mrf.py index b7b2691b..ee1041c1 100644 --- a/src/torchsim/simulators/mrf.py +++ b/src/blochsim/simulators/mrf.py @@ -22,7 +22,7 @@ class MRFSimulator(Simulator): .. exec:: import torch - from torchsim.simulators import MRFSimulator + from blochsim.simulators import MRFSimulator sequence = MRFSimulator( flip=torch.linspace(5.0, 60.0, 1000), TR=10.0, states=20 diff --git a/src/torchsim/simulators/relaxometry.py b/src/blochsim/simulators/relaxometry.py similarity index 97% rename from src/torchsim/simulators/relaxometry.py rename to src/blochsim/simulators/relaxometry.py index f4a75469..44a4034d 100644 --- a/src/torchsim/simulators/relaxometry.py +++ b/src/blochsim/simulators/relaxometry.py @@ -35,7 +35,7 @@ class InversionRecoverySimulator(Simulator): -------- .. exec:: - from torchsim.simulators import InversionRecoverySimulator + from blochsim.simulators import InversionRecoverySimulator sequence = InversionRecoverySimulator(TI=(50.0, 400.0, 1100.0, 2500.0)) signal = sequence.simulate(T1=(800.0, 1400.0)) @@ -99,7 +99,7 @@ class MultiEchoSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import MultiEchoSimulator + from blochsim.simulators import MultiEchoSimulator sequence = MultiEchoSimulator(TE=(10.0, 20.0, 40.0, 80.0)) signal = sequence.simulate(T2=(40.0, 90.0)) @@ -153,7 +153,7 @@ class DoubleAngleSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import DoubleAngleSimulator + from blochsim.simulators import DoubleAngleSimulator sequence = DoubleAngleSimulator(flip=(60.0, 120.0)) signal = sequence.simulate(B1=(0.8, 1.0, 1.2)) diff --git a/src/torchsim/simulators/spgr.py b/src/blochsim/simulators/spgr.py similarity index 98% rename from src/torchsim/simulators/spgr.py rename to src/blochsim/simulators/spgr.py index d57ac851..91e7d0f5 100644 --- a/src/torchsim/simulators/spgr.py +++ b/src/blochsim/simulators/spgr.py @@ -38,7 +38,7 @@ class SPGRSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import SPGRSimulator + from blochsim.simulators import SPGRSimulator sequence = SPGRSimulator(flip=13.0, TR=10.0, TE=5.0) signal = sequence.simulate(T1=1000.0, T2star=30.0) diff --git a/src/torchsim/simulators/spin_echo.py b/src/blochsim/simulators/spin_echo.py similarity index 98% rename from src/torchsim/simulators/spin_echo.py rename to src/blochsim/simulators/spin_echo.py index 5f7a0de4..1a646562 100644 --- a/src/torchsim/simulators/spin_echo.py +++ b/src/blochsim/simulators/spin_echo.py @@ -35,7 +35,7 @@ class SpinEchoSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import SpinEchoSimulator + from blochsim.simulators import SpinEchoSimulator sequence = SpinEchoSimulator(TE=(10.0, 30.0, 60.0, 100.0), TR=2000.0) signal = sequence.simulate(T1=1000.0, T2=(50.0, 100.0)) diff --git a/src/torchsim/simulators/stimulated_echo.py b/src/blochsim/simulators/stimulated_echo.py similarity index 98% rename from src/torchsim/simulators/stimulated_echo.py rename to src/blochsim/simulators/stimulated_echo.py index c1304e22..dfbe2ccb 100644 --- a/src/torchsim/simulators/stimulated_echo.py +++ b/src/blochsim/simulators/stimulated_echo.py @@ -36,7 +36,7 @@ class StimulatedEchoSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import StimulatedEchoSimulator + from blochsim.simulators import StimulatedEchoSimulator sequence = StimulatedEchoSimulator(flip=90.0, TE=20.0, TM=100.0) spin_echo, stimulated_echo = sequence.simulate(T1=1000.0, T2=80.0) diff --git a/src/torchsim/simulators/truefisp.py b/src/blochsim/simulators/truefisp.py similarity index 99% rename from src/torchsim/simulators/truefisp.py rename to src/blochsim/simulators/truefisp.py index 47fc24f2..dd3f7a14 100644 --- a/src/torchsim/simulators/truefisp.py +++ b/src/blochsim/simulators/truefisp.py @@ -43,7 +43,7 @@ class TrueFISPSimulator(Simulator): -------- .. exec:: - from torchsim.simulators import TrueFISPSimulator + from blochsim.simulators import TrueFISPSimulator sequence = TrueFISPSimulator( flip=45.0, TR=4.5, nshots=200, inversion="adiabatic" diff --git a/src/torchsim/utils/__init__.py b/src/blochsim/utils/__init__.py similarity index 100% rename from src/torchsim/utils/__init__.py rename to src/blochsim/utils/__init__.py diff --git a/src/torchsim/utils/_b1rms.py b/src/blochsim/utils/_b1rms.py similarity index 100% rename from src/torchsim/utils/_b1rms.py rename to src/blochsim/utils/_b1rms.py diff --git a/tests/conftest.py b/tests/conftest.py index 6512fa38..791e3392 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,6 @@ def always_worth_detecting(monkeypatch): trains one by one -- can fall on opposite sides of it and be compared across kernels. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators monkeypatch.setattr(_accelerators, "detection", lambda kind, device: 0.0) diff --git a/tests/estimators/test_dictionary.py b/tests/estimators/test_dictionary.py index 9a0b6596..0036dbd7 100644 --- a/tests/estimators/test_dictionary.py +++ b/tests/estimators/test_dictionary.py @@ -5,8 +5,8 @@ import pytest import torch -from torchsim.estimators import DictionaryMatcher -from torchsim.simulators import MultiEchoSimulator +from blochsim.estimators import DictionaryMatcher +from blochsim.simulators import MultiEchoSimulator @pytest.mark.parametrize( diff --git a/tests/estimators/test_grouped.py b/tests/estimators/test_grouped.py index 04efc15b..e1552bed 100644 --- a/tests/estimators/test_grouped.py +++ b/tests/estimators/test_grouped.py @@ -5,9 +5,9 @@ import pytest import torch -from torchsim.estimators import DictionaryMatcher -from torchsim.estimators._grouped import Grouping -from torchsim.simulators import MRFSimulator +from blochsim.estimators import DictionaryMatcher +from blochsim.estimators._grouped import Grouping +from blochsim.simulators import MRFSimulator @pytest.fixture(scope="module") @@ -220,7 +220,7 @@ def test_grouping_composes_with_a_temporal_subspace(fingerprints, measured) -> N other, and the answer is the one the compressed dictionary gives without grouping -- the grouping must not add error of its own. """ - from torchsim import ( + from blochsim import ( Subspace, ) @@ -247,7 +247,7 @@ def test_clustering_happens_inside_the_global_basis(fingerprints) -> None: leaving the space the measurement is in. Everything stays at the rank the dictionary was compressed to. """ - from torchsim import ( + from blochsim import ( Subspace, ) diff --git a/tests/estimators/test_lookup.py b/tests/estimators/test_lookup.py index c65e6336..f32c0fab 100644 --- a/tests/estimators/test_lookup.py +++ b/tests/estimators/test_lookup.py @@ -5,8 +5,8 @@ import pytest import torch -from torchsim.estimators import LookupTable -from torchsim.simulators import MP2RAGESimulator +from blochsim.estimators import LookupTable +from blochsim.simulators import MP2RAGESimulator PROTOCOL = dict( TI=(800.0, 2700.0), diff --git a/tests/estimators/test_mapping.py b/tests/estimators/test_mapping.py index a79294db..0dacf4d9 100644 --- a/tests/estimators/test_mapping.py +++ b/tests/estimators/test_mapping.py @@ -10,12 +10,12 @@ import pytest import torch -from torchsim import ( +from blochsim import ( PERK, DictionaryMatcher, Estimator, ) -from torchsim.simulators import FSESimulator, MRFSimulator +from blochsim.simulators import FSESimulator, MRFSimulator ECHOES = 32 diff --git a/tests/estimators/test_mapping_comparison.py b/tests/estimators/test_mapping_comparison.py index 0bbe2fb6..169d8e3a 100644 --- a/tests/estimators/test_mapping_comparison.py +++ b/tests/estimators/test_mapping_comparison.py @@ -4,8 +4,8 @@ import torch -from torchsim.estimators import PERK, DictionaryMatcher -from torchsim.simulators import FSESimulator +from blochsim.estimators import PERK, DictionaryMatcher +from blochsim.simulators import FSESimulator def test_perk_is_comparable_to_dictionary_matching_under_noise() -> None: diff --git a/tests/estimators/test_nlls.py b/tests/estimators/test_nlls.py index c8f6d73d..ae776f89 100644 --- a/tests/estimators/test_nlls.py +++ b/tests/estimators/test_nlls.py @@ -7,9 +7,9 @@ import torch from scipy.optimize import least_squares -import torchsim.recon._operator as _operator -from torchsim.estimators import NonlinearLeastSquares -from torchsim.simulators import ( +import blochsim.recon._operator as _operator +from blochsim.estimators import NonlinearLeastSquares +from blochsim.simulators import ( FSESimulator, InversionRecoverySimulator, MultiEchoSimulator, @@ -255,7 +255,7 @@ def test_an_equality_constraint_is_written_into_the_model() -> None: restoring it after each step, so the constraint holds at every iterate and not merely at the answer. """ - from torchsim.model import Simulator + from blochsim.model import Simulator class FatWater(Simulator): """Two species at their own frequencies, in known proportion.""" @@ -327,7 +327,7 @@ def test_the_solve_is_a_gauss_newton_and_nothing_else_is_here() -> None: moves, what stops a voxel -- lives on the loop, so there is one place to set it and one place for it to be wrong. """ - from torchsim.recon import GaussNewton, TrustRegion, direct + from blochsim.recon import GaussNewton, TrustRegion, direct default = NonlinearLeastSquares() @@ -347,7 +347,7 @@ def test_the_solve_is_a_gauss_newton_and_nothing_else_is_here() -> None: ) def test_loop_settings_that_make_no_sense(settings, complaint) -> None: """Caught where they are written, not where they misbehave.""" - from torchsim.recon import GaussNewton + from blochsim.recon import GaussNewton with pytest.raises(ValueError, match=complaint): GaussNewton(**settings) diff --git a/tests/estimators/test_perk.py b/tests/estimators/test_perk.py index 7348cf8a..636851ab 100644 --- a/tests/estimators/test_perk.py +++ b/tests/estimators/test_perk.py @@ -7,9 +7,9 @@ import pytest import torch -from torchsim.estimators import PERK, DictionaryMatcher -from torchsim.model import Simulator -from torchsim.simulators import MultiEchoSimulator +from blochsim.estimators import PERK, DictionaryMatcher +from blochsim.model import Simulator +from blochsim.simulators import MultiEchoSimulator @pytest.mark.parametrize( diff --git a/tests/estimators/test_perk_kernel.py b/tests/estimators/test_perk_kernel.py index f9b4e930..666a8397 100644 --- a/tests/estimators/test_perk_kernel.py +++ b/tests/estimators/test_perk_kernel.py @@ -12,8 +12,8 @@ import pytest import torch -import torchsim.estimators._perk as _perk -from torchsim.estimators import PERK +import blochsim.estimators._perk as _perk +from blochsim.estimators import PERK def assert_agrees( @@ -157,7 +157,7 @@ def test_a_gradient_wanted_for_a_fitted_tensor_falls_back(device, monkeypatch) - def _through_the_polynomial(angles: torch.Tensor) -> torch.Tensor: """``sqrt(2) * cos(angle)``, computed by the host kernel and nothing else.""" - native = pytest.importorskip("torchsim.estimators._perk_native") + native = pytest.importorskip("blochsim.estimators._perk_native") one = torch.ones(1, 1) return native.regress( angles, one, one, torch.zeros(1), torch.zeros(1), one, torch.zeros(1) diff --git a/tests/estimators/test_streaming.py b/tests/estimators/test_streaming.py index 7f8ff177..546535ff 100644 --- a/tests/estimators/test_streaming.py +++ b/tests/estimators/test_streaming.py @@ -11,13 +11,13 @@ import pytest import torch -from torchsim import ( +from blochsim import ( PERK, DictionaryMatcher, ) -from torchsim.estimators import _perk -from torchsim.sequence import execution -from torchsim.simulators import MRFSimulator +from blochsim.estimators import _perk +from blochsim.sequence import execution +from blochsim.simulators import MRFSimulator CUDA = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") TWO_CARDS = pytest.mark.skipif( diff --git a/tests/estimators/test_subspace.py b/tests/estimators/test_subspace.py index f350ac4d..1d65fed7 100644 --- a/tests/estimators/test_subspace.py +++ b/tests/estimators/test_subspace.py @@ -10,10 +10,10 @@ import pytest import torch -from torchsim import ( +from blochsim import ( Subspace, ) -from torchsim.simulators import FSESimulator +from blochsim.simulators import FSESimulator RANKS = (1, 2, 4, 8) diff --git a/tests/estimators/test_uncertainty.py b/tests/estimators/test_uncertainty.py index 483ab7d0..ea821e66 100644 --- a/tests/estimators/test_uncertainty.py +++ b/tests/estimators/test_uncertainty.py @@ -19,9 +19,9 @@ import pytest import torch -import torchsim -from torchsim.estimators import PERK, DictionaryMatcher, NonlinearLeastSquares -from torchsim.simulators import MRFSimulator +import blochsim +from blochsim.estimators import PERK, DictionaryMatcher, NonlinearLeastSquares +from blochsim.simulators import MRFSimulator CONTRASTS, VOXELS, DRAWS = 400, 40, 300 @@ -290,7 +290,7 @@ def test_a_standard_error_is_never_below_the_bound( stated = _least_squares(acquisition, noise_std).uncertainty_of(clean) _signal, sensitivity = acquisition.jacobian(["T1", "T2"], T1=truth[0], T2=truth[1]) - floor = torchsim.crlb(sensitivity, noise_variance=noise_std**2).sqrt() + floor = blochsim.crlb(sensitivity, noise_variance=noise_std**2).sqrt() for column, name in enumerate(("T1", "T2")): assert float((stated[name] / floor[:, column]).min()) > 0.99, name @@ -307,9 +307,9 @@ def test_a_voxel_that_cannot_be_read_is_infinite_rather_than_a_failure() -> None sensitivity[1, 1] = 0.0 # nothing there responds to the second parameter with pytest.raises(torch.linalg.LinAlgError): - torchsim.crlb(sensitivity) + blochsim.crlb(sensitivity) - bound = torchsim.crlb(sensitivity, singular="infinite") + bound = blochsim.crlb(sensitivity, singular="infinite") assert torch.isinf(bound[1]).all() assert torch.isfinite(bound[[0, 2]]).all() diff --git a/tests/functionals/test_bssfp_func.py b/tests/functionals/test_bssfp_func.py index 2ea0cef4..cdb9261d 100644 --- a/tests/functionals/test_bssfp_func.py +++ b/tests/functionals/test_bssfp_func.py @@ -3,7 +3,7 @@ import numpy as np from pytest import fixture -from torchsim import ( +from blochsim import ( bssfp_sim, ) diff --git a/tests/functionals/test_fse_func.py b/tests/functionals/test_fse_func.py index 82c1e4de..1ea15221 100644 --- a/tests/functionals/test_fse_func.py +++ b/tests/functionals/test_fse_func.py @@ -3,7 +3,7 @@ import numpy as np from pytest import fixture -from torchsim import ( +from blochsim import ( fse_sim, ) @@ -45,7 +45,7 @@ def test_multiple_gradient(flip): def test_current_simulator_physics_passes_through(flip): """The convenience call does not hide physics the Simulator accepts.""" - from torchsim.simulators import FSESimulator + from blochsim.simulators import FSESimulator got = fse_sim( flip, diff --git a/tests/functionals/test_mp2rage_func.py b/tests/functionals/test_mp2rage_func.py index 3022d2ff..963ca65d 100644 --- a/tests/functionals/test_mp2rage_func.py +++ b/tests/functionals/test_mp2rage_func.py @@ -1,6 +1,6 @@ """MP2RAGE tests.""" -from torchsim import mp2rage_sim +from blochsim import mp2rage_sim def test_scalar_forward(): diff --git a/tests/functionals/test_mp2rage_reference.py b/tests/functionals/test_mp2rage_reference.py index d01e994e..fb0bacbd 100644 --- a/tests/functionals/test_mp2rage_reference.py +++ b/tests/functionals/test_mp2rage_reference.py @@ -12,8 +12,8 @@ import pytest import torch -from torchsim.model import Simulator -from torchsim.simulators import MP2RAGESimulator +from blochsim.model import Simulator +from blochsim.simulators import MP2RAGESimulator # The protocol of Marques et al., Neuroimage 49(2):1271, Table 1 at 7 T. TI_MS = (800.0, 2700.0) diff --git a/tests/functionals/test_mpnrage_func.py b/tests/functionals/test_mpnrage_func.py index 2b69af30..cb58ae82 100644 --- a/tests/functionals/test_mpnrage_func.py +++ b/tests/functionals/test_mpnrage_func.py @@ -1,6 +1,6 @@ """MPnRAGE tests.""" -from torchsim import ( +from blochsim import ( mpnrage_sim, ) diff --git a/tests/functionals/test_mprage_func.py b/tests/functionals/test_mprage_func.py index 1e11aa4f..3f8d5a62 100644 --- a/tests/functionals/test_mprage_func.py +++ b/tests/functionals/test_mprage_func.py @@ -1,6 +1,6 @@ """MPRAGE tests.""" -from torchsim import ( +from blochsim import ( mprage_sim, ) diff --git a/tests/functionals/test_mrf_func.py b/tests/functionals/test_mrf_func.py index 1af564bc..9c837d44 100644 --- a/tests/functionals/test_mrf_func.py +++ b/tests/functionals/test_mrf_func.py @@ -3,7 +3,7 @@ import numpy as np from pytest import fixture -from torchsim import ( +from blochsim import ( mrf_sim, ) @@ -45,7 +45,7 @@ def test_multiple_gradient(flip): def test_current_simulator_protocol_arguments_pass_through(flip): """The convenience call accepts protocol fields added to MRFSimulator.""" - from torchsim.simulators import MRFSimulator + from blochsim.simulators import MRFSimulator phases = np.linspace(0.0, 117.0, flip.size, dtype=np.float32) got = mrf_sim( diff --git a/tests/functionals/test_spgr_func.py b/tests/functionals/test_spgr_func.py index ce97ec5f..b21c760e 100644 --- a/tests/functionals/test_spgr_func.py +++ b/tests/functionals/test_spgr_func.py @@ -3,7 +3,7 @@ import numpy as np from pytest import fixture -from torchsim import ( +from blochsim import ( spgr_sim, ) diff --git a/tests/model/test_array_backends.py b/tests/model/test_array_backends.py index c5cec521..8a0ad4b1 100644 --- a/tests/model/test_array_backends.py +++ b/tests/model/test_array_backends.py @@ -12,9 +12,9 @@ import pytest import torch -import torchsim -from torchsim.sequence._array import as_torch, backend_of, brought, like, matched -from torchsim.simulators import FSESimulator, MRFSimulator, SPGRSimulator +import blochsim +from blochsim.sequence._array import as_torch, backend_of, brought, like, matched +from blochsim.simulators import FSESimulator, MRFSimulator, SPGRSimulator cupy = pytest.importorskip("cupy", reason="no second device backend installed") @@ -129,13 +129,13 @@ def test_a_shared_parameter_is_spread_over_the_events() -> None: @pytest.mark.parametrize( "call", [ - lambda d: torchsim.spgr_sim( + lambda d: blochsim.spgr_sim( 5.0, TE=2.0, TR=10.0, T1=1000.0, T2star=100.0, device=d ), - lambda d: torchsim.bssfp_sim( + lambda d: blochsim.bssfp_sim( 5.0, TE=2.0, TR=10.0, T1=1000.0, T2=100.0, device=d ), - lambda d: torchsim.mp2rage_sim( + lambda d: blochsim.mp2rage_sim( TI=(500.0, 1500.0), flip=5.0, TRspgr=5.0, @@ -144,14 +144,14 @@ def test_a_shared_parameter_is_spread_over_the_events() -> None: T1=1000.0, device=d, ), - lambda d: torchsim.mrf_sim(FLIP, TR=10.0, T1=1000.0, T2=80.0, device=d), - lambda d: torchsim.mprage_sim( + lambda d: blochsim.mrf_sim(FLIP, TR=10.0, T1=1000.0, T2=80.0, device=d), + lambda d: blochsim.mprage_sim( TI=500.0, flip=5.0, TRspgr=5.0, nshots=128, T1=1000.0, device=d ), - lambda d: torchsim.mpnrage_sim( + lambda d: blochsim.mpnrage_sim( nshots=8, flip=5.0, TR=10.0, T1=1000.0, device=d ), - lambda d: torchsim.fse_sim( + lambda d: blochsim.fse_sim( flip=np.full(4, 120.0, dtype=np.float32), ESP=5.0, T1=1000.0, diff --git a/tests/model/test_jacobian_contract.py b/tests/model/test_jacobian_contract.py index e9347d58..e7d3a7fe 100644 --- a/tests/model/test_jacobian_contract.py +++ b/tests/model/test_jacobian_contract.py @@ -11,15 +11,15 @@ import pytest import torch -import torchsim -from torchsim.model import Simulator +import blochsim +from blochsim.model import Simulator FLIP = np.ones(7) * 120.0 ECHOES = 7 def _fse(t1, t2, diff): - return torchsim.fse_sim(flip=FLIP, ESP=5.0, T1=t1, T2=t2, diff=diff) + return blochsim.fse_sim(flip=FLIP, ESP=5.0, T1=t1, T2=t2, diff=diff) VECTOR_T1 = torch.tensor([800.0, 1000.0, 1200.0]) @@ -47,8 +47,8 @@ def test_jacobian_matches_finite_differences() -> None: """Forward mode is exact where central differences are only close.""" step = 1e-2 _, jacobian = _fse(VECTOR_T1, VECTOR_T2, "T2") - forward = torchsim.fse_sim(flip=FLIP, ESP=5.0, T1=VECTOR_T1, T2=VECTOR_T2 + step) - backward = torchsim.fse_sim(flip=FLIP, ESP=5.0, T1=VECTOR_T1, T2=VECTOR_T2 - step) + forward = blochsim.fse_sim(flip=FLIP, ESP=5.0, T1=VECTOR_T1, T2=VECTOR_T2 + step) + backward = blochsim.fse_sim(flip=FLIP, ESP=5.0, T1=VECTOR_T1, T2=VECTOR_T2 - step) numerical = (forward - backward) / (2.0 * step) assert torch.allclose(jacobian, numerical, atol=1e-4) @@ -56,7 +56,7 @@ def test_jacobian_matches_finite_differences() -> None: def test_signal_matches_undifferentiated_call() -> None: """The primal returned alongside the Jacobian is the ordinary signal.""" signal, _ = _fse(VECTOR_T1, VECTOR_T2, "T2") - plain = torchsim.fse_sim(flip=FLIP, ESP=5.0, T1=VECTOR_T1, T2=VECTOR_T2) + plain = blochsim.fse_sim(flip=FLIP, ESP=5.0, T1=VECTOR_T1, T2=VECTOR_T2) assert torch.equal(signal, plain) @@ -68,7 +68,7 @@ def test_the_answer_comes_back_in_the_array_library_it_was_asked_in() -> None: """ numpy_t1 = VECTOR_T1.numpy() numpy_t2 = VECTOR_T2.numpy() - signal, jacobian = torchsim.fse_sim( + signal, jacobian = blochsim.fse_sim( flip=FLIP, ESP=5.0, T1=numpy_t1, T2=numpy_t2, diff="T2" ) assert isinstance(signal, np.ndarray) diff --git a/tests/model/test_model_physics.py b/tests/model/test_model_physics.py index 0cd04efb..c4192a08 100644 --- a/tests/model/test_model_physics.py +++ b/tests/model/test_model_physics.py @@ -12,14 +12,14 @@ import pytest import torch -from torchsim.model import UNBALANCED, Simulator, SpinPhysics -from torchsim.sequence import ( +from blochsim.model import UNBALANCED, Simulator, SpinPhysics +from blochsim.sequence import ( EpgEngine, TissueProperties, _accelerators, mrf_description, ) -from torchsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES +from blochsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES FLIP = torch.linspace(5.0, 60.0, 24) T1 = torch.tensor([600.0, 1000.0, 1400.0]) diff --git a/tests/model/test_signal_model.py b/tests/model/test_signal_model.py index b24fe641..214946be 100644 --- a/tests/model/test_signal_model.py +++ b/tests/model/test_signal_model.py @@ -13,8 +13,8 @@ import pytest import torch -from torchsim.model import REFOCUSED, Simulator, SpinPhysics -from torchsim.sequence import _simulation +from blochsim.model import REFOCUSED, Simulator, SpinPhysics +from blochsim.sequence import _simulation class Relaxation(Simulator): diff --git a/tests/model/test_state_machine.py b/tests/model/test_state_machine.py index 1b47b129..949fcd70 100644 --- a/tests/model/test_state_machine.py +++ b/tests/model/test_state_machine.py @@ -15,7 +15,7 @@ import pytest import torch -from torchsim.model import ( +from blochsim.model import ( BALANCED, REFOCUSED, SPOILED, @@ -24,8 +24,8 @@ Simulator, SpinPhysics, ) -from torchsim.sequence import EpgEngine, EventAction, EventType, ideal_rf_definition -from torchsim.simulators import MRFSimulator +from blochsim.sequence import EpgEngine, EventAction, EventType, ideal_rf_definition +from blochsim.simulators import MRFSimulator FLIP = torch.linspace(5.0, 60.0, 12) T1 = torch.tensor([600.0, 1000.0, 1400.0]) @@ -102,7 +102,7 @@ def test_the_trigger_leaves_the_description_ordinary() -> None: def test_a_shipped_simulator_reaches_the_fused_kernels(monkeypatch) -> None: """A fast path has to be shown taken, not inferred from agreement.""" - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators seen: list[int] = [] original = _accelerators._run_packed @@ -177,7 +177,7 @@ def test_a_handed_over_description_agrees_with_the_engine_directly() -> None: described = protocol.describe(flip=FLIP, TR=10.0) handed = Simulator.from_description(described, protocol.model, states=8) - from torchsim.sequence import TissueProperties + from blochsim.sequence import TissueProperties direct = ( EpgEngine() @@ -198,7 +198,7 @@ def test_the_protocol_may_be_overridden_per_call() -> None: def test_a_trigger_table_defaults_to_the_bare_operators() -> None: """An unassigned slot is the plain event, not a surprise.""" - from torchsim.sequence import Excitation, Readout + from blochsim.sequence import Excitation, Readout table = EventOperators() assert table.excitation is Excitation @@ -238,9 +238,9 @@ def test_a_gradient_standing_on_its_own_does_not_survive_the_round_trip() -> Non nor a sample, so nothing reinstates it, and a stream arriving from a scanner could not have carried it either. """ - from torchsim import Delay, Excitation, Spoil - from torchsim.model._state_machine import realised - from torchsim.sequence import EventAction + from blochsim import Delay, Excitation, Spoil + from blochsim.model._state_machine import realised + from blochsim.sequence import EventAction class Prepared(Simulator): model = _with(SPOILED).model @@ -277,8 +277,8 @@ def test_shim_definition_reaches_the_pulses(): array whose sensitivities are alike cancels exactly when the drive is a half turn apart, and does not when it is not. """ - from torchsim import ShimDefinition - from torchsim.simulators import FSESimulator + from blochsim import ShimDefinition + from blochsim.simulators import FSESimulator array = dict( T1=torch.tensor([1000.0]), diff --git a/tests/optim/test_binding.py b/tests/optim/test_binding.py index 9d8938a5..ade5a224 100644 --- a/tests/optim/test_binding.py +++ b/tests/optim/test_binding.py @@ -14,11 +14,11 @@ import pytest import torch -from torchsim.model import Simulator, SpinPhysics -from torchsim.model._binding import bind -from torchsim.sequence import _accelerators -from torchsim.sequence._accelerators import pack_description -from torchsim.simulators import ( +from blochsim.model import Simulator, SpinPhysics +from blochsim.model._binding import bind +from blochsim.sequence import _accelerators +from blochsim.sequence._accelerators import pack_description +from blochsim.simulators import ( FSESimulator, MPnRAGESimulator, MPRAGESimulator, diff --git a/tests/optim/test_design.py b/tests/optim/test_design.py index 2789ed8e..803da1ae 100644 --- a/tests/optim/test_design.py +++ b/tests/optim/test_design.py @@ -12,8 +12,8 @@ import pytest import torch -from torchsim.optim import Bounded, SequenceDesign, crlb -from torchsim.simulators import FSESimulator, SPGRSimulator, bSSFPSimulator +from blochsim.optim import Bounded, SequenceDesign, crlb +from blochsim.simulators import FSESimulator, SPGRSimulator, bSSFPSimulator TISSUE = {"T1": [800.0, 1400.0], "T2": [45.0, 120.0]} diff --git a/tests/recon/test_constraints.py b/tests/recon/test_constraints.py index b43e59bc..c7e67f1f 100644 --- a/tests/recon/test_constraints.py +++ b/tests/recon/test_constraints.py @@ -9,8 +9,8 @@ import pytest import torch -from torchsim.model import Simulator -from torchsim.recon import ( +from blochsim.model import Simulator +from blochsim.recon import ( GaussNewton, ModelOperator, Schedule, @@ -18,10 +18,10 @@ direct, iterative, ) -from torchsim.simulators import MultiEchoSimulator +from blochsim.simulators import MultiEchoSimulator #: ``iterative()`` with nothing named falls back to deepinv's ``least_squares``, -#: which TorchSim does not depend on. +#: which BlochSim does not depend on. needs_deepinv = pytest.mark.skipif( importlib.util.find_spec("deepinv") is None, reason="deepinv supplies the fallback inner solve", diff --git a/tests/recon/test_deepinv.py b/tests/recon/test_deepinv.py index e568b891..193367b7 100644 --- a/tests/recon/test_deepinv.py +++ b/tests/recon/test_deepinv.py @@ -5,11 +5,11 @@ import pytest import torch -from torchsim import ( +from blochsim import ( execution, ) -from torchsim.recon import ModelOperator -from torchsim.simulators import MultiEchoSimulator +from blochsim.recon import ModelOperator +from blochsim.simulators import MultiEchoSimulator deepinv = pytest.importorskip("deepinv") @@ -68,7 +68,7 @@ def test_differentiating_through_the_operator_does_not_stream_it( differentiated, and the streamed path would silently give it nothing, so a call under one runs where it stands however loud the policy is. """ - from torchsim.recon import _operator as module + from blochsim.recon import _operator as module entered = [] real = module.per_voxel @@ -108,7 +108,7 @@ def test_a_deepinv_optimizer_drives_the_composed_operator(operator) -> None: Composing this way gives up the analytic derivative -- deepinv takes the Jacobian products by differentiating the whole chain -- which is the trade for reaching every optimizer and prior it has. - :class:`~torchsim.recon.GaussNewton` chains the two operators' own + :class:`~blochsim.recon.GaussNewton` chains the two operators' own products instead and keeps it. """ from deepinv.optim import L2 diff --git a/tests/recon/test_gauss_newton.py b/tests/recon/test_gauss_newton.py index 41d001b6..7fc66083 100644 --- a/tests/recon/test_gauss_newton.py +++ b/tests/recon/test_gauss_newton.py @@ -8,8 +8,8 @@ import pytest import torch -from torchsim.estimators import NonlinearLeastSquares -from torchsim.recon import ( +from blochsim.estimators import NonlinearLeastSquares +from blochsim.recon import ( GaussNewton, LeastSquares, Linearization, @@ -19,10 +19,10 @@ direct, iterative, ) -from torchsim.simulators import MultiEchoSimulator +from blochsim.simulators import MultiEchoSimulator #: ``iterative()`` with nothing named falls back to deepinv's ``least_squares``, -#: which TorchSim does not depend on. What is asserted under this mark is the +#: which BlochSim does not depend on. What is asserted under this mark is the #: fallback; the duck-typed route beside it is what holds without deepinv. needs_deepinv = pytest.mark.skipif( importlib.util.find_spec("deepinv") is None, @@ -119,7 +119,7 @@ def test_the_two_policies_land_in_the_same_place(problem) -> None: def test_a_trust_region_is_the_fit_that_ships(problem) -> None: """The generalization is exact: the loop reproduces the estimator. - :class:`~torchsim.NonlinearLeastSquares` is this loop under a per-voxel + :class:`~blochsim.NonlinearLeastSquares` is this loop under a per-voxel trust region with the voxel-diagonal solve, and nothing else. """ acquisition = MultiEchoSimulator(TE=TE_MS) @@ -351,7 +351,7 @@ def test_one_of_deepinvs_solvers_is_that_function_with_its_argument_bound( ) -> None: """Which one suits is the caller's to measure, so all of them must run. - Binding the argument is the caller's own composition -- TorchSim takes an + Binding the argument is the caller's own composition -- BlochSim takes an object and never a name. """ least_squares = pytest.importorskip("deepinv.optim.linear").least_squares diff --git a/tests/recon/test_operator.py b/tests/recon/test_operator.py index bb36521d..e5ff6dc5 100644 --- a/tests/recon/test_operator.py +++ b/tests/recon/test_operator.py @@ -5,12 +5,12 @@ import pytest import torch -from torchsim import ( +from blochsim import ( Subspace, execution, ) -from torchsim.recon import ModelOperator -from torchsim.simulators import FSESimulator, MultiEchoSimulator +from blochsim.recon import ModelOperator +from blochsim.simulators import FSESimulator, MultiEchoSimulator TE_MS = torch.tensor([10.0, 20.0, 40.0, 80.0, 160.0]) BOUND = {"T2": (10.0, 300.0)} diff --git a/tests/recon/test_subspace_route.py b/tests/recon/test_subspace_route.py index 216039e2..bbc5f240 100644 --- a/tests/recon/test_subspace_route.py +++ b/tests/recon/test_subspace_route.py @@ -6,11 +6,11 @@ import pytest import torch -from torchsim import ( +from blochsim import ( Subspace, ) -from torchsim.estimators import DictionaryMatcher -from torchsim.simulators import MultiEchoSimulator +from blochsim.estimators import DictionaryMatcher +from blochsim.simulators import MultiEchoSimulator mrinufft = pytest.importorskip("mrinufft") diff --git a/tests/sequence/test_against_epgpy.py b/tests/sequence/test_against_epgpy.py index 42e9cf28..4b91b6f2 100644 --- a/tests/sequence/test_against_epgpy.py +++ b/tests/sequence/test_against_epgpy.py @@ -1,8 +1,8 @@ """The same sequences, run by another EPG library. -Every other test in this suite is TorchSim measured against a closed form, a +Every other test in this suite is BlochSim measured against a closed form, a published figure, or an integration written out beside it. This one is -TorchSim measured against a second implementation of the whole formalism: +BlochSim measured against a second implementation of the whole formalism: `epgpy `_, which shares no code, no author and no array library with this one. @@ -22,7 +22,7 @@ import numpy as np import pytest -from torchsim import ( +from blochsim import ( Delay, Dephase, Excitation, @@ -30,7 +30,7 @@ Refocusing, SequenceDescription, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, ) @@ -41,7 +41,7 @@ ORDERS = 40 -def _torchsim(parts, nstates=ORDERS): +def _blochsim(parts, nstates=ORDERS): signal = ( EpgEngine() .simulate( @@ -92,7 +92,7 @@ def test_a_refocused_train_agrees_echo_for_echo(flip_deg: float) -> None: Readout(0.0), ] - theirs, ours = _epgpy(theirs), _torchsim(ours) + theirs, ours = _epgpy(theirs), _blochsim(ours) assert ours.shape == theirs.shape assert np.abs(ours - theirs).max() / theirs.max() < 1e-5 @@ -131,7 +131,7 @@ def test_a_spoiled_train_agrees_over_two_hundred_repetitions() -> None: ) ] - theirs, ours = _epgpy(theirs, nstates=30), _torchsim(ours, nstates=30) + theirs, ours = _epgpy(theirs, nstates=30), _blochsim(ours, nstates=30) assert np.abs(ours - theirs).max() / theirs.max() < 1e-3 # And it is a settled answer rather than two curves that never started. assert theirs[-1] > 0.01 diff --git a/tests/sequence/test_both_pools.py b/tests/sequence/test_both_pools.py index 7e5e0d80..80a9a77c 100644 --- a/tests/sequence/test_both_pools.py +++ b/tests/sequence/test_both_pools.py @@ -17,18 +17,18 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( NO_GEOMETRY, _pack_events, _run_packed, ) -from torchsim.sequence._lineshape import lineshape_table -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._lineshape import lineshape_table +from blochsim.sequence._simulation import _prepare_tissue from utils.packed_reference import simulate_packed ECHOES = 8 @@ -156,7 +156,7 @@ def _free_induction(times_s, **properties): ``times_s`` are measured from the excitation; the events carry the intervals between them, which is what the state machine steps through. """ - from torchsim.sequence._accelerators import _EXCITATION, _RECORD + from blochsim.sequence._accelerators import _EXCITATION, _RECORD intervals = [ after - before for before, after in zip((0.0, *times_s), times_s, strict=False) @@ -252,7 +252,7 @@ def _inversion_recovery(delays_s, **properties): fixed point. The readout is a hard ninety degrees, which writes ``Z`` into the transverse plane. """ - from torchsim.sequence._accelerators import _EXCITATION, _INVERSION, _RECORD + from blochsim.sequence._accelerators import _EXCITATION, _INVERSION, _RECORD prepared, _, _ = _prepare_tissue( TissueProperties(**{"t1_ms": 1000.0, "t2_ms": 80.0, **properties}), "cpu" @@ -357,7 +357,7 @@ def _live_events(): of a difference, which is a property of the probe rather than of the kernel; here the recovery interval makes them live. """ - from torchsim.sequence._accelerators import _EXCITATION, _INVERSION, _RECORD + from blochsim.sequence._accelerators import _EXCITATION, _INVERSION, _RECORD return ( torch.tensor([0.0, 0.35, 0.0, 0.0, 3e-3, 0.0], dtype=torch.float32), @@ -381,7 +381,7 @@ def _prepared(device="cpu", **properties): def _live_readout(prepared, seed=None, device="cpu"): """The reading, or its directional derivative.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp events = tuple(value.to(device) for value in _live_events()) table = lineshape_table(device=torch.device(device)) @@ -407,7 +407,7 @@ def test_forward_mode_matches_finite_differences(name: str) -> None: """Every direction the three-pool system carries, including the two that only the semisolid pool has and the five only the exchanging one does. """ - from torchsim.sequence._parameters import TISSUE_NAMES + from blochsim.sequence._parameters import TISSUE_NAMES index = TISSUE_NAMES.index(name) prepared = _prepared() @@ -493,7 +493,7 @@ def run(value): def _live_adjoint(prepared, seed): """The gradients a cotangent on the reading leaves.""" - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp return _run_packed_vjp( prepared, @@ -531,7 +531,7 @@ def test_the_adjoint_matches_finite_differences(name: str) -> None: """Every direction three pools carry, including the semisolid pool's own three and the exchanging pool's five. """ - from torchsim.sequence._parameters import TISSUE_NAMES + from blochsim.sequence._parameters import TISSUE_NAMES index = TISSUE_NAMES.index(name) seed = _cotangent() @@ -581,7 +581,7 @@ def test_the_second_order_pass_differentiates_the_adjoint(): """Given no direction to follow, the forward-over-reverse kernel returns the adjoint on its own -- and given one, the adjoint's own derivative. """ - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp prepared = _prepared() events = _live_events() @@ -603,7 +603,7 @@ def test_the_second_order_pass_differentiates_the_adjoint(): if scale > 1e-9: assert float((expected - measured).abs().max()) / scale < 1e-4 - from torchsim.sequence._parameters import TISSUE_NAMES + from blochsim.sequence._parameters import TISSUE_NAMES # The direction is confined to the properties the difference below moves, # so the two are contracted against the same one. @@ -648,7 +648,7 @@ def test_the_second_order_pass_saturates_the_pool_the_pulse_deposits_into(): what the first-order adjoint does, and the two reach the saturation by different code. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _run_packed_vjp, _run_packed_vjp_jvp, ) @@ -863,7 +863,7 @@ def _train_events(): def _routes(leaves, events): """The kernels and the oracle, fed from one place.""" - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg fused = _NativeEpg.apply( *leaves, @@ -1034,7 +1034,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_a_streamed_volume_matches_the_whole_one(): """Streaming cuts the voxel axis, which all three pools follow.""" - from torchsim.sequence._accelerators import offload + from blochsim.sequence._accelerators import offload voxels = 3000 packed = _pack_events( @@ -1088,7 +1088,7 @@ def test_a_streamed_adjoint_matches_the_whole_one(): Given no direction to follow the forward-over-reverse kernel returns the adjoint on its own, and that is the reverse route the offload plan reaches. """ - from torchsim.sequence._accelerators import _run_packed_vjp_jvp, offload + from blochsim.sequence._accelerators import _run_packed_vjp_jvp, offload voxels = 3000 packed = _pack_events( @@ -1205,7 +1205,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_a_streamed_forward_mode_matches_the_whole_one(): """Streaming cuts the voxel axis, which all three pools' seeds follow.""" - from torchsim.sequence._accelerators import offload + from blochsim.sequence._accelerators import offload voxels = 3000 prepared = _prepared( @@ -1243,8 +1243,8 @@ def _instantaneous_table(): """A pulse with no gradient across it: one rotation, every position.""" import numpy as np - from torchsim.sequence._description import RfDefinition, RfShape - from torchsim.sequence._transition import transition_table + from blochsim.sequence._description import RfDefinition, RfShape + from blochsim.sequence._transition import transition_table flat = RfDefinition( id=0, @@ -1262,7 +1262,7 @@ def test_a_tabulated_rotation_reaches_both_pools() -> None: """The kernels are templated on the rotation mode and the pool count together, so a table beside three pools is an instantiation of its own. """ - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg leaves = _prepared() events = _train_events() @@ -1308,9 +1308,9 @@ def test_three_pools_take_the_first_order_kernel_on_the_card( forward-over-reverse pass on the same card: two arms of one wrong kernel agree with each other, and the backends share no code. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp - from torchsim.sequence._parameters import TISSUE_NAMES + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._parameters import TISSUE_NAMES voxels = 64 tissue = TissueProperties( @@ -1473,8 +1473,8 @@ def test_the_series_carries_the_answer_up_to_the_spread_it_is_trusted_to() -> No import triton import triton.language as tl - from torchsim.sequence._epg_triton import _three_pool_step - from torchsim.sequence._parameters import NARROW_SPREAD + from blochsim.sequence._epg_triton import _three_pool_step + from blochsim.sequence._parameters import NARROW_SPREAD @triton.jit def only_the_series( @@ -1557,7 +1557,7 @@ def test_a_long_interval_declines_the_series_branch() -> None: so the launch has to fall back to the roots -- checked on the predicate the launchers call, not inferred from the answer agreeing. """ - from torchsim.sequence._parameters import narrow_three_pool + from blochsim.sequence._parameters import narrow_three_pool voxels = 256 tissue, _, _ = _prepare_tissue( @@ -1597,7 +1597,7 @@ def test_the_series_branch_gives_the_answer_the_roots_give(state_count) -> None: The gate cannot be measured against the host: that comparison moves for reasons of its own. What it has to be held to is the branch it replaces. """ - from torchsim.sequence import _accelerators, _epg_triton + from blochsim.sequence import _accelerators, _epg_triton voxels = 256 tissue, _, _ = _prepare_tissue( diff --git a/tests/sequence/test_bound_pool.py b/tests/sequence/test_bound_pool.py index 1c7128d1..155611f0 100644 --- a/tests/sequence/test_bound_pool.py +++ b/tests/sequence/test_bound_pool.py @@ -17,20 +17,20 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( NO_GEOMETRY, _pack_events, _run_packed, ) -from torchsim.sequence._description import EventType, RfDefinition, RfShape -from torchsim.sequence._lineshape import lineshape_table -from torchsim.sequence._parameters import TISSUE_NAMES -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._description import EventType, RfDefinition, RfShape +from blochsim.sequence._lineshape import lineshape_table +from blochsim.sequence._parameters import TISSUE_NAMES +from blochsim.sequence._simulation import _prepare_tissue from utils.packed_reference import simulate_packed ECHOES = 8 @@ -131,7 +131,7 @@ def _inversion_recovery(delays_s, **properties): 90 degrees, which writes ``Z`` into the transverse plane, so the recorded signal is proportional to the free pool's longitudinal state. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _EXCITATION, _INVERSION, _RECORD, @@ -336,7 +336,7 @@ def _saturation_events(saturation, offset_hz, delay_s=0.3): the reading proportional to ``Z_a``, unlike an echo train's magnitudes, which mix pathways and are not monotone in it. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _EXCITATION, _RECORD, _SPOIL_AFTER, @@ -473,7 +473,7 @@ def test_the_builders_idealized_pulse_declares_almost_no_power(): def _live_readout(prepared, seed=None): """The saturate-then-read signal, or its directional derivative.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp events = _saturation_events(MILLISECOND_PULSE, PULSE_OFFSET_HZ, delay_s=0.4) table = lineshape_table() @@ -640,7 +640,7 @@ def _live_events(): def _live_adjoint(prepared, seed, profile=None): """Gradients of ``Re()`` w.r.t. every differentiable input.""" - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp return _run_packed_vjp( prepared, @@ -678,7 +678,7 @@ def test_the_adjoint_is_the_transpose_of_the_forward_direction( reaches the answer through both the rotation and the power the pulse deposits, and the two arrive at its gradient by different routes. """ - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp profile = _instantaneous_table() if tabulated else None prepared = _prepared(**LIVE) @@ -774,7 +774,7 @@ def test_the_second_order_pass_carries_the_bound_pool(): so an error in any entry of it shows up rather than only in the one the bound pool is most obviously responsible for. """ - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg prepared = _prepared(**LIVE) events = _live_events() @@ -975,7 +975,7 @@ def _tuned(offset_hz: float): def test_the_offset_a_sequence_drives_its_pulses_to_is_read_off_the_events(): - from torchsim.sequence._accelerators import largest_pulse_offset + from blochsim.sequence._accelerators import largest_pulse_offset assert largest_pulse_offset(_description()) == 0.0 assert largest_pulse_offset(_tuned(-6.0e3)) == 6.0e3 @@ -990,7 +990,7 @@ def test_a_table_stopping_short_cannot_tell_two_far_pulses_apart(): a pulse at 60 kHz and one at 90 kHz the same answer; one sized to reach them does not. """ - from torchsim.sequence._lineshape import lineshape_reaching + from blochsim.sequence._lineshape import lineshape_reaching near, far = torch.tensor(60.0e3), torch.tensor(90.0e3) stops_short = lineshape_table() @@ -1003,7 +1003,7 @@ def test_a_table_stopping_short_cannot_tell_two_far_pulses_apart(): def test_reaching_further_adds_knots_rather_than_spreading_them(): """A sequence played further out gets a longer table, not a coarser one.""" - from torchsim.sequence._lineshape import lineshape_reaching + from blochsim.sequence._lineshape import lineshape_reaching default = lineshape_table() stretched = lineshape_reaching(90.0e3) @@ -1021,7 +1021,7 @@ def test_sequences_asking_for_similar_ranges_share_one_table(): that happen for ranges that merely differ, rather than only for ones that match to the hertz. """ - from torchsim.sequence._lineshape import OFFSET_MAX_HZ, lineshape_reaching + from blochsim.sequence._lineshape import OFFSET_MAX_HZ, lineshape_reaching assert lineshape_reaching(0.0) is lineshape_reaching(30.0e3) assert lineshape_reaching(0.0).offset_max_hz == OFFSET_MAX_HZ @@ -1034,7 +1034,7 @@ def test_the_voxel_is_counted_beside_the_pulse(): from the sequence alone still stops short of what an off-resonant voxel reads. """ - from torchsim.sequence._simulation import _absorption_table + from blochsim.sequence._simulation import _absorption_table still = _absorption_table(_tuned(33.0e3), torch.zeros(1), None) spread = _absorption_table(_tuned(33.0e3), torch.tensor([-8.0e3, 8.0e3]), None) @@ -1073,7 +1073,7 @@ def signal(offset_hz): def _instantaneous_table(): """A pulse with no gradient across it: one rotation, every position.""" - from torchsim.sequence._transition import transition_table + from blochsim.sequence._transition import transition_table flat = RfDefinition( id=0, @@ -1113,7 +1113,7 @@ def test_a_tabulated_bound_pool_run_is_differentiable_both_ways(): """The profiled and bound-pool kernels are one instantiation, so the two features have to be checked together rather than each beside a plain run. """ - from torchsim.sequence._accelerators import _run_packed_jvp, _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_jvp, _run_packed_vjp prepared = _prepared(**LIVE) events = _live_events() @@ -1177,7 +1177,7 @@ def moved(step): def _routes(leaves, events, profile=None): """The kernels and the oracle, fed from one place.""" - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg table = lineshape_table() fused = _NativeEpg.apply( @@ -1349,7 +1349,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_the_cuda_forward_mode_matches_the_cpu_kernel(): """The tangent of the two-pool step, and of the saturation, on the card.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp voxels = 6 spread = dict( @@ -1385,7 +1385,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_a_streamed_volume_matches_the_whole_one(): """Streaming cuts the voxel axis, which the bound pool's buffers follow.""" - from torchsim.sequence._accelerators import offload + from blochsim.sequence._accelerators import offload voxels = 3000 packed = _pack_events( @@ -1416,7 +1416,7 @@ def test_a_streamed_volume_matches_the_whole_one(): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_a_streamed_forward_mode_matches_the_whole_one(): """Streaming cuts the voxel axis, which the bound pool's seeds follow.""" - from torchsim.sequence._accelerators import _run_packed_jvp, offload + from blochsim.sequence._accelerators import _run_packed_jvp, offload voxels = 3000 prepared = _prepared( @@ -1484,7 +1484,7 @@ def test_the_cuda_adjoint_matches_the_cpu_kernel(): the signal and disagrees on every gradient, including the ones that have nothing to do with the bound pool. """ - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp voxels = 6 events = _saturation_events(MILLISECOND_PULSE, PULSE_OFFSET_HZ, delay_s=0.4) @@ -1507,7 +1507,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_the_cuda_second_order_pass_matches_the_cpu_kernel(): """Forward-over-reverse, where a direction rides through the adjoint.""" - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp voxels = 6 events = _saturation_events(MILLISECOND_PULSE, PULSE_OFFSET_HZ, delay_s=0.4) @@ -1546,7 +1546,7 @@ def test_a_streamed_adjoint_matches_the_whole_one(): its own is that pass given no direction to follow -- so it is the one the chunked buffers have to carry the second pool through. """ - from torchsim.sequence._accelerators import _run_packed_vjp_jvp, offload + from blochsim.sequence._accelerators import _run_packed_vjp_jvp, offload voxels = 3000 prepared = _prepared(**_spread(voxels)) @@ -1593,7 +1593,7 @@ def test_a_streamed_public_simulation_matches_the_whole_one(): """The path a user takes: a volume simulated through the public API with the devices given less memory than it needs, so it arrives in chunks. """ - from torchsim.sequence._accelerators import offload + from blochsim.sequence._accelerators import offload voxels = 3000 tissue = _volume( @@ -1622,7 +1622,7 @@ def test_a_gradient_taken_after_a_streamed_forward_matches_the_whole_one(): replays the trajectory from the inputs rather than from anything the forward left, so the two halves have to agree across that boundary. """ - from torchsim.sequence._accelerators import offload + from blochsim.sequence._accelerators import offload voxels = 3000 @@ -1659,8 +1659,8 @@ def test_a_pool_takes_the_first_order_kernel_on_the_card(state_count) -> None: forward-over-reverse pass on the same card: two arms of one wrong kernel agree with each other, and the backends share no code. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp voxels = 64 tissue = TissueProperties( diff --git a/tests/sequence/test_calibration.py b/tests/sequence/test_calibration.py index 4826f541..96d517f5 100644 --- a/tests/sequence/test_calibration.py +++ b/tests/sequence/test_calibration.py @@ -11,7 +11,7 @@ import pytest import torch -from torchsim.sequence._calibration import crossover, detection +from blochsim.sequence._calibration import crossover, detection KINDS = ["forward", "jvp", "adjoint"] diff --git a/tests/sequence/test_cuda_parity.py b/tests/sequence/test_cuda_parity.py index ca1d6adf..41afb3b0 100644 --- a/tests/sequence/test_cuda_parity.py +++ b/tests/sequence/test_cuda_parity.py @@ -8,16 +8,16 @@ import pytest import torch -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( _pack_events, _run_packed, _run_packed_jvp, _run_packed_vjp_jvp, geometry_of, ) -from torchsim.sequence._builders import fse_description -from torchsim.sequence._parameters import OUTSIDE_THE_SUBSPACE -from torchsim.sequence._simulation import TissueProperties, _prepare_tissue +from blochsim.sequence._builders import fse_description +from blochsim.sequence._parameters import OUTSIDE_THE_SUBSPACE +from blochsim.sequence._simulation import TissueProperties, _prepare_tissue pytestmark = pytest.mark.skipif( not torch.cuda.is_available(), reason="CUDA is unavailable" @@ -34,7 +34,7 @@ def _probed_atoms(kind="forward", trains=64, share=8): Taken from the threshold rather than written down, so the size follows it wherever it moves. """ - from torchsim.sequence._calibration import detection + from blochsim.sequence._calibration import detection events, _prepared, _count = _real_case(trains, "cpu", atoms=1) per_atom = trains * int(events[1].numel()) @@ -231,7 +231,7 @@ def test_an_off_resonance_seed_keeps_the_complex_kernel_on_cuda(): @pytest.mark.parametrize("block_states", [4, 16]) def test_the_packing_width_is_a_power_of_two(block_states): """It indexes a ``tl.arange``, which rejects anything else.""" - from torchsim.sequence._epg_triton import _problems_per_program + from blochsim.sequence._epg_triton import _problems_per_program width = _problems_per_program(block_states) assert width >= 1 @@ -246,7 +246,7 @@ def test_the_packing_width_ignores_how_many_problems_there_are(block_states): off the launch size would make a streamed volume answer differently from the same volume run whole. """ - from torchsim.sequence._epg_triton import _problems_per_program + from blochsim.sequence._epg_triton import _problems_per_program assert _problems_per_program(block_states) >= 1 @@ -269,7 +269,7 @@ def test_a_small_problem_skips_the_subspace_test_on_cuda(): A GPU clears the work behind the verdict fast enough that a test worth running on the CPU is pure overhead here. """ - from torchsim.sequence._calibration import detection + from blochsim.sequence._calibration import detection assert detection("forward", torch.device("cuda", 0)) > detection( "forward", torch.device("cpu") @@ -362,7 +362,7 @@ def test_an_inversion_pulse_reaches_the_same_gradients(inversion): def test_a_trajectory_too_large_for_one_launch_is_split(monkeypatch): """The grid rounds up past a wave, onto rows the next launch owns.""" - from torchsim.sequence import _epg_triton + from blochsim.sequence import _epg_triton expected = _second_order("cpu", 17, 5) monkeypatch.setattr(_epg_triton, "_TRAJECTORY_BUDGET_BYTES", 40_000) @@ -389,7 +389,7 @@ def test_the_directions_outside_the_subspace_stay_zero(): def _spgr_description(flip): - from torchsim.sequence._builders import spgr_description + from blochsim.sequence._builders import spgr_description return spgr_description( flip, @@ -496,7 +496,7 @@ def test_the_complex_second_order_kernel_matches_across_shapes(trains, atoms): def test_the_complex_trajectory_splits_into_waves(monkeypatch): """Twice the planes of the real one, so it reaches the budget sooner.""" - from torchsim.sequence import _epg_triton + from blochsim.sequence import _epg_triton expected = _complex_second_order("cpu", 17, 5) monkeypatch.setattr(_epg_triton, "_TRAJECTORY_BUDGET_BYTES", 40_000) @@ -542,7 +542,7 @@ def test_the_second_order_path_needs_no_subspace_verdict_on_cuda(): def test_device_tensors_are_refused_by_the_cpu_kernels(): """A CPU kernel must never be handed a device pointer.""" - from torchsim.sequence._accelerators import _pointers + from blochsim.sequence._accelerators import _pointers events, prepared, _ = _real_case(4, "cuda", atoms=2) with pytest.raises(ValueError, match="CPU tensors"): diff --git a/tests/sequence/test_description.py b/tests/sequence/test_description.py index 6cd37014..e98b946c 100644 --- a/tests/sequence/test_description.py +++ b/tests/sequence/test_description.py @@ -6,7 +6,7 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( AdcRole, EventType, RfDefinition, diff --git a/tests/sequence/test_description_helper.py b/tests/sequence/test_description_helper.py index f92f1f52..35e6bb3a 100644 --- a/tests/sequence/test_description_helper.py +++ b/tests/sequence/test_description_helper.py @@ -1,7 +1,7 @@ """Assembling a description out of operators in one call. -The claim is only that :func:`~torchsim.description` is -:func:`~torchsim.compose` with the surrounding fields filled in, so what is +The claim is only that :func:`~blochsim.description` is +:func:`~blochsim.compose` with the surrounding fields filled in, so what is checked is that a description built the short way simulates to the same signal as one built the long way -- and that the fields a caller does not name take values that mean "nothing here" rather than being quietly dropped. @@ -14,7 +14,7 @@ import pytest import torch -from torchsim import ( +from blochsim import ( Delay, Excitation, Readout, @@ -22,7 +22,7 @@ SequenceDescription, ShimDefinition, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, compose, diff --git a/tests/sequence/test_diffusion_kernels.py b/tests/sequence/test_diffusion_kernels.py index 92a5cfbb..4974915b 100644 --- a/tests/sequence/test_diffusion_kernels.py +++ b/tests/sequence/test_diffusion_kernels.py @@ -12,11 +12,11 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( NO_GEOMETRY, _NativeEpg, _pack_events, @@ -25,8 +25,8 @@ _run_packed_vjp_jvp, damping_scale, ) -from torchsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES +from blochsim.sequence._simulation import _prepare_tissue from utils.epg import diffusion_op from utils.packed_reference import simulate_packed diff --git a/tests/sequence/test_distribute.py b/tests/sequence/test_distribute.py index 58bd3a20..8ba4288f 100644 --- a/tests/sequence/test_distribute.py +++ b/tests/sequence/test_distribute.py @@ -19,9 +19,9 @@ _worst_disagreement, ) -import torchsim.sequence._accelerators as accelerators -from torchsim.sequence import distribute -from torchsim.sequence._accelerators import ( +import blochsim.sequence._accelerators as accelerators +from blochsim.sequence import distribute +from blochsim.sequence._accelerators import ( _run_packed, _run_packed_jvp, _run_packed_vjp_jvp, @@ -191,7 +191,7 @@ def test_a_sharded_first_order_adjoint_matches_the_whole_one(count): """Shards of a gradient are partial sums, and each takes the kernel the whole one would have. """ - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp events, prepared, outputs = _real_case(TRAINS, "cuda", atoms=ATOMS) seed = torch.ones((TRAINS, ATOMS, outputs), dtype=torch.complex64, device="cuda") @@ -214,8 +214,8 @@ def test_a_shard_reaches_the_first_order_kernel(monkeypatch): to the forward-over-reverse pass, which costs twice the time and twice the trajectory for a derivative nobody asked for. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp events, prepared, outputs = _real_case(TRAINS, "cuda", atoms=ATOMS) seed = torch.ones((TRAINS, ATOMS, outputs), dtype=torch.complex64, device="cuda") diff --git a/tests/sequence/test_dynamic_transmit.py b/tests/sequence/test_dynamic_transmit.py index a5c19675..4771a0ad 100644 --- a/tests/sequence/test_dynamic_transmit.py +++ b/tests/sequence/test_dynamic_transmit.py @@ -14,8 +14,8 @@ import pytest import torch -from torchsim.sequence._description import RfDefinition, RfShape -from torchsim.sequence._transition import ( +from blochsim.sequence._description import RfDefinition, RfShape +from blochsim.sequence._transition import ( dynamic_pair, transition_table, ) @@ -294,10 +294,10 @@ def _train(): the tabulated route and the per-voxel one describe -- so it is the case that can hold them against each other. """ - from torchsim.sequence._accelerators import _pack_events, _shim_count - from torchsim.sequence._builders import fse_description - from torchsim.sequence._simulation import TissueProperties, _prepare_tissue - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _pack_events, _shim_count + from blochsim.sequence._builders import fse_description + from blochsim.sequence._simulation import TissueProperties, _prepare_tissue + from blochsim.sequence._transition import DynamicPairs definition = _pulse(samples=96) flips = torch.deg2rad(torch.linspace(100.0, 170.0, ECHOES)) @@ -363,7 +363,7 @@ def test_the_reference_reads_a_dynamic_pair_where_it_reads_a_table(): The pair is integrated at each pulse's own flip, so a row per pulse is what the table's flip axis stands in for. """ - from torchsim.sequence._transition import transition_table + from blochsim.sequence._transition import transition_table definition, prepared, events, pairs = _train() table = transition_table( @@ -389,7 +389,7 @@ def test_the_host_kernel_reads_the_pair_the_reference_does(): The two share no code: the reference turns a pulse in torch and the kernel reads four floats per voxel out of a packed buffer. """ - from torchsim.sequence._accelerators import _run_packed + from blochsim.sequence._accelerators import _run_packed _, prepared, events, pairs = _train() @@ -407,8 +407,8 @@ def test_the_host_kernel_still_reads_a_table_where_one_is_given(): """The mode is picked from which buffer the caller filled, so a sequence with a table has to be untouched by the pair's arrival. """ - from torchsim.sequence._accelerators import _run_packed - from torchsim.sequence._transition import SliceTables, transition_table + from blochsim.sequence._accelerators import _run_packed + from blochsim.sequence._transition import SliceTables, transition_table definition, prepared, events, _ = _train() table = SliceTables.alone( @@ -445,8 +445,8 @@ def test_the_host_forward_mode_follows_a_direction_along_the_pair(): stored in ``complex64``, so the difference stops improving below a step of about 1e-2; this one sits at that floor rather than under it. """ - from torchsim.sequence._accelerators import _run_packed, _run_packed_jvp - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed, _run_packed_jvp + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() generator = torch.Generator().manual_seed(13) @@ -495,7 +495,7 @@ def test_a_direction_along_nothing_moves_nothing(): """Seeding no direction at all has to leave the forward-mode result at zero, which is what catches a buffer read where none was given. """ - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp _, prepared, events, pairs = _train() still = tuple(torch.zeros_like(value) for value in prepared) @@ -529,8 +529,8 @@ def test_the_host_adjoint_returns_the_cotangent_on_the_pair(): entry has exactly one writer and the reverse pass accumulates nothing across threads. """ - from torchsim.sequence._accelerators import _run_packed, _run_packed_vjp - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed, _run_packed_vjp + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() generator = torch.Generator().manual_seed(21) @@ -580,7 +580,7 @@ def test_a_sequence_with_no_pair_still_gets_the_gradients_it_did(): """The pair's arrival adds a buffer to every entry point's tail, so a run that fills none of it has to come back exactly as it was. """ - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp _, prepared, events, _ = _train() generator = torch.Generator().manual_seed(23) @@ -602,7 +602,7 @@ def test_the_second_order_pass_returns_the_adjoint_given_no_direction(): what the first-order one does -- including on the pair, which the two reach by different code. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _run_packed_vjp, _run_packed_vjp_jvp, ) @@ -658,11 +658,11 @@ def test_the_second_order_pass_differentiates_the_pair_gradient(): """Given a direction along the pair, the curvature is what the first-order gradient's own derivative is. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _run_packed_vjp, _run_packed_vjp_jvp, ) - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() generator = torch.Generator().manual_seed(21) @@ -724,9 +724,9 @@ def test_the_cuda_forward_reads_the_pair_the_host_does(): read honest on the card: a row taken from the wrong train would still produce a plausible train. """ - from torchsim.sequence._accelerators import _run_packed - from torchsim.sequence._epg_triton import simulate - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed + from blochsim.sequence._epg_triton import simulate + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() host = _run_packed(prepared, events, 16, ECHOES, 1, dynamic=pairs) @@ -753,8 +753,8 @@ def test_the_cuda_reverse_agrees_at_every_width(state_count: int) -> None: the card: each one is a kernel of its own, and a rotation read per voxel rather than built from a flip angle is the largest of them. """ - from torchsim.sequence._accelerators import _run_packed_vjp - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() generator = torch.Generator().manual_seed(21) @@ -787,8 +787,8 @@ def test_the_cuda_reverse_agrees_at_every_width(state_count: int) -> None: @pytest.mark.parametrize("state_count", [15, 16, 17]) def test_the_cuda_second_order_agrees_at_every_width(state_count: int) -> None: """The curvature along a direction on the pair, against the host.""" - from torchsim.sequence._accelerators import _run_packed_vjp_jvp - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() generator = torch.Generator().manual_seed(21) @@ -841,8 +841,8 @@ def test_the_cuda_second_order_agrees_at_every_width(state_count: int) -> None: @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_the_cuda_forward_mode_follows_the_direction_the_host_does(): """Forward mode along a direction on the pair, against the host.""" - from torchsim.sequence._accelerators import _run_packed_jvp - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() generator = torch.Generator().manual_seed(29) @@ -898,10 +898,10 @@ def _real_subspace_train(trains: int = 1): pair argument, so a verdict of 1 does not slow this train down, it plays a different pulse. """ - from torchsim.sequence._accelerators import _pack_events - from torchsim.sequence._builders import fse_description - from torchsim.sequence._simulation import TissueProperties, _prepare_tissue - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _pack_events + from blochsim.sequence._builders import fse_description + from blochsim.sequence._simulation import TissueProperties, _prepare_tissue + from blochsim.sequence._transition import DynamicPairs definition = _pulse(samples=96) flips = torch.deg2rad(torch.linspace(100.0, 170.0, ECHOES)) @@ -965,7 +965,7 @@ def test_the_subspace_verdict_refuses_a_per_voxel_pair(): """The predicate itself, held to the reason: this train is inside the real subspace, and a pair still has to rule the reduced kernels out. """ - from torchsim.sequence._accelerators import real_subspace_axis + from blochsim.sequence._accelerators import real_subspace_axis prepared, events, pairs, _ = _real_subspace_train() assert real_subspace_axis(events, prepared) == 1 @@ -979,7 +979,7 @@ def test_a_pair_survives_a_train_the_real_kernel_would_have_taken(monkeypatch): test that hopes to clear it by carrying enough voxels is testing the threshold. Forcing it to zero asks the question directly. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators monkeypatch.setattr(_accelerators, "detection", lambda kind, device: 0.0) prepared, events, pairs, output_count = _real_subspace_train() @@ -996,14 +996,14 @@ def test_the_lane_forward_leaves_a_pair_to_the_scalar_kernel(monkeypatch): """The lane kernel carries no rotation to read a pair into, so a run that would otherwise vectorize has to fall back rather than play a hard pulse. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators - monkeypatch.setenv("TORCHSIM_LANES", "1") + monkeypatch.setenv("BLOCHSIM_LANES", "1") prepared, events, pairs, output_count = _real_subspace_train(trains=4) lanes = _accelerators._run_packed( prepared, events, 16, output_count, 1, dynamic=pairs ) - monkeypatch.delenv("TORCHSIM_LANES") + monkeypatch.delenv("BLOCHSIM_LANES") scalar = _accelerators._run_packed( prepared, events, 16, output_count, 1, dynamic=pairs ) @@ -1175,8 +1175,8 @@ def test_a_pulse_reaches_its_rotation_one_way_or_the_other(): """Handed both, the kernels read the pair and the table says nothing, so a caller who built one is owed the news rather than a silent choice. """ - from torchsim.sequence._accelerators import _run_packed - from torchsim.sequence._transition import transition_table + from blochsim.sequence._accelerators import _run_packed + from blochsim.sequence._transition import transition_table definition, prepared, events, pairs = _train() table = transition_table( @@ -1202,7 +1202,7 @@ def _split_across_two_channels(flips: torch.Tensor): """ from dataclasses import replace - from torchsim.sequence._builders import fse_description + from blochsim.sequence._builders import fse_description base = fse_description( flips, @@ -1227,7 +1227,7 @@ def test_a_split_pulse_records_the_train_its_sum_records(): the per-voxel rotation, and the answer is the one the flip-and-phase route gives for the field the channels sum to. """ - from torchsim.sequence import EpgEngine, TissueProperties + from blochsim.sequence import EpgEngine, TissueProperties flips = torch.deg2rad(torch.linspace(100.0, 170.0, ECHOES)) split, plain = _split_across_two_channels(flips) @@ -1272,7 +1272,7 @@ def test_a_split_pulse_leaves_the_transmit_phase_to_the_rotation(): is already in it. Left on the tissue as well it would be turned by twice, and the run would not answer the summed field. """ - from torchsim.sequence._simulation import _dynamic_transmit + from blochsim.sequence._simulation import _dynamic_transmit split, _ = _split_across_two_channels(torch.deg2rad(torch.full((ECHOES,), 140.0))) magnitude, phase = _sensitivities() @@ -1291,7 +1291,7 @@ def test_a_split_pulse_leaves_the_transmit_phase_to_the_rotation(): def _tissue_properties(magnitude, phase): - from torchsim.sequence import TissueProperties + from blochsim.sequence import TissueProperties return TissueProperties( t1_ms=torch.linspace(700.0, 1300.0, VOXELS), @@ -1303,8 +1303,8 @@ def _tissue_properties(magnitude, phase): def test_a_single_channel_train_never_reaches_the_pair(): """Nothing a builder emits is to be diverted onto the integrated route.""" - from torchsim.sequence._builders import fse_description - from torchsim.sequence._simulation import _dynamic_transmit + from blochsim.sequence._builders import fse_description + from blochsim.sequence._simulation import _dynamic_transmit plain = fse_description(torch.deg2rad(torch.full((ECHOES,), 140.0)), 5e-3) magnitude, phase = _sensitivities() @@ -1319,8 +1319,8 @@ def test_a_single_channel_train_never_reaches_the_pair(): def test_a_static_shim_beside_a_dynamic_pulse_is_refused(): from dataclasses import replace - from torchsim.sequence import ShimDefinition - from torchsim.sequence._simulation import _dynamic_transmit + from blochsim.sequence import ShimDefinition + from blochsim.sequence._simulation import _dynamic_transmit split, _ = _split_across_two_channels(torch.deg2rad(torch.full((ECHOES,), 140.0))) shimmed = replace( @@ -1334,7 +1334,7 @@ def test_a_static_shim_beside_a_dynamic_pulse_is_refused(): def test_a_transmit_map_short_of_the_channels_is_refused(): - from torchsim.sequence._simulation import _dynamic_transmit + from blochsim.sequence._simulation import _dynamic_transmit split, _ = _split_across_two_channels(torch.deg2rad(torch.full((ECHOES,), 140.0))) magnitude, phase = _sensitivities() @@ -1347,7 +1347,7 @@ def test_the_dispatch_hands_the_kernels_a_pair_and_no_table(monkeypatch): """Agreement with the flip-and-phase route is only evidence about the pair if the pair is what ran. """ - from torchsim.sequence import EpgEngine, _accelerators + from blochsim.sequence import EpgEngine, _accelerators split, _ = _split_across_two_channels(torch.deg2rad(torch.full((ECHOES,), 140.0))) magnitude, phase = _sensitivities() @@ -1375,7 +1375,7 @@ def test_a_dynamic_pulse_is_integrated_across_the_slice(): """Asking for positions spreads each voxel over them and averages the recorded signal, so a selective pulse records less than an ideal one. """ - from torchsim.sequence import EpgEngine, exact_slice_profile + from blochsim.sequence import EpgEngine, exact_slice_profile flips = torch.deg2rad(torch.full((ECHOES,), 150.0)) split, _ = _split_across_two_channels(flips) @@ -1407,7 +1407,7 @@ def test_a_relaxation_gradient_reaches_through_the_pair(): around it, so a relaxation gradient is the one the flip-and-phase route returns for the same field. """ - from torchsim.sequence import EpgEngine, TissueProperties + from blochsim.sequence import EpgEngine, TissueProperties flips = torch.deg2rad(torch.linspace(100.0, 170.0, ECHOES)) split, plain = _split_across_two_channels(flips) @@ -1442,7 +1442,7 @@ def test_the_flip_gradient_comes_back_through_the_pulse_integral(): it has to be the gradient the flip-and-phase route gives for the field the channels sum to. """ - from torchsim.sequence import EpgEngine, TissueProperties + from blochsim.sequence import EpgEngine, TissueProperties magnitude, phase = _sensitivities() combined = torch.polar(magnitude, phase).mean(dim=0) @@ -1479,7 +1479,7 @@ def test_a_transmit_gradient_comes_back_through_the_pulse_integral(name: str): """Against central differences, because there is no second route to the per-channel maps: the pair is the only thing that reads them. """ - from torchsim.sequence import EpgEngine, TissueProperties + from blochsim.sequence import EpgEngine, TissueProperties magnitude, phase = _sensitivities() split, _ = _split_across_two_channels( @@ -1526,7 +1526,7 @@ def test_a_forward_direction_follows_the_pair(): brings a cotangent back from it, so a directional derivative through the per-channel maps is the one differencing the simulation gives. """ - from torchsim.sequence import EpgEngine, TissueProperties + from blochsim.sequence import EpgEngine, TissueProperties magnitude, phase = _sensitivities() split, _ = _split_across_two_channels( @@ -1568,7 +1568,7 @@ def test_a_hessian_vector_product_reaches_through_the_pair(): """The second derivative is the first one differenced, and the pair carries both halves: a direction along it going in, a curvature coming back. """ - from torchsim.sequence import EpgEngine, TissueProperties + from blochsim.sequence import EpgEngine, TissueProperties magnitude, phase = _sensitivities() nominal = torch.deg2rad(torch.linspace(100.0, 170.0, ECHOES)) @@ -1755,7 +1755,7 @@ def test_a_moving_gradient_has_to_last_the_pulse(): gradient that runs out before the RF does is a caller's mistake, not a shorter pulse. """ - from torchsim.sequence._transition import compose_spinor + from blochsim.sequence._transition import compose_spinor drive = [torch.tensor(0.1, dtype=torch.complex128) for _ in range(4)] turns = [torch.zeros(2, dtype=torch.float64) for _ in range(3)] @@ -1798,7 +1798,7 @@ class _Elsewhere: def _streamed(monkeypatch): - from torchsim import ( + from blochsim import ( _execution, ) @@ -1812,7 +1812,7 @@ def test_the_streamed_route_refuses_a_per_voxel_pair(monkeypatch, pass_name): would have to cut it the same way -- and is told to say so rather than quietly reach for a hard pulse. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _run_packed, _run_packed_jvp, _run_packed_vjp, @@ -1838,7 +1838,7 @@ def test_the_streamed_route_still_takes_a_train_with_no_pair(monkeypatch): """The guard has to be about the pair and not about the route, or a plain sequence would lose streaming with it. """ - from torchsim.sequence._accelerators import _carries_the_pair + from blochsim.sequence._accelerators import _carries_the_pair assert _carries_the_pair(None, "streamed") is None @@ -1848,8 +1848,8 @@ def test_the_sharded_route_refuses_a_per_voxel_pair(): """Shards cut the trains rather than the voxels, but each device gets its own launch and the pair would have to travel with it. """ - from torchsim.sequence._accelerators import _run_packed, distribute - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _run_packed, distribute + from blochsim.sequence._transition import DynamicPairs prepared, events, pairs, echoes = _real_subspace_train(trains=4) prepared = tuple(value.cuda() for value in prepared) @@ -1871,10 +1871,10 @@ def _pooled_train(**properties): so a pair beside a pool is an instantiation of its own -- and one nothing reached until now. """ - from torchsim.sequence._accelerators import _pack_events - from torchsim.sequence._builders import fse_description - from torchsim.sequence._simulation import TissueProperties, _prepare_tissue - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence._accelerators import _pack_events + from blochsim.sequence._builders import fse_description + from blochsim.sequence._simulation import TissueProperties, _prepare_tissue + from blochsim.sequence._transition import DynamicPairs definition = _pulse(samples=96) flips = torch.deg2rad(torch.linspace(100.0, 170.0, ECHOES)) @@ -1960,8 +1960,8 @@ def test_a_pair_reaches_every_pool_the_kernels_carry(pool: str): """One instantiation of the kernel template per (rotation, pools), against the oracle reading the same buffers. """ - from torchsim.sequence._accelerators import _run_packed - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence._accelerators import _run_packed + from blochsim.sequence._lineshape import lineshape_table prepared, events, pairs, echoes = _pooled_train(**POOLS[pool]) carried = dict( @@ -1991,8 +1991,8 @@ def test_a_pool_moves_the_answer_a_pair_gives(pool: str): """The agreement above is only worth having if the pool is doing something, so the same train without one must record a different signal. """ - from torchsim.sequence._accelerators import _run_packed - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence._accelerators import _run_packed + from blochsim.sequence._lineshape import lineshape_table prepared, events, pairs, echoes = _pooled_train(**POOLS[pool]) bare, _, _, _ = _pooled_train() @@ -2022,9 +2022,9 @@ def test_a_pair_takes_the_first_order_kernel_on_the_card(state_count): state count before, and the pair adds a per-event load and an atomic the others do not make. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp - from torchsim.sequence._transition import DynamicPairs + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._transition import DynamicPairs _, prepared, events, pairs = _train() prepared = tuple(value.cuda() for value in prepared) diff --git a/tests/sequence/test_engine_coverage.py b/tests/sequence/test_engine_coverage.py index a1ec4a70..0b4713e4 100644 --- a/tests/sequence/test_engine_coverage.py +++ b/tests/sequence/test_engine_coverage.py @@ -9,12 +9,12 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._description import RfUse +from blochsim.sequence._description import RfUse ECHOES = 8 STATES = 10 @@ -36,8 +36,8 @@ def _tissue(): def _reference(description, tissue, state_count: int = STATES): """The same train through the state machine written out in torch.""" - from torchsim.sequence._accelerators import _pack_events, geometry_of - from torchsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._accelerators import _pack_events, geometry_of + from blochsim.sequence._simulation import _prepare_tissue from utils.packed_reference import simulate_packed prepared, shape, device = _prepare_tissue(tissue, "cpu") @@ -90,8 +90,8 @@ def _handmade(): This is the shape an autogenerated description arrives in, and the point of carrying the action word is that it reaches the kernels anyway. """ - from torchsim.sequence import EventAction, ideal_rf_definition - from torchsim.sequence._description import ( + from blochsim.sequence import EventAction, ideal_rf_definition + from blochsim.sequence._description import ( AdcRole, SequenceDescription, SequenceEvent, @@ -139,7 +139,7 @@ def test_the_crushers_are_what_the_description_says(): """ from dataclasses import replace - from torchsim.sequence import EventAction + from blochsim.sequence import EventAction described = _handmade() bare = replace( @@ -165,7 +165,7 @@ def test_a_tissue_no_kernel_can_take_says_so(): """With one route there is no fallback, so an unreachable device raises rather than quietly running somewhere else. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators original = _accelerators._backend_available _accelerators._backend_available = lambda device: False diff --git a/tests/sequence/test_exact_profile.py b/tests/sequence/test_exact_profile.py index 1de92759..1bad85f0 100644 --- a/tests/sequence/test_exact_profile.py +++ b/tests/sequence/test_exact_profile.py @@ -15,16 +15,16 @@ import pytest import torch -from torchsim import ( +from blochsim import ( rf_definition, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, exact_slice_profile, fse_description, ) -from torchsim.sequence._description import RfDefinition -from torchsim.sequence._simulation import TissueProperties +from blochsim.sequence._description import RfDefinition +from blochsim.sequence._simulation import TissueProperties ECHOES = 6 STATES = 10 @@ -120,7 +120,7 @@ def _two_shapes(second: RfDefinition): """An FSE whose excitation and refocusing are shaped differently.""" from dataclasses import replace - from torchsim.sequence._description import RfUse + from blochsim.sequence._description import RfUse description = _describe(definition=_sinc(4.0e3)) events = [] @@ -215,7 +215,7 @@ def test_a_shaped_pulse_is_integrated_without_being_asked() -> None: """A definition carrying a waveform worth integrating is tabulated on its own, so the caller never names a mode -- only where to sample it. """ - from torchsim.sequence._accelerators import _wants_a_table + from blochsim.sequence._accelerators import _wants_a_table shaped = _describe(definition=_sinc(4.0e3)) hard = _describe() @@ -231,13 +231,13 @@ def test_a_builder_pulse_stays_the_hard_pulse_to_the_bit() -> None: reading it at a flip between knots would not, so it must not happen by itself. """ - from torchsim.sequence._accelerators import _run_packed + from blochsim.sequence._accelerators import _run_packed description = _describe() seen = {} signal = None - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators original = _accelerators._run_packed @@ -272,7 +272,7 @@ def test_the_table_lays_its_copies_out_voxel_major() -> None: """The mean at the end folds the last axis, so the copies have to be there and each voxel's have to be adjacent. """ - from torchsim.sequence._accelerators import _across_the_table + from blochsim.sequence._accelerators import _across_the_table tissue = tuple(torch.tensor([800.0, 1400.0], dtype=torch.float32) for _ in range(7)) spread = _across_the_table(tissue, 3) @@ -290,7 +290,7 @@ def test_the_positions_are_counted_by_the_memory_policy() -> None: """ from test_offload import _peak_over_baseline - from torchsim.sequence import offload + from blochsim.sequence import offload voxels, points = 40_000, 5 budget = 8 << 20 @@ -322,14 +322,14 @@ def test_a_table_takes_the_first_order_kernel_on_the_card(state_count) -> None: The flip gradient comes off the table's own slope here rather than off a differentiated rotation, which is the part a single width would not check. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import ( + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import ( _pack_events, _run_packed_vjp, geometry_of, ) - from torchsim.sequence._simulation import _prepare_tissue - from torchsim.sequence._transition import transition_table + from blochsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._transition import transition_table description = _describe(definition=_sinc(4.0e3)) packed = _pack_events( diff --git a/tests/sequence/test_exchange.py b/tests/sequence/test_exchange.py index c0aec106..7e25ce65 100644 --- a/tests/sequence/test_exchange.py +++ b/tests/sequence/test_exchange.py @@ -16,18 +16,18 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( NO_GEOMETRY, _pack_events, _run_packed, ) -from torchsim.sequence._parameters import TISSUE_NAMES -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._parameters import TISSUE_NAMES +from blochsim.sequence._simulation import _prepare_tissue from utils.packed_reference import simulate_packed ECHOES = 8 @@ -112,7 +112,7 @@ def _free_induction(times_s, **properties): ``times_s`` are measured from the excitation; the events carry the intervals between them, which is what the state machine steps through. """ - from torchsim.sequence._accelerators import _EXCITATION, _RECORD + from blochsim.sequence._accelerators import _EXCITATION, _RECORD intervals = [ after - before for before, after in zip((0.0, *times_s), times_s, strict=False) @@ -288,7 +288,7 @@ def _prepared(device="cpu", **properties): def _live_events(): - from torchsim.sequence._accelerators import _EXCITATION, _RECORD + from blochsim.sequence._accelerators import _EXCITATION, _RECORD times = READ_TIMES_S intervals = [ @@ -309,7 +309,7 @@ def _live_events(): def _live_readout(prepared, seed=None): """The free-induction reading, or its directional derivative.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp events = _live_events() if seed is None: @@ -457,7 +457,7 @@ def signal(value): def _live_adjoint(prepared, seed): """The gradients a cotangent on the free-induction reading leaves.""" - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp return _run_packed_vjp( prepared, @@ -755,8 +755,8 @@ def _instantaneous_table(device="cpu"): """A pulse with no gradient across it: one rotation, every position.""" import numpy as np - from torchsim.sequence._description import RfDefinition, RfShape - from torchsim.sequence._transition import transition_table + from blochsim.sequence._description import RfDefinition, RfShape + from blochsim.sequence._transition import transition_table flat = RfDefinition( id=0, @@ -774,7 +774,7 @@ def _instantaneous_table(device="cpu"): def _routes(leaves, events, profile=None): """The kernels and the oracle, fed from one place.""" - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg fused = _NativeEpg.apply( *leaves, @@ -831,7 +831,7 @@ def _inverted_events(): sweep saturates a bound pool and turns free water over, so the two must start at ``-(1 - f), -f`` rather than at ``-(1 - f), f``. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _EXCITATION, _INVERSION, _RECORD, @@ -856,7 +856,7 @@ def test_an_inversion_turns_both_pools_over(): """ leaves = _oracle_leaves() events = _inverted_events() - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg fused = _NativeEpg.apply( *leaves, *events, STATES, 1, 1, NO_GEOMETRY, None, None, None, None, True, None @@ -879,7 +879,7 @@ def test_the_inversion_efficiency_carries_a_gradient_from_both_pools(): """ leaves = _oracle_leaves() events = _inverted_events() - from torchsim.sequence._accelerators import _NativeEpg + from blochsim.sequence._accelerators import _NativeEpg seed = torch.full((1, 1), 1.0 + 1.0j, dtype=torch.complex64) fused = torch.autograd.grad( @@ -1029,7 +1029,7 @@ def test_the_cuda_forward_mode_matches_the_cpu_kernel(): transverse pool's derivative honest there: a shift dropped from the tangent alone still produces a plausible direction. """ - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp voxels = 6 spread = dict( @@ -1067,7 +1067,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_a_streamed_forward_mode_matches_the_whole_one(): """Streaming cuts the voxel axis, which the second pool's seeds follow.""" - from torchsim.sequence._accelerators import _run_packed_jvp, offload + from blochsim.sequence._accelerators import _run_packed_jvp, offload voxels = 3000 prepared = _prepared( @@ -1140,7 +1140,7 @@ def run(device): @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable") def test_a_streamed_volume_matches_the_whole_one(): """Streaming cuts the voxel axis, which the second pool's buffers follow.""" - from torchsim.sequence._accelerators import offload + from blochsim.sequence._accelerators import offload voxels = 3000 packed = _pack_events( @@ -1174,7 +1174,7 @@ def test_the_cuda_adjoint_matches_the_cpu_kernel(): """The two backends share no code, so agreement is what keeps the second transverse pool's cotangent honest there. """ - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp voxels = 6 spread = dict( @@ -1224,7 +1224,7 @@ def test_a_streamed_adjoint_matches_the_whole_one(): The forward-over-reverse pass is the one that streams, and it is also what a first-order adjoint on the card runs with no direction to follow. """ - from torchsim.sequence._accelerators import _run_packed_vjp_jvp, offload + from blochsim.sequence._accelerators import _run_packed_vjp_jvp, offload voxels = 3000 prepared = _prepared( @@ -1278,8 +1278,8 @@ def test_an_exchanging_pool_takes_the_first_order_kernel_on_the_card( forward-over-reverse pass on the same card: two arms of one wrong kernel agree with each other, and the backends share no code. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp voxels = 64 tissue = TissueProperties( @@ -1353,8 +1353,8 @@ def test_a_tabulated_rotation_beside_a_pool_takes_the_first_order_kernel() -> No contributes to the flip and the phase through the table's slopes rather than through the instant rotation's derivative. Held against the host. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp voxels = 64 tissue = TissueProperties( diff --git a/tests/sequence/test_execution.py b/tests/sequence/test_execution.py index ffcd67a2..a2d718a7 100644 --- a/tests/sequence/test_execution.py +++ b/tests/sequence/test_execution.py @@ -13,9 +13,9 @@ import torch from test_offload import _seeds, _volume # noqa: E402 -import torchsim._execution as _policy -from torchsim.sequence import execution -from torchsim.sequence._accelerators import ( +import blochsim._execution as _policy +from blochsim.sequence import execution +from blochsim.sequence._accelerators import ( _FLOAT_INPUTS, _bytes_per_voxel, _choose, @@ -24,8 +24,8 @@ _run_packed_vjp, _run_packed_vjp_jvp, ) -from torchsim.sequence._calibration import crossover -from torchsim.sequence._parameters import OUTSIDE_THE_SUBSPACE +from blochsim.sequence._calibration import crossover +from blochsim.sequence._parameters import OUTSIDE_THE_SUBSPACE STATES = 10 cuda_only = pytest.mark.skipif( diff --git a/tests/sequence/test_feature_gates.py b/tests/sequence/test_feature_gates.py index 2e167c8b..558550cf 100644 --- a/tests/sequence/test_feature_gates.py +++ b/tests/sequence/test_feature_gates.py @@ -14,12 +14,12 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._parameters import ( +from blochsim.sequence._parameters import ( TISSUE_NAMES, TISSUE_PARAMETERS, Geometry, @@ -28,12 +28,12 @@ ) # The kernel module imports Triton at its top, and Triton is a dependency of -# the CUDA build of PyTorch rather than of TorchSim. Everything below the guard +# the CUDA build of PyTorch rather than of BlochSim. Everything below the guard # reaches it, so the two imports stay here rather than moving up with the rest. pytest.importorskip("triton") -from torchsim.sequence import _epg_triton # noqa: E402 -from torchsim.sequence._epg_triton import _feature_flags # noqa: E402 +from blochsim.sequence import _epg_triton # noqa: E402 +from blochsim.sequence._epg_triton import _feature_flags # noqa: E402 ECHOES = 8 STATES = 8 @@ -294,8 +294,8 @@ def test_an_off_resonance_asked_for_its_gradient_gets_a_real_one(): def _gate_inputs(extra): - from torchsim.sequence._accelerators import _pack_events - from torchsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._accelerators import _pack_events + from blochsim.sequence._simulation import _prepare_tissue generator = torch.Generator().manual_seed(7) @@ -356,8 +356,8 @@ def test_the_forward_answer_does_not_depend_on_the_gate(extra, geometry): """The forward kernel, run once with the terms the tissue asks for and once with every term, on the same input. """ - from torchsim.sequence._accelerators import _run_packed - from torchsim.sequence._parameters import features_of + from blochsim.sequence._accelerators import _run_packed + from blochsim.sequence._parameters import features_of prepared, events, output_count = _gate_inputs(extra) live = features_of( @@ -393,8 +393,8 @@ def test_the_forward_direction_does_not_depend_on_the_gate(extra, geometry): product beside it to be rounded on its own, which moves the last bits: the value half of this same pair is exact. """ - from torchsim.sequence._accelerators import _run_packed_jvp - from torchsim.sequence._parameters import features_of + from blochsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._parameters import features_of prepared, events, output_count = _gate_inputs(extra) seeds = _seeds(prepared, events, {"t1_ms", "t2_ms", *extra}) @@ -446,7 +446,7 @@ def test_a_diffusion_coefficient_asked_for_its_gradient_gets_a_real_one(): def _laid_out(tissue: TissueProperties, features) -> set[str]: """Which tissue properties got a value per voxel.""" - from torchsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._simulation import _prepare_tissue prepared, _, _ = _prepare_tissue(tissue, torch.device("cpu"), 1, features) return { diff --git a/tests/sequence/test_features.py b/tests/sequence/test_features.py index 6e8fe901..ac21d9be 100644 --- a/tests/sequence/test_features.py +++ b/tests/sequence/test_features.py @@ -12,7 +12,7 @@ import pytest import torch -from torchsim.sequence._parameters import ( +from blochsim.sequence._parameters import ( TISSUE_NAMES, TISSUE_PARAMETERS, at_identity, @@ -20,7 +20,7 @@ wants_bound_pool, wants_exchange_pool, ) -from torchsim.sequence._simulation import TissueProperties +from blochsim.sequence._simulation import TissueProperties def _parameter(name: str): diff --git a/tests/sequence/test_first_order_adjoint.py b/tests/sequence/test_first_order_adjoint.py index 02f1eb4b..0e476192 100644 --- a/tests/sequence/test_first_order_adjoint.py +++ b/tests/sequence/test_first_order_adjoint.py @@ -12,17 +12,17 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( _accelerators, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( _pack_events, _run_packed_vjp, _train_count, ) -from torchsim.sequence._parameters import FLOAT_NAMES, Geometry -from torchsim.sequence._simulation import TissueProperties, _prepare_tissue +from blochsim.sequence._parameters import FLOAT_NAMES, Geometry +from blochsim.sequence._simulation import TissueProperties, _prepare_tissue ECHOES = 12 ECHO_SPACING_S = 5e-3 diff --git a/tests/sequence/test_fixed_point.py b/tests/sequence/test_fixed_point.py index aa0a777d..a4d7e439 100644 --- a/tests/sequence/test_fixed_point.py +++ b/tests/sequence/test_fixed_point.py @@ -19,18 +19,18 @@ import pytest import torch -from torchsim import ( +from blochsim import ( SequenceDescription, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _builders, ideal_rf_definition, operator, ) -from torchsim.sequence._fixed_point import carries_one_order -from torchsim.sequence._operators import compose +from blochsim.sequence._fixed_point import carries_one_order +from blochsim.sequence._operators import compose TR_S, TE_S, FLIP_DEG = 5e-3, 2.5e-3, 30.0 diff --git a/tests/sequence/test_flow_kernels.py b/tests/sequence/test_flow_kernels.py index ea163102..68567f8a 100644 --- a/tests/sequence/test_flow_kernels.py +++ b/tests/sequence/test_flow_kernels.py @@ -13,11 +13,11 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( Geometry, _pack_events, _run_packed, @@ -28,8 +28,8 @@ geometry_of, real_subspace_axis, ) -from torchsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES +from blochsim.sequence._simulation import _prepare_tissue from utils.epg import flow_op from utils.packed_reference import simulate_packed diff --git a/tests/sequence/test_gradient_operators.py b/tests/sequence/test_gradient_operators.py index 204303c8..54a436fa 100644 --- a/tests/sequence/test_gradient_operators.py +++ b/tests/sequence/test_gradient_operators.py @@ -13,7 +13,7 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( AdcRole, Delay, Dephase, diff --git a/tests/sequence/test_host_feature_mask.py b/tests/sequence/test_host_feature_mask.py index 2935b239..ddd85c37 100644 --- a/tests/sequence/test_host_feature_mask.py +++ b/tests/sequence/test_host_feature_mask.py @@ -3,7 +3,7 @@ The Triton backend takes a flag per term because each one compiles a kernel of its own; the host kernels take one integer and branch on it at run time. These pin the two ends together -- that the mask says what -:func:`torchsim.sequence._parameters.feature_flags` says, and that a host +:func:`blochsim.sequence._parameters.feature_flags` says, and that a host kernel told to drop a term gives the answer it gives when told to keep it. """ @@ -12,13 +12,13 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _accelerators, fse_description, ) -from torchsim.sequence._parameters import ( +from blochsim.sequence._parameters import ( FEATURE_BITS, Geometry, feature_flags, @@ -237,7 +237,7 @@ def test_an_inversion_the_tissue_never_declared_is_left_out(monkeypatch) -> None """The one term a refocused train cannot exercise: it drives no inversion, so the gate is held against a sequence that does. """ - from torchsim.sequence import EpgEngine, mprage_description + from blochsim.sequence import EpgEngine, mprage_description def signal(mask, efficiency): monkeypatch.setattr( diff --git a/tests/sequence/test_lineshape.py b/tests/sequence/test_lineshape.py index 275ccc4a..79c503fc 100644 --- a/tests/sequence/test_lineshape.py +++ b/tests/sequence/test_lineshape.py @@ -16,7 +16,7 @@ import pytest import torch -from torchsim.sequence._lineshape import BOUND_T2_S, lineshape_table +from blochsim.sequence._lineshape import BOUND_T2_S, lineshape_table def _integral(offset_hz, bound_t2_s: float = BOUND_T2_S, quadrature: int = 20000): diff --git a/tests/sequence/test_many_pools.py b/tests/sequence/test_many_pools.py index a1920865..eae49806 100644 --- a/tests/sequence/test_many_pools.py +++ b/tests/sequence/test_many_pools.py @@ -3,7 +3,7 @@ BART's Bloch-McConnell model carries the free water, up to four pools that exchange with it and not with each other, and a semisolid pool. Past one exchanging pool the kernels read a tissue as the operators its intervals apply, -tabulated per voxel and per interval length by ``torchsim.sequence._pools``, so +tabulated per voxel and per interval length by ``blochsim.sequence._pools``, so what is held here is the tables and the kernels that walk them: against the state machine written out in torch, against the package's own N-pool operators, against the closed-form kernels at one pool, and against the @@ -19,8 +19,8 @@ import torch from torch.autograd import forward_ad -from torchsim.sequence import EpgEngine, TissueProperties, _pools, fse_description -from torchsim.sequence._accelerators import ( +from blochsim.sequence import EpgEngine, TissueProperties, _pools, fse_description +from blochsim.sequence._accelerators import ( _EXCITATION, _INVERSION, _POST_SHIFT, @@ -31,9 +31,9 @@ NO_GEOMETRY, _NativeEpg, ) -from torchsim.sequence._lineshape import lineshape_table -from torchsim.sequence._parameters import POOL_NAMES, Geometry -from torchsim.sequence._transition import DynamicPairs +from blochsim.sequence._lineshape import lineshape_table +from blochsim.sequence._parameters import POOL_NAMES, Geometry +from blochsim.sequence._transition import DynamicPairs from utils.packed_reference import simulate_packed_pools STATES = 6 @@ -93,8 +93,8 @@ def _instantaneous_table(): """ import numpy as np - from torchsim.sequence._description import RfDefinition, RfShape - from torchsim.sequence._transition import transition_table + from blochsim.sequence._description import RfDefinition, RfShape + from blochsim.sequence._transition import transition_table flat = RfDefinition( id=0, diff --git a/tests/sequence/test_mrd_reader.py b/tests/sequence/test_mrd_reader.py index b06171ed..8349a1fc 100644 --- a/tests/sequence/test_mrd_reader.py +++ b/tests/sequence/test_mrd_reader.py @@ -19,10 +19,10 @@ import pytest import torch -from torchsim import Excitation, SPGRReadout -from torchsim.model import Simulator -from torchsim.sequence import EventType, RfUse, read_mrd_description -from torchsim.simulators import SPGRSimulator +from blochsim import Excitation, SPGRReadout +from blochsim.model import Simulator +from blochsim.sequence import EventType, RfUse, read_mrd_description +from blochsim.simulators import SPGRSimulator pp = pytest.importorskip("pypulseq", reason="building the sequence needs pypulseq") diff --git a/tests/sequence/test_offload.py b/tests/sequence/test_offload.py index 1c98c5d2..d40c0bee 100644 --- a/tests/sequence/test_offload.py +++ b/tests/sequence/test_offload.py @@ -15,10 +15,10 @@ import pytest import torch -import torchsim._execution as _policy -import torchsim.sequence._accelerators as accelerators -from torchsim.sequence import EpgEngine, offload -from torchsim.sequence._accelerators import ( +import blochsim._execution as _policy +import blochsim.sequence._accelerators as accelerators +from blochsim.sequence import EpgEngine, offload +from blochsim.sequence._accelerators import ( _FLOAT_INPUTS, _bytes_per_voxel, _chunk_voxels, @@ -27,9 +27,9 @@ _pack_events, _run_packed, ) -from torchsim.sequence._builders import fse_description -from torchsim.sequence._parameters import NO_GEOMETRY, OUTSIDE_THE_SUBSPACE, Geometry -from torchsim.sequence._simulation import TissueProperties, _prepare_tissue +from blochsim.sequence._builders import fse_description +from blochsim.sequence._parameters import NO_GEOMETRY, OUTSIDE_THE_SUBSPACE, Geometry +from blochsim.sequence._simulation import TissueProperties, _prepare_tissue pytestmark = pytest.mark.skipif( not torch.cuda.is_available(), reason="CUDA is unavailable" @@ -240,7 +240,7 @@ def _spgr_volume(voxels, trains=1, pulses=12): rounding noise around zero and comparing it says nothing. SPGR records at an echo time, so every gradient is live and worth checking. """ - from torchsim.sequence._builders import spgr_description + from blochsim.sequence._builders import spgr_description generator = torch.Generator().manual_seed(0) packed = [ @@ -303,7 +303,7 @@ def _seeds(events, prepared, index): @pytest.mark.parametrize("real_axis", [1, -1]) @pytest.mark.parametrize("trains", [1, 3]) def test_a_streamed_forward_mode_matches_the_cpu_run(budget, real_axis, trains): - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp events, prepared, outputs = _volume(4000, trains=trains) tissue_seed, event_seed = _seeds(events, prepared, 1) @@ -319,7 +319,7 @@ def test_a_streamed_forward_mode_matches_the_cpu_run(budget, real_axis, trains): def test_an_event_seed_reaches_every_chunk(): """Event seeds are shared by all voxels, so they are replicated not sliced.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp events, prepared, outputs = _volume(4000) tissue_seed = tuple(torch.zeros_like(value) for value in prepared) @@ -340,7 +340,7 @@ def test_an_event_seed_reaches_every_chunk(): def _adjoint( events, prepared, outputs, voxels, trains, real_axis, budget, geometry=NO_GEOMETRY ): - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp tissue_seed, event_seed = _seeds(events, prepared, 1) generator = torch.Generator().manual_seed(7) @@ -522,7 +522,7 @@ def test_a_streamed_shimmed_volume_matches_the_whole_one(budget, trains): @pytest.mark.parametrize("budget", [1 << 20, 512 << 20]) def test_a_streamed_shimmed_forward_mode_matches_the_whole_one(budget): - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp events, prepared, outputs = _shim_volume(4000) # Seed the transmit magnitude, so the tangent travels the shim rows too. @@ -605,7 +605,7 @@ def _first_order_adjoint( geometry=NO_GEOMETRY, ): """The route ``torch.autograd`` takes for a plain ``.backward()``.""" - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp generator = torch.Generator().manual_seed(7) shape = (trains, voxels, outputs) if trains > 1 else (voxels, outputs) @@ -678,7 +678,7 @@ def test_a_host_resident_first_order_adjoint_follows_the_execution_target(): """The forward moves to the card under this policy, so the backward has to move with it rather than stay behind. """ - from torchsim.sequence import execution + from blochsim.sequence import execution voxels = 20_000 events, prepared, outputs = _volume(voxels) @@ -727,7 +727,7 @@ def test_a_backward_through_the_public_api_streams(): ``torch.autograd`` reaches the adjoint by a route of its own, and it is the one an ordinary user's ``.backward()`` takes. """ - from torchsim.sequence import TissueProperties + from blochsim.sequence import TissueProperties voxels = 20_000 reached = [] @@ -785,7 +785,7 @@ def test_a_streamed_first_order_adjoint_takes_its_own_kernel(trains): """A chunk of a first-order adjoint is a first-order adjoint, so streaming must not cost the kernel written for it. """ - from torchsim.sequence import _accelerators as accelerators + from blochsim.sequence import _accelerators as accelerators voxels = 2000 events, prepared, outputs = _volume(voxels, trains=trains) @@ -813,7 +813,7 @@ def test_streaming_a_first_order_adjoint_makes_wider_chunks(): """Half the trajectory is the second saving: for one budget the chunks come out wider than the pass carrying a forward direction would allow. """ - from torchsim.sequence._accelerators import _bytes_per_voxel + from blochsim.sequence._accelerators import _bytes_per_voxel shape = (4, 40, 8, 16, None, 1) around = _bytes_per_voxel("adjoint", *shape) diff --git a/tests/sequence/test_operators.py b/tests/sequence/test_operators.py index 4e219d43..18bde3ad 100644 --- a/tests/sequence/test_operators.py +++ b/tests/sequence/test_operators.py @@ -13,16 +13,16 @@ import pytest import torch -from torchsim.sequence._accelerators import _pack_events -from torchsim.sequence._builders import ( +from blochsim.sequence._accelerators import _pack_events +from blochsim.sequence._builders import ( fse_description, mpnrage_description, mprage_description, mrf_description, spgr_description, ) -from torchsim.sequence._description import AdcRole, EventAction, EventType -from torchsim.sequence._operators import ( +from blochsim.sequence._description import AdcRole, EventAction, EventType +from blochsim.sequence._operators import ( Delay, Excitation, Readout, diff --git a/tests/sequence/test_parameters.py b/tests/sequence/test_parameters.py index 3d648b57..442d9969 100644 --- a/tests/sequence/test_parameters.py +++ b/tests/sequence/test_parameters.py @@ -11,14 +11,14 @@ import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _accelerators, fse_description, ) -from torchsim.sequence._accelerators import _pack_events -from torchsim.sequence._parameters import ( +from blochsim.sequence._accelerators import _pack_events +from blochsim.sequence._parameters import ( EVENT_PARAMETERS, FLOAT_INPUTS, PACKED_COUNT, @@ -32,7 +32,7 @@ tissue_gradient_height, tissue_gradient_rows, ) -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._simulation import _prepare_tissue def test_the_table_names_the_tissue_dataclass_fields(): diff --git a/tests/sequence/test_pulseq_reader.py b/tests/sequence/test_pulseq_reader.py index a26fb493..1ec75838 100644 --- a/tests/sequence/test_pulseq_reader.py +++ b/tests/sequence/test_pulseq_reader.py @@ -14,10 +14,10 @@ import pytest import torch -from torchsim import Excitation, SequenceDescription, SPGRReadout -from torchsim.model import Simulator -from torchsim.sequence import AdcRole, EventType, RfUse -from torchsim.simulators import SPGRSimulator +from blochsim import Excitation, SequenceDescription, SPGRReadout +from blochsim.model import Simulator +from blochsim.sequence import AdcRole, EventType, RfUse +from blochsim.simulators import SPGRSimulator pp = pytest.importorskip("pypulseq", reason="reading a .seq file needs pypulseq") diff --git a/tests/sequence/test_real_subspace.py b/tests/sequence/test_real_subspace.py index c9eaa622..2dd67699 100644 --- a/tests/sequence/test_real_subspace.py +++ b/tests/sequence/test_real_subspace.py @@ -10,19 +10,19 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, fse_description, ) -from torchsim.sequence import _accelerators as accelerators -from torchsim.sequence._accelerators import ( +from blochsim.sequence import _accelerators as accelerators +from blochsim.sequence._accelerators import ( _one_axis, _pack_events, _run_packed, real_subspace_axis, ) -from torchsim.sequence._parameters import FLOAT_NAMES, OUTSIDE_THE_SUBSPACE -from torchsim.sequence._simulation import TissueProperties, _prepare_tissue +from blochsim.sequence._parameters import FLOAT_NAMES, OUTSIDE_THE_SUBSPACE +from blochsim.sequence._simulation import TissueProperties, _prepare_tissue # The gradients a real adjoint does produce: every differentiable input the # subspace contains. Derived rather than listed, so a new parameter cannot @@ -120,7 +120,7 @@ def _buffers(packed): @pytest.mark.parametrize("phase", [0.0, torch.pi / 4, torch.pi / 2]) def test_real_kernel_reproduces_the_complex_one(phase): """The real kernel is an optimization, not an approximation.""" - from torchsim.sequence._accelerators import _run_packed + from blochsim.sequence._accelerators import _run_packed description = fse_description( _flip(), @@ -147,7 +147,7 @@ def test_real_kernel_reproduces_the_complex_one(phase): def test_real_jvp_reproduces_the_complex_one(): """Forward mode along T2 stays inside the subspace, so the kernels agree.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp description = fse_description( _flip(), @@ -192,7 +192,7 @@ def test_real_adjoint_reproduces_the_complex_one(phase, always_worth_detecting): -- so the verdict is reached through ``wanted`` rather than handed over, which is the route the public adjoint takes. """ - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp description = fse_description( _flip(), @@ -243,7 +243,7 @@ def test_the_real_adjoint_reaches_the_inversion_gradient(): the representation describes a sequence the builders produce, and a train assembled from scratch is not one. """ - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _INVERSION, _run_packed_vjp, ) @@ -310,7 +310,7 @@ def ahead(values, first): def test_real_second_order_kernel_reproduces_the_complex_one(): """Forward-over-reverse agrees on every gradient the subspace contains.""" - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp description = fse_description( _flip(), @@ -385,7 +385,7 @@ def _packed(trains): # the block's first train. These counts straddle that boundary. @pytest.mark.parametrize("trains", [1, 7, 8, 9, 17]) def test_partial_train_blocks_match_the_complex_kernel(trains): - from torchsim.sequence._accelerators import _run_packed_jvp, _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_jvp, _run_packed_vjp_jvp packed = _packed(trains) events = _buffers(packed) @@ -456,7 +456,7 @@ def _tissue_events(trains, echoes=20, atoms=64, b0_hz=0.0): def _trains_worth(share, echoes=20, atoms=64): """Enough trains to carry ``share`` of the work the test is worth at.""" - from torchsim.sequence._calibration import detection + from blochsim.sequence._calibration import detection events, _tissue, _count = _tissue_events(1, echoes=echoes, atoms=atoms) per_train = atoms * int(events[1].numel()) @@ -466,7 +466,7 @@ def _trains_worth(share, echoes=20, atoms=64): def test_the_fast_path_is_chosen_without_being_asked(): """A caller should not have to know the subspace rule to benefit from it.""" - from torchsim.sequence._accelerators import _auto_real_axis, _run_packed + from blochsim.sequence._accelerators import _auto_real_axis, _run_packed events, tissue, count = _tissue_events(_trains_worth(8)) assert _auto_real_axis("forward", events, tissue) == 1 @@ -477,7 +477,7 @@ def test_the_fast_path_is_chosen_without_being_asked(): def test_off_resonance_is_not_chosen(): - from torchsim.sequence._accelerators import _auto_real_axis + from blochsim.sequence._accelerators import _auto_real_axis events, tissue, _ = _tissue_events(_trains_worth(8), b0_hz=15.0) assert _auto_real_axis("forward", events, tissue) is None @@ -492,8 +492,8 @@ def test_a_tiny_problem_skips_the_test_that_would_cost_more_than_it_saves(monkey machine could land either side of it. The rule is the same whatever the number: work under the threshold is not worth the test. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _auto_real_axis + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _auto_real_axis events, tissue, _ = _tissue_events(1, atoms=2) work = 2 * int(events[1].numel()) @@ -511,7 +511,7 @@ def test_a_seed_that_leaves_the_subspace_is_not_chosen(direction): verdict has to look at the seed as well; otherwise the fast path would silently return zero for exactly the direction that was asked for. """ - from torchsim.sequence._accelerators import _auto_real_axis + from blochsim.sequence._accelerators import _auto_real_axis events, tissue, _ = _tissue_events(_trains_worth(8)) seed = tuple( @@ -525,7 +525,7 @@ def test_a_seed_that_leaves_the_subspace_is_not_chosen(direction): def test_an_rf_phase_seed_is_not_chosen(): - from torchsim.sequence._accelerators import _auto_real_axis + from blochsim.sequence._accelerators import _auto_real_axis events, tissue, _ = _tissue_events(_trains_worth(8)) zeros = tuple(torch.zeros_like(value) for value in tissue) @@ -584,7 +584,7 @@ def _only(*names): ) def test_the_adjoint_verdict_follows_what_the_caller_will_read(wanted, expected): """A real adjoint is three gradients short, so it depends who is asking.""" - from torchsim.sequence._accelerators import _auto_real_axis_adjoint + from blochsim.sequence._accelerators import _auto_real_axis_adjoint events, tissue, tangents, _cotangent, _count = _adjoint_case(_trains_worth(8)) @@ -596,11 +596,11 @@ def test_the_adjoint_verdict_follows_what_the_caller_will_read(wanted, expected) ) def test_wanting_a_gradient_outside_the_subspace_keeps_the_complex_kernel(position): """Each is genuinely non-zero; returning zero would be wrong.""" - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _auto_real_axis_adjoint, _run_packed_vjp_jvp, ) - from torchsim.sequence._parameters import Geometry + from blochsim.sequence._parameters import Geometry events, tissue, tangents, cotangent, count = _adjoint_case(_trains_worth(8)) wanted = _only(_FLIP, position) @@ -626,7 +626,7 @@ def test_wanting_a_gradient_outside_the_subspace_keeps_the_complex_kernel(positi def test_an_adjoint_that_stays_in_the_subspace_reaches_the_real_kernel(): """Bitwise, because only the same kernel gives the same bits.""" - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp events, tissue, tangents, cotangent, count = _adjoint_case(_trains_worth(8)) shared = (tissue, events, tangents, cotangent, 10, count, 1) @@ -639,10 +639,10 @@ def test_an_adjoint_that_stays_in_the_subspace_reaches_the_real_kernel(): def _forward_over_reverse(atoms): """A directional derivative, differentiated: what backward fuses.""" - from torchsim.sequence import ( + from blochsim.sequence import ( EpgEngine, ) - from torchsim.sequence._simulation import TissueProperties + from blochsim.sequence._simulation import TissueProperties generator = torch.Generator().manual_seed(0) flip = torch.deg2rad(80.0 + 80.0 * torch.rand(20, generator=generator)) @@ -668,7 +668,7 @@ def test_autograd_asks_for_what_the_graph_needs(monkeypatch, always_worth_detect Differentiating T2 stays inside the subspace, so the fast adjoint is available -- and the answer must not depend on it being taken. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators chosen = [] original = _accelerators._auto_real_axis_adjoint @@ -706,8 +706,8 @@ def test_the_cuda_real_adjoint_agrees_with_the_second_order_kernel( Widths are swept because a reverse kernel has miscompiled silently at one state count before, and a single width would not have caught it. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp description = fse_description( _flip(), @@ -786,8 +786,8 @@ def test_the_device_adjoint_stops_short_of_the_forward_over_reverse_pass( """The first-order kernel is the point of this route, so the test above has to be reaching it rather than agreeing with itself. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp description = fse_description( _flip(), @@ -839,7 +839,7 @@ def test_the_device_adjoint_stops_short_of_the_forward_over_reverse_pass( def _spoiled_axis(phases_rad): """The verdict and the signal for an unbalanced train of the given phases.""" - from torchsim.sequence import ( + from blochsim.sequence import ( mrf_description, ) @@ -887,7 +887,7 @@ def test_rf_spoiling_breaks_the_subspace(): def test_the_real_kernel_reproduces_the_complex_one_without_refocusing(): """The widened verdict is an optimization, not an approximation.""" - from torchsim.sequence._accelerators import _run_packed + from blochsim.sequence._accelerators import _run_packed axis, _, packed, prepared = _spoiled_axis(0.0) assert axis == 1 @@ -905,7 +905,7 @@ def test_a_declared_tissue_settles_the_question_without_a_buffer(): is the whole answer: the verdict is the same one the reduction gives, and it is reached without reading the buffer that reduction would read. """ - from torchsim.sequence._parameters import features_of + from blochsim.sequence._parameters import features_of description = fse_description( _flip(), echo_spacing_s=ECHO_SPACING_S, phases_rad=0.0, excitation_phase_rad=0.0 @@ -942,7 +942,7 @@ def test_a_map_of_zeros_keeps_the_axis_it_declares_it_has_left(): whatever the map holds, and a map of zeros is one a run should not lose the fast path to. """ - from torchsim.sequence._parameters import features_of + from blochsim.sequence._parameters import features_of description = fse_description( _flip(), echo_spacing_s=ECHO_SPACING_S, phases_rad=0.0, excitation_phase_rad=0.0 @@ -1000,7 +1000,7 @@ def test_a_rewritten_phase_buffer_is_read_again(): # block writes and every per-event gradient it sums has to ignore them. @pytest.mark.parametrize("atoms", [7, 8, 9, 17]) def test_partial_atom_blocks_of_the_adjoint_match_the_complex_kernel(atoms): - from torchsim.sequence._accelerators import ( + from blochsim.sequence._accelerators import ( _auto_real_axis_adjoint, _run_packed_vjp, ) diff --git a/tests/sequence/test_rf_definition.py b/tests/sequence/test_rf_definition.py index 2c678faa..e5c4550f 100644 --- a/tests/sequence/test_rf_definition.py +++ b/tests/sequence/test_rf_definition.py @@ -17,16 +17,16 @@ import pytest import torch -from torchsim import ( +from blochsim import ( rf_definition, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, RfMode, exact_slice_profile, fse_description, ) -from torchsim.sequence._simulation import TissueProperties +from blochsim.sequence._simulation import TissueProperties ECHOES = 6 STATES = 10 diff --git a/tests/sequence/test_settling.py b/tests/sequence/test_settling.py index 768aa234..b4d9bd8f 100644 --- a/tests/sequence/test_settling.py +++ b/tests/sequence/test_settling.py @@ -16,18 +16,18 @@ import pytest import torch -from torchsim import ( +from blochsim import ( SequenceDescription, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _builders, ideal_rf_definition, operator, ) -from torchsim.sequence._operators import Excitation, compose -from torchsim.sequence._settling import settled +from blochsim.sequence._operators import Excitation, compose +from blochsim.sequence._settling import settled STATES = 32 diff --git a/tests/sequence/test_simulation.py b/tests/sequence/test_simulation.py index 12748177..7da8d169 100644 --- a/tests/sequence/test_simulation.py +++ b/tests/sequence/test_simulation.py @@ -7,7 +7,7 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, @@ -91,7 +91,7 @@ def test_subspace_basis_uses_time_as_leading_dimension() -> None: @pytest.mark.skipif( - importlib.util.find_spec("torchsim._epg_cpu") is None, + importlib.util.find_spec("blochsim._epg_cpu") is None, reason="CPU extension has not been built", ) @pytest.mark.parametrize("policy_name", ["fse", "ssfp-fid", "spgr"]) @@ -233,8 +233,8 @@ def _oracle(description, tissue, device: str, state_count: int = 10): route -- description, preparation, packing, kernel -- against an implementation that shares none of it. """ - from torchsim.sequence._accelerators import _pack_events, geometry_of - from torchsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._accelerators import _pack_events, geometry_of + from blochsim.sequence._simulation import _prepare_tissue from utils.packed_reference import simulate_packed prepared, shape, resolved = _prepare_tissue(tissue, device) diff --git a/tests/sequence/test_slice_table.py b/tests/sequence/test_slice_table.py index 55bcffb4..668d7539 100644 --- a/tests/sequence/test_slice_table.py +++ b/tests/sequence/test_slice_table.py @@ -18,20 +18,20 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( _pack_events, _run_packed, offload, real_subspace_axis, ) -from torchsim.sequence._description import RfDefinition, RfShape -from torchsim.sequence._parameters import FLOAT_NAMES, TISSUE_COUNT -from torchsim.sequence._simulation import _prepare_tissue -from torchsim.sequence._transition import transition_table +from blochsim.sequence._description import RfDefinition, RfShape +from blochsim.sequence._parameters import FLOAT_NAMES, TISSUE_COUNT +from blochsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._transition import transition_table from utils.packed_reference import simulate_packed ECHOES = 4 @@ -272,7 +272,7 @@ def test_a_chunk_holds_whole_voxels_when_a_table_is_read() -> None: So a chunk boundary inside a voxel's slice copies shifts every row after it, which returns a plausible signal for an entirely different slice. """ - from torchsim.sequence._accelerators import _Offload, _offload_plan + from blochsim.sequence._accelerators import _Offload, _offload_plan _, events, outputs = _packed(96) plan = _Offload(devices=(torch.device("cuda"),), budget_bytes=1 << 12, lanes=1) @@ -292,7 +292,7 @@ def test_a_chunk_holds_whole_voxels_when_a_table_is_read() -> None: @pytest.mark.skipif(not torch.cuda.is_available(), reason="needs CUDA") def test_a_streamed_adjoint_reads_the_table_a_whole_one_does() -> None: - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp locations, voxels = 3, 24 table = transition_table( @@ -328,7 +328,7 @@ def test_a_streamed_adjoint_reads_the_table_a_whole_one_does() -> None: def test_forward_mode_follows_the_table() -> None: """The tangent rides the slope the table already stores.""" - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp locations, voxels = 3, 3 table = transition_table( @@ -393,7 +393,7 @@ def _compare(expected, actual, names, tolerance=2e-4) -> None: def test_the_adjoint_follows_the_table() -> None: """Every gradient the table stands in front of, against the oracle.""" - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp locations, voxels = 3, 3 table = transition_table( @@ -439,7 +439,7 @@ def test_the_adjoint_follows_the_table() -> None: def test_an_unprofiled_adjoint_is_untouched() -> None: """The table path is another kernel, not a branch inside this one.""" - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp tissue, events, outputs = _packed(6) generator = torch.Generator().manual_seed(5) @@ -456,7 +456,7 @@ def test_an_unprofiled_adjoint_is_untouched() -> None: def test_the_second_order_pass_follows_the_table() -> None: """Forward-over-reverse, which needs the Hermite's own curvature.""" - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp locations, voxels = 3, 2 table = transition_table( @@ -512,7 +512,7 @@ def test_the_second_order_pass_follows_the_table() -> None: def test_an_unprofiled_second_order_pass_is_untouched() -> None: - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp tissue, events, outputs = _packed(6) generator = torch.Generator().manual_seed(7) @@ -536,7 +536,7 @@ def test_an_unprofiled_second_order_pass_is_untouched() -> None: @pytest.mark.skipif(not torch.cuda.is_available(), reason="needs CUDA") def test_the_card_follows_the_table_through_both_adjoint_passes() -> None: """One kernel serves both: the card reaches a first adjoint through it.""" - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp locations, voxels = 3, 2 table = transition_table( @@ -582,7 +582,7 @@ def test_the_card_follows_the_table_through_both_adjoint_passes() -> None: @pytest.mark.skipif(not torch.cuda.is_available(), reason="needs CUDA") def test_the_card_follows_the_table_in_forward_mode() -> None: - from torchsim.sequence._accelerators import _run_packed_jvp + from blochsim.sequence._accelerators import _run_packed_jvp locations, voxels = 3, 3 table = transition_table( diff --git a/tests/sequence/test_ssfp_readouts.py b/tests/sequence/test_ssfp_readouts.py index 9e170067..c303a5cf 100644 --- a/tests/sequence/test_ssfp_readouts.py +++ b/tests/sequence/test_ssfp_readouts.py @@ -5,7 +5,7 @@ would refocus, which is the same state read after the gradient rather than before it. :func:`SSFPFidReadout` takes the first and :func:`SSFPEchoReadout` the second, and this pins both against an extended phase graph written out here -from the operators in the literature rather than from anything TorchSim does. +from the operators in the literature rather than from anything BlochSim does. The reference is held honest by being asked for both: it has to reproduce the FID sample, which is what the rest of the suite and the published comparisons @@ -17,17 +17,17 @@ import numpy as np import pytest -from torchsim import ( +from blochsim import ( SequenceDescription, SSFPEchoReadout, SSFPFidReadout, ) -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, ideal_rf_definition, ) -from torchsim.sequence._operators import Excitation, compose +from blochsim.sequence._operators import Excitation, compose # More orders than the train can reach, so neither side truncates and the # comparison is of the physics rather than of two truncation policies. @@ -128,7 +128,7 @@ def test_the_first_repetition_has_no_echo_to_read(): def test_it_is_reachable_by_name(): - from torchsim.sequence import ( + from blochsim.sequence import ( operator, operator_names, ) diff --git a/tests/sequence/test_static_spread.py b/tests/sequence/test_static_spread.py index c9c22b97..3ecc2ed5 100644 --- a/tests/sequence/test_static_spread.py +++ b/tests/sequence/test_static_spread.py @@ -6,7 +6,7 @@ gone unrefocused -- growing either side of a spin echo and never recovering after a gradient echo. -These hold that against things outside TorchSim: the characteristic function of +These hold that against things outside BlochSim: the characteristic function of the Cauchy distribution, summed here over a quadrature rather than taken on trust, and the closed form ``T2*`` a gradient echo decays at. The ensemble is also played through the states themselves, so the claim that a spread and a @@ -19,18 +19,18 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, EventAction, TissueProperties, _builders, ) -from torchsim.sequence._description import ( +from blochsim.sequence._description import ( AdcRole, SequenceDescription, ideal_rf_definition, ) -from torchsim.sequence._operators import ( +from blochsim.sequence._operators import ( Dephase, Excitation, Readout, @@ -152,7 +152,7 @@ def _signal(description, *, repetitions=1, **tissue) -> np.ndarray: def _unrefocused_s(description) -> np.ndarray: """How long each sample has gone unrefocused, from the run's own labels.""" - from torchsim.sequence._accelerators import _pack_events + from blochsim.sequence._accelerators import _pack_events packed = _pack_events( description, @@ -340,8 +340,8 @@ def test_a_settled_state_carries_the_spread(route): def test_a_model_declares_it_like_any_other_property(): - from torchsim.model._state_machine import SpinPhysics - from torchsim.sequence._parameters import features_of + from blochsim.model._state_machine import SpinPhysics + from blochsim.sequence._parameters import features_of physics = SpinPhysics( properties={"T1": "t1_ms", "T2": "t2_ms", "T2p": "t2_prime_ms"} diff --git a/tests/sequence/test_steady_state.py b/tests/sequence/test_steady_state.py index 6e745a05..ab497df5 100644 --- a/tests/sequence/test_steady_state.py +++ b/tests/sequence/test_steady_state.py @@ -7,7 +7,7 @@ dictionary the dictionary the scanner acquires. The settled answer is held against a closed form written out here, so what is -being checked is the physics rather than TorchSim's agreement with itself. +being checked is the physics rather than BlochSim's agreement with itself. """ from __future__ import annotations @@ -18,7 +18,7 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _builders, @@ -131,7 +131,7 @@ def test_settling_is_refused_where_it_makes_no_sense(): def test_a_sequence_may_declare_how_far_it_has_to_settle(): """A simulator knows its own physics; a caller may still overrule it.""" - from torchsim.simulators import MRFSimulator + from blochsim.simulators import MRFSimulator flip = torch.linspace(5.0, 60.0, 20) assert MRFSimulator(flip=flip, TR=10.0, states=16).repetitions == 1 diff --git a/tests/sequence/test_subspace_streams.py b/tests/sequence/test_subspace_streams.py index c6597ef9..2046c9e8 100644 --- a/tests/sequence/test_subspace_streams.py +++ b/tests/sequence/test_subspace_streams.py @@ -17,8 +17,8 @@ import pytest import torch -from torchsim.sequence._accelerators import _run_packed, real_subspace_axis -from torchsim.sequence._simulation import TissueProperties, _prepare_tissue +from blochsim.sequence._accelerators import _run_packed, real_subspace_axis +from blochsim.sequence._simulation import TissueProperties, _prepare_tissue VOXELS = 32 STATES = 8 @@ -111,7 +111,7 @@ def _pair(backend: str, tissue, events, recorded): if backend == "host": shape = (tissue, events, STATES, recorded) return _run_packed(*shape, 1), _run_packed(*shape, 1, real_axis=1) - from torchsim.sequence import _epg_triton + from blochsim.sequence import _epg_triton shape = dict(state_count=STATES, output_count=recorded) return ( diff --git a/tests/sequence/test_train_batching.py b/tests/sequence/test_train_batching.py index 05c864df..a5238814 100644 --- a/tests/sequence/test_train_batching.py +++ b/tests/sequence/test_train_batching.py @@ -8,14 +8,14 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import _pack_events, _run_packed, _run_packed_vjp -from torchsim.sequence._parameters import TISSUE_COUNT -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._accelerators import _pack_events, _run_packed, _run_packed_vjp +from blochsim.sequence._parameters import TISSUE_COUNT +from blochsim.sequence._simulation import _prepare_tissue # Where duration, flip and phase land in a gradient tuple. _EVENT_GRADIENTS = (TISSUE_COUNT, TISSUE_COUNT + 1, TISSUE_COUNT + 2) @@ -215,7 +215,7 @@ def test_mismatched_train_widths_are_rejected(always_worth_detecting): def _second_order(threads): - from torchsim.sequence._accelerators import _run_packed_vjp_jvp + from blochsim.sequence._accelerators import _run_packed_vjp_jvp flip = _schedules(TRAINS, 12) tissue = TissueProperties( @@ -330,7 +330,7 @@ def test_placing_a_definition_at_once_writes_what_placing_it_per_pulse_writes(tr out rather than after, so that one entry rounds a step earlier. Every other entry is bit for bit. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators flip = _schedules(trains, 6) scattered = _pack(flip) @@ -355,7 +355,7 @@ def test_a_schedule_of_tensors_is_placed_bit_for_bit(): gathering changes no dtype and the two ways of filling the buffers do the same arithmetic on the same values. """ - from torchsim.sequence import _accelerators, _builders + from blochsim.sequence import _accelerators, _builders echoes = 12 description = _builders.mrf_description( @@ -386,7 +386,7 @@ def test_a_definition_is_asked_what_it_turns_through_once_per_packing(): nested forward-mode interpreters, so a call per pulse is the cost that makes a long train expensive to resolve. """ - from torchsim.sequence._description import RfDefinition + from blochsim.sequence._description import RfDefinition flip = _schedules(1, 64) calls = 0 diff --git a/tests/sequence/test_transition.py b/tests/sequence/test_transition.py index f7af1c72..6e0928a1 100644 --- a/tests/sequence/test_transition.py +++ b/tests/sequence/test_transition.py @@ -19,9 +19,9 @@ import pytest import torch -from torchsim import compose_spinor -from torchsim.sequence._description import RfDefinition, RfShape -from torchsim.sequence._transition import transition_table +from blochsim import compose_spinor +from blochsim.sequence._description import RfDefinition, RfShape +from blochsim.sequence._transition import transition_table SAMPLES = 256 RASTER = 1e-5 @@ -368,7 +368,7 @@ def test_a_dynamic_pair_reads_the_same_sample_times() -> None: """The per-voxel integrator and the table are the same integration, so a declared duration has to reach both. """ - from torchsim.sequence._transition import dynamic_pair + from blochsim.sequence._transition import dynamic_pair stretch = 2.0 coarse = stretch * np.arange(SAMPLES, dtype=np.float64) diff --git a/tests/sequence/test_transmit_array.py b/tests/sequence/test_transmit_array.py index af12b882..36ffe62b 100644 --- a/tests/sequence/test_transmit_array.py +++ b/tests/sequence/test_transmit_array.py @@ -19,12 +19,12 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( _across_the_table, _pack_events, _run_packed, @@ -33,10 +33,10 @@ _run_packed_vjp_jvp, offload, ) -from torchsim.sequence._description import EventType, ShimDefinition -from torchsim.sequence._parameters import TISSUE_COUNT -from torchsim.sequence._simulation import _prepare_tissue, _resolve_transmit -from torchsim.sequence._transmit import ( +from blochsim.sequence._description import EventType, ShimDefinition +from blochsim.sequence._parameters import TISSUE_COUNT +from blochsim.sequence._simulation import _prepare_tissue, _resolve_transmit +from blochsim.sequence._transmit import ( channel_count, shim_rows, transmit_field, @@ -855,7 +855,7 @@ def _pooled_shim_packed(voxels: int = 3, **properties): @pytest.mark.parametrize("pool", sorted(POOLS)) def test_the_shim_rows_reach_every_pool_the_kernels_carry(pool: str) -> None: """Against the oracle, which indexes the rows and the pools independently.""" - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence._lineshape import lineshape_table tissue, events, output_count = _pooled_shim_packed(**POOLS[pool]) carried = dict( @@ -884,7 +884,7 @@ def test_a_pooled_pulse_still_reads_only_the_shim_it_names(pool: str) -> None: """A row no pulse drives is dead weight whatever pools are carried, so filling it must leave the signal alone to the bit. """ - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence._lineshape import lineshape_table tissue, events, output_count = _pooled_shim_packed(**POOLS[pool]) atoms = tissue[0].numel() @@ -916,8 +916,8 @@ def test_a_shimmed_gradient_takes_the_first_order_kernel(state_count) -> None: state count before, and the shim adds a per-event load the others do not make. """ - from torchsim.sequence import _accelerators - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence import _accelerators + from blochsim.sequence._accelerators import _run_packed_vjp tissue, events, output_count = _two_shim_packed(voxels=64, device="cuda") seed = torch.randn( @@ -961,7 +961,7 @@ def test_a_row_no_pulse_drives_gets_no_gradient() -> None: """The gradient lands in the shim the pulse named, so a row nobody drives comes back at zero -- which is what says the row index was read. """ - from torchsim.sequence._accelerators import _run_packed_vjp + from blochsim.sequence._accelerators import _run_packed_vjp tissue, events, output_count = _two_shim_packed(voxels=32, device="cuda") atoms = tissue[0].numel() diff --git a/tests/sequence/test_unrefocused_time.py b/tests/sequence/test_unrefocused_time.py index 6c00abb6..196986fa 100644 --- a/tests/sequence/test_unrefocused_time.py +++ b/tests/sequence/test_unrefocused_time.py @@ -20,12 +20,12 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _builders, ) -from torchsim.sequence._accelerators import _pack_events +from blochsim.sequence._accelerators import _pack_events T1_MS, T2_MS = 1000.0, 80.0 # Deliberately not a whole number of turns per repetition or echo spacing: a @@ -162,7 +162,7 @@ def test_a_spoiled_train_carries_its_echo_time_to_every_sample(): def _both_ways(description, tissue, wants_grad=False): """The same run with the field on the samples and carried by the states.""" - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators answers = [] for analytic in (True, False): @@ -226,7 +226,7 @@ def test_off_resonance_no_longer_keeps_a_run_off_the_real_kernels( is not: the states never see it, so they stay on the axis and the reduced kernels stay available -- for the plain pass and for the derivatives alike. """ - from torchsim.sequence import _accelerators + from blochsim.sequence import _accelerators verdicts = [] original = _accelerators._auto_real_axis diff --git a/tests/sequence/test_vjp.py b/tests/sequence/test_vjp.py index 3edb2863..7f0e7f82 100644 --- a/tests/sequence/test_vjp.py +++ b/tests/sequence/test_vjp.py @@ -5,7 +5,7 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( EpgEngine, TissueProperties, _accelerators, @@ -14,14 +14,14 @@ mrf_description, spgr_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( NO_GEOMETRY, _NativeEpg, _pack_events, _run_packed_vjp, ) -from torchsim.sequence._parameters import FLOAT_NAMES, TISSUE_COUNT -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._parameters import FLOAT_NAMES, TISSUE_COUNT +from blochsim.sequence._simulation import _prepare_tissue from utils.packed_reference import simulate_packed # Gradient tuples arrive in the packing order, so name them from the same place. @@ -231,17 +231,17 @@ def test_fused_vjp_is_bitwise_deterministic(monkeypatch) -> None: for expected, actual in zip(first, _gradients(), strict=True): assert torch.equal(expected, actual) - monkeypatch.setenv("TORCHSIM_NUM_THREADS", "1") + monkeypatch.setenv("BLOCHSIM_NUM_THREADS", "1") single = _gradients() - monkeypatch.setenv("TORCHSIM_NUM_THREADS", "8") + monkeypatch.setenv("BLOCHSIM_NUM_THREADS", "8") for expected, actual in zip(single, _gradients(), strict=True): assert torch.equal(expected, actual) def _objective_gradient(): """A precision cost, which is a directional derivative differentiated back.""" - from torchsim.optim import crlb - from torchsim.simulators import FSESimulator + from blochsim.optim import crlb + from blochsim.simulators import FSESimulator sequence = FSESimulator(ESP=5.0, TR=3000.0, states=10) flip = torch.linspace(150.0, 90.0, 12).requires_grad_(True) diff --git a/tests/sequence/test_washout_kernels.py b/tests/sequence/test_washout_kernels.py index bb0ae6a6..1c442a78 100644 --- a/tests/sequence/test_washout_kernels.py +++ b/tests/sequence/test_washout_kernels.py @@ -16,11 +16,11 @@ import pytest import torch -from torchsim.sequence import ( +from blochsim.sequence import ( TissueProperties, fse_description, ) -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( NO_GEOMETRY, Geometry, _pack_events, @@ -30,8 +30,8 @@ _run_packed_vjp_jvp, geometry_of, ) -from torchsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES -from torchsim.sequence._simulation import _prepare_tissue +from blochsim.sequence._parameters import FLOAT_NAMES, TISSUE_NAMES +from blochsim.sequence._simulation import _prepare_tissue from utils.epg import washout_op from utils.packed_reference import simulate_packed diff --git a/tests/sequence/test_worker_pool.py b/tests/sequence/test_worker_pool.py index 0a64da7d..1aaf5e92 100644 --- a/tests/sequence/test_worker_pool.py +++ b/tests/sequence/test_worker_pool.py @@ -15,7 +15,7 @@ Only a build that multiversions has a wide clone to leave a state behind. Where the kernels are compiled once -- clang, MSVC, musl, anything not x86 -- the two arms run the same code and the ratio measures how quiet the machine was, which -is not a property of TorchSim. +is not a property of BlochSim. """ from __future__ import annotations @@ -25,14 +25,14 @@ import pytest import torch -import torchsim._epg_cpu as kernels -from torchsim.sequence._accelerators import ( +import blochsim._epg_cpu as kernels +from blochsim.sequence._accelerators import ( _pack_events, _run_packed, _run_packed_jvp, ) -from torchsim.sequence._builders import mrf_description -from torchsim.sequence._simulation import TissueProperties, _prepare_tissue +from blochsim.sequence._builders import mrf_description +from blochsim.sequence._simulation import TissueProperties, _prepare_tissue ATOMS = 4000 REPETITIONS = 120 diff --git a/tests/simulators/test_closed_forms.py b/tests/simulators/test_closed_forms.py index 14a1f70b..f00da180 100644 --- a/tests/simulators/test_closed_forms.py +++ b/tests/simulators/test_closed_forms.py @@ -10,7 +10,7 @@ import pytest import torch -from torchsim.simulators import ( +from blochsim.simulators import ( ASLSimulator, DiffusionSimulator, HSFPSimulator, diff --git a/tests/simulators/test_epg_trains.py b/tests/simulators/test_epg_trains.py index 74e83987..d9f57370 100644 --- a/tests/simulators/test_epg_trains.py +++ b/tests/simulators/test_epg_trains.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from torchsim.simulators import ( +from blochsim.simulators import ( FLASHSimulator, HyperechoSimulator, StimulatedEchoSimulator, diff --git a/tests/simulators/test_sampled_pulses.py b/tests/simulators/test_sampled_pulses.py index 530bc61d..4eb22347 100644 --- a/tests/simulators/test_sampled_pulses.py +++ b/tests/simulators/test_sampled_pulses.py @@ -18,8 +18,8 @@ import torch from scipy.linalg import expm -from torchsim.sequence import SampledPulse -from torchsim.simulators import CESTSimulator, FLASHSimulator, TrueFISPSimulator +from blochsim.sequence import SampledPulse +from blochsim.simulators import CESTSimulator, FLASHSimulator, TrueFISPSimulator GAMMA_BAR = 42.57747892e6 DWELL = 1e-5 diff --git a/tests/utils/interpreted.py b/tests/utils/interpreted.py index d795fb0a..43622be3 100644 --- a/tests/utils/interpreted.py +++ b/tests/utils/interpreted.py @@ -73,16 +73,16 @@ class _Poisoned: def install(poison: bool = False) -> None: """Point the kernels at an ``acos`` the interpreter can evaluate.""" - from torchsim.sequence import _epg_triton + from blochsim.sequence import _epg_triton _epg_triton.libdevice = _Poisoned if poison else _Interpretable def _tissue(voxels: int) -> tuple[torch.Tensor, ...]: - from torchsim.sequence import ( + from blochsim.sequence import ( TissueProperties, ) - from torchsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._simulation import _prepare_tissue prepared, _, _ = _prepare_tissue( TissueProperties( @@ -101,7 +101,7 @@ def _tissue(voxels: int) -> tuple[torch.Tensor, ...]: def _events(description: Any) -> tuple[tuple[torch.Tensor, ...], int]: - from torchsim.sequence._accelerators import _pack_events + from blochsim.sequence._accelerators import _pack_events packed = _pack_events( description, @@ -131,7 +131,7 @@ def _both(run: Any, force_narrow: bool) -> tuple[Any, Any]: narrow, so every row takes the series and the two arms should agree to the bit. """ - from torchsim.sequence import _epg_triton + from blochsim.sequence import _epg_triton original = _epg_triton._tabulate_three_pool built: list[bool] = [] @@ -188,7 +188,7 @@ def _chunked(voxels: int) -> Any: The cotangent table is sized and indexed per chunk, so a single-chunk run cannot tell a chunk-local index from a global one. """ - from torchsim.sequence import _epg_triton + from blochsim.sequence import _epg_triton cut: list[int] = [] @@ -210,9 +210,9 @@ def _unread(voxels: int, states: int) -> None: is what says so rather than reading the branches and believing it. """ install(poison=True) - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._lineshape import lineshape_table - from torchsim.sequence._parameters import narrow_three_pool + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._lineshape import lineshape_table + from blochsim.sequence._parameters import narrow_three_pool echoes = 6 tissue = _tissue(voxels) @@ -257,7 +257,7 @@ def _streamed(voxels: int, states: int) -> None: and nothing else here reaches it. """ install() - from torchsim.sequence import _builders, _epg_triton + from blochsim.sequence import _builders, _epg_triton echoes = 6 tissue = _tissue(voxels) @@ -301,9 +301,9 @@ def _washed(voxels: int, states: int) -> None: than one, because the gradients it pools are scaled by it. """ install() - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._lineshape import lineshape_table - from torchsim.sequence._parameters import TISSUE_NAMES, Geometry + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._lineshape import lineshape_table + from blochsim.sequence._parameters import TISSUE_NAMES, Geometry echoes = 6 tissue = list(_tissue(voxels)) @@ -353,9 +353,9 @@ def _real(voxels: int, states: int, shims: int = 1) -> None: of it is reached by the other cases here, which are all complex. """ install() - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._accelerators import real_subspace_axis - from torchsim.sequence._parameters import FLOAT_NAMES, OUTSIDE_THE_SUBSPACE + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._accelerators import real_subspace_axis + from blochsim.sequence._parameters import FLOAT_NAMES, OUTSIDE_THE_SUBSPACE from utils.packed_reference import simulate_packed echoes = 6 @@ -465,9 +465,9 @@ def _spoiled(voxels: int, states: int) -> None: each held to the complex kernels that carry the same train. """ install() - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._accelerators import real_subspace_axis - from torchsim.sequence._parameters import FLOAT_NAMES, OUTSIDE_THE_SUBSPACE + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._accelerators import real_subspace_axis + from blochsim.sequence._parameters import FLOAT_NAMES, OUTSIDE_THE_SUBSPACE from utils.packed_reference import simulate_packed repetitions = 6 @@ -547,8 +547,8 @@ def _pooled(voxels: int, states: int, pools: int) -> None: and neither is exercised at one or two pools without a card. """ install() - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._lineshape import lineshape_table from utils.packed_reference import simulate_packed echoes = 6 @@ -611,8 +611,8 @@ def _shimmed(voxels: int, states: int) -> None: case that would show one standing in for the other. """ install() - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._lineshape import lineshape_table shims, echoes = 2, 6 held = _tissue(voxels) @@ -712,10 +712,10 @@ def _profiled(voxels: int, states: int) -> None: import numpy as np - from torchsim import rf_definition - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._accelerators import _across_the_table - from torchsim.sequence._transition import ( + from blochsim import rf_definition + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._accelerators import _across_the_table + from blochsim.sequence._transition import ( SliceTables, exact_slice_profile, transition_table, @@ -773,14 +773,14 @@ def _narrowed(voxels: int, states: int) -> None: install() import math - from torchsim.sequence import ( + from blochsim.sequence import ( TissueProperties, _builders, _epg_triton, ) - from torchsim.sequence._accelerators import real_subspace_axis - from torchsim.sequence._parameters import TISSUE_NAMES, features_of - from torchsim.sequence._simulation import _prepare_tissue + from blochsim.sequence._accelerators import real_subspace_axis + from blochsim.sequence._parameters import TISSUE_NAMES, features_of + from blochsim.sequence._simulation import _prepare_tissue echoes = 6 tissue = TissueProperties( @@ -861,8 +861,8 @@ def _case(name: str) -> None: _unread(3, 4) return install() - from torchsim.sequence import _builders, _epg_triton - from torchsim.sequence._lineshape import lineshape_table + from blochsim.sequence import _builders, _epg_triton + from blochsim.sequence._lineshape import lineshape_table voxels, states = 3, 4 if name == "narrow": diff --git a/tests/utils/interpreted_pools.py b/tests/utils/interpreted_pools.py index 464703e0..d3ffb917 100644 --- a/tests/utils/interpreted_pools.py +++ b/tests/utils/interpreted_pools.py @@ -21,6 +21,10 @@ import torch +from blochsim.sequence import _accelerators, _pools, _pools_triton +from blochsim.sequence._lineshape import lineshape_table +from blochsim.sequence._parameters import NO_GEOMETRY +from blochsim.sequence._transition import DynamicPairs from sequence.test_many_pools import ( _PAIR_ROW, GEOMETRY, @@ -32,10 +36,6 @@ _instantaneous_table, _tissue, ) -from torchsim.sequence import _accelerators, _pools, _pools_triton -from torchsim.sequence._lineshape import lineshape_table -from torchsim.sequence._parameters import NO_GEOMETRY -from torchsim.sequence._transition import DynamicPairs # What one kernel is held to against the other, relative to the largest entry # of each result. Both accumulate in float32, in different orders. diff --git a/tests/utils/packed_reference.py b/tests/utils/packed_reference.py index 07dc2e78..e4e69b2b 100644 --- a/tests/utils/packed_reference.py +++ b/tests/utils/packed_reference.py @@ -20,7 +20,7 @@ import torch -from torchsim.sequence._accelerators import ( +from blochsim.sequence._accelerators import ( _INVERSION, _POST_SHIFT, _PRE_SHIFT, @@ -28,7 +28,7 @@ _SHIFT_AFTER, _SPOIL_AFTER, ) -from torchsim.sequence._parameters import NO_GEOMETRY, Geometry +from blochsim.sequence._parameters import NO_GEOMETRY, Geometry __all__ = ["simulate_packed", "simulate_packed_pools"] @@ -63,12 +63,12 @@ def simulate_packed( The two scales the velocity is read through, exactly as the kernels take them. profile - A :class:`~torchsim.sequence._transition.TransitionTable`, or ``None`` + A :class:`~blochsim.sequence._transition.TransitionTable`, or ``None`` for the instantaneous pulse. Given one, a pulse turns through the rotation the table holds at its effective flip rather than through a flip and a phase. dynamic - A :class:`~torchsim.sequence._transition.DynamicPairs`, for a pulse + A :class:`~blochsim.sequence._transition.DynamicPairs`, for a pulse whose channel weights vary while it plays. The rotation is already integrated at that pulse's own flip, so only the event's phase is left to apply. Mutually exclusive with ``profile``: a pair integrated at the @@ -78,7 +78,7 @@ def simulate_packed( voxel-major, so a voxel's position along the slice is its index modulo this, which is the row of the table it reads. lineshape - A :class:`~torchsim.sequence._lineshape.LineshapeTable`, or ``None`` + A :class:`~blochsim.sequence._lineshape.LineshapeTable`, or ``None`` for a single pool. Given one, the longitudinal step carries a bound pool alongside the free water and each pulse saturates it. exchanging @@ -634,7 +634,7 @@ def simulate_packed_pools( events, state_count, output_count, geometry, profile, dynamic, locations As :func:`simulate_packed` takes them. lineshape - A :class:`~torchsim.sequence._lineshape.LineshapeTable` for a + A :class:`~blochsim.sequence._lineshape.LineshapeTable` for a semisolid pool, or ``None`` for none. Returns From e447b3b583474558d01d251294b015a375a0ca1a Mon Sep 17 00:00:00 2001 From: mcencini Date: Tue, 6 Oct 2026 16:44:58 +0200 Subject: [PATCH 2/2] Import the CUDA parity test's kernels from blochsim Co-Authored-By: Claude Opus 5.5 --- tests/sequence/test_cuda_parity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sequence/test_cuda_parity.py b/tests/sequence/test_cuda_parity.py index 03883071..f2be8b6e 100644 --- a/tests/sequence/test_cuda_parity.py +++ b/tests/sequence/test_cuda_parity.py @@ -573,7 +573,7 @@ def test_one_half_of_the_off_axis_turn_matches_the_cpu_kernel(given): after each echo winds unlike the crushers, which keeps off-resonance in the states rather than on the samples. """ - from torchsim.sequence import ( + from blochsim.sequence import ( AdcRole, EpgEngine, EventAction,