dkx 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dkx-2.2.0 → dkx-2.3.0}/PKG-INFO +39 -42
- {dkx-2.2.0 → dkx-2.3.0}/README.md +36 -40
- {dkx-2.2.0 → dkx-2.3.0}/dkx/__init__.py +84 -5
- {dkx-2.2.0 → dkx-2.3.0}/dkx/plotting.py +136 -9
- {dkx-2.2.0 → dkx-2.3.0}/dkx/representative.py +142 -11
- {dkx-2.2.0 → dkx-2.3.0}/dkx/run.py +127 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/solve.py +20 -3
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/release.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/dkx.egg-info/PKG-INFO +39 -42
- {dkx-2.2.0 → dkx-2.3.0}/dkx.egg-info/SOURCES.txt +7 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx.egg-info/requires.txt +2 -1
- {dkx-2.2.0 → dkx-2.3.0}/pyproject.toml +12 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_benchmark_artifact_policy.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_benchmark_doc_claims.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_collisionality_artifact.py +1 -1
- dkx-2.3.0/tests/test_compilation_cache_is_not_size_capped.py +68 -0
- dkx-2.3.0/tests/test_environment_guard.py +57 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_er_trajectory_sweep.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_er_trajectory_sweep_artifact.py +1 -1
- dkx-2.3.0/tests/test_example_docstrings_stay_short.py +33 -0
- dkx-2.3.0/tests/test_examples_tree_contract.py +287 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generate_autodiff_sensitivity_validation.py +2 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generate_fortran_suite_benchmark_summary.py +2 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generate_sfincs_paper_figs.py +2 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generate_validation_dashboard.py +2 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generate_w7x_high_nu_performance.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_getting_started_examples.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_paper_benchmark_bootstrap_loop.py +2 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_paper_benchmark_electron_root.py +2 -2
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_paper_benchmark_monoenergetic.py +4 -4
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_paper_benchmark_w7x_ambipolar.py +2 -2
- dkx-2.3.0/tests/test_plot_pdf_paging_is_documented_correctly.py +36 -0
- dkx-2.3.0/tests/test_readme_quickstart_runs.py +53 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_release_gate_metadata.py +1 -1
- dkx-2.3.0/tests/test_representative_run_works_when_installed.py +91 -0
- dkx-2.3.0/tests/test_run_function_is_not_shadowed_by_run_module.py +67 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_artifacts.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_deferred_lane_gates.py +3 -3
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_manifest_schema.py +1 -1
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validity.py +1 -1
- dkx-2.2.0/tests/test_examples_tree_contract.py +0 -459
- {dkx-2.2.0 → dkx-2.3.0}/LICENSE +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/__main__.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/ambipolar.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/api.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/batch.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/bootstrap.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/bounce_averaged.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/cli.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/coarse_precond.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/collisions.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/collocation.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/compare.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/console.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/constants.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/drift_kinetic.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/er.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/impurity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/input_compat.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/inputs.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/io.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/magnetic_geometry.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/moments.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/momentum_correction.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/monoenergetic.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/multigrid.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/namelist.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/paths.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/phase_space.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/phi1.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/profiling.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/sensitivity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/shaing_callen.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/solver_trace.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/sparse_precond.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/species.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/units.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/__init__.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/artifacts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/data_fetch.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/equilibria_manifest.json +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/fortran.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/lanes.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/panels.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/policy.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validation/series.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/validity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/variational.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/vmec_ascii.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/workflows/__init__.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/workflows/geometry_adapters.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/workflows/optimization.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/workflows/scans.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/writer.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx/xgrid.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx.egg-info/dependency_links.txt +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx.egg-info/entry_points.txt +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/dkx.egg-info/top_level.txt +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/setup.cfg +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_api_contracts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_audit_suite_output_keys.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_audit_suite_runtime_drift.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_batch.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_bootstrap_objective.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_bounce_averaged.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_classical_transport_physics_gates.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_cli_solve_mode.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_cli_validation_io_fast_coverage.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_coarse_precond_constants.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_collision_physics_gates.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_collisions_improved_sugama.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_collocation.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_compare_reference_corruption.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_create_production_benchmark_inputs.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_data_fetch.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_domain_package_import_contracts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_drift_kinetic.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_er.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_er_scan_and_ambipolar.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_example_qa_bootstrap.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_examples_bootstrap_dkx.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_examples_optimization.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_examples_pedagogic.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_examples_tutorials.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_examples_workflow_browser.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_export_f_canonical_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_figure_provenance.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_finite_beta_vmec_example.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_float64_single_owner.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_fokker_planck_phi1_reduces_to_no_phi1.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_fortran_mpi_wrapper.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generate_readme_fast_branch_audit.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_generated_deck_coverage.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_geometry_scheme11_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_gpu_solver_path_artifacts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_h5_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_helper_module_coverage.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_impurity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_input_compat.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_input_namelist_writer.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_inputs_console.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_jax_geometry_adapters.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_kinetic_operator_fortran_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_magnetic_drift_diagonal.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_magnetic_drifts_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_magnetic_geometry.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_momentum_correction.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_monoenergetic_database.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_multigrid.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_namelist.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_comparison.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_evidence.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_ladder.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_neoclassical_objectives.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_promotion.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_public_scripts_cli.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_real_artifacts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_wave3_coverage.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_optimization_workflow.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_coordinate_physics_gates.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_formats.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_constraintscheme34_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_magdrift_schemes_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_nonstelsym_vmec_scheme5_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme11_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme13_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme1_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme2_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme4_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme4_quick2species_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_scheme5_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_h5_xgrid_schemes_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_key_coverage_regressions.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_output_npz_canonical_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_package_metadata.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_phase_space.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_phase_timing.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_phi1.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_plotting.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_preconditioner_xi_semantics.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_profile_write_output_trace.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_profiling.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_profiling_helpers.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_public_docs_wording_contract.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_public_facades_and_paths.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_reference_goldens_lightweight.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_remaining_public_contracts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_repo_size_policy.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_representative.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_research_lane_policy.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_rhsmode1_current_closure.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_rhsmode1_phi1_write_output_end_to_end.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_rhsmode1_write_output_end_to_end.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_rosenbluth_quadrature.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_run_rhsmode1.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_run_transport.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_runtime_helper_coverage.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_scans_progress_and_recycle.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_sensitivity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_shaing_callen.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_solve.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_solver_path_artifacts.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_solver_trace.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_solver_trace_canonical_parity.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_source_tree_consolidation.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_sparse_precond.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_species_constants.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_tier1_refinement.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_tier2_memory_guard.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_transport_matrix_write_output_end_to_end.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_units.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_upstream_scanplot2_smoke.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_utils_smoke.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_figures.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_math.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_petsc_and_upstream_helpers.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_validation_policy_coverage.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_variational_bounds.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_vmec_wout_ascii.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_vmec_wout_conventions.py +0 -0
- {dkx-2.2.0 → dkx-2.3.0}/tests/test_vmex_workflow.py +0 -0
{dkx-2.2.0 → dkx-2.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dkx
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Neoclassical transport solver with CPU/GPU and differentiable JAX workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/uwplasma/DKX
|
|
6
6
|
Project-URL: Documentation, https://sfincs-jax.readthedocs.io/
|
|
@@ -10,7 +10,7 @@ Requires-Python: >=3.10
|
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Dist: jax
|
|
13
|
-
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: numpy>=2.1
|
|
14
14
|
Requires-Dist: scipy
|
|
15
15
|
Requires-Dist: h5py
|
|
16
16
|
Requires-Dist: netCDF4
|
|
@@ -23,6 +23,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
23
23
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
24
24
|
Requires-Dist: pytest-xdist; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest-split; extra == "dev"
|
|
26
|
+
Requires-Dist: pypdf; extra == "dev"
|
|
26
27
|
Provides-Extra: docs
|
|
27
28
|
Requires-Dist: sphinx; extra == "docs"
|
|
28
29
|
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
@@ -92,45 +93,41 @@ throughout: 37.3 s on W7-X and 45.8 s on a finite-β precise-QA equilibrium, the
|
|
|
92
93
|
latter split 16.4 s for the 21-point scan, 6.0 s for the `|B|` solve, and 22.5 s
|
|
93
94
|
for the radial scan's 50 drift-kinetic solves.
|
|
94
95
|
|
|
95
|
-
The same solve from Python
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
The same solve from Python. No input file, no argument parsing — the
|
|
97
|
+
parameters are the ones a deck carries, passed as keywords
|
|
98
|
+
([`examples/1_basics/run_tokamak.py`](examples/1_basics/run_tokamak.py)):
|
|
98
99
|
|
|
99
100
|
```python
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Zs = 1 mHats = 1.0 nHats = 1.0 THats = 0.5
|
|
114
|
-
dNHatdrHats = -6.0 dTHatdrHats = -3.0
|
|
115
|
-
/
|
|
116
|
-
&physicsParameters
|
|
117
|
-
Delta = 4.5694d-3 alpha = 1.0 nu_n = 8.4774d-3
|
|
118
|
-
Er = 0.0 collisionOperator = 1 ! pitch-angle scattering
|
|
119
|
-
/
|
|
120
|
-
&resolutionParameters
|
|
121
|
-
Ntheta = 15 Nzeta = 1 Nxi = 8 NL = 4 Nx = 6
|
|
122
|
-
solverTolerance = 1d-10
|
|
123
|
-
/
|
|
124
|
-
""")
|
|
125
|
-
|
|
126
|
-
run = run_profile(deck, solve_method="auto", out_path=Path("sfincsOutput.h5"))
|
|
101
|
+
import dkx
|
|
102
|
+
|
|
103
|
+
run = dkx.run(
|
|
104
|
+
geometryScheme=1, # circular tokamak: BHat = 1 + 0.1 cos(theta)
|
|
105
|
+
inputRadialCoordinate=3, rN_wish=0.3,
|
|
106
|
+
B0OverBBar=1.0, GHat=1.0, IHat=0.0, iota=1.31,
|
|
107
|
+
epsilon_t=0.1, epsilon_h=0.0, psiAHat=0.045, aHat=0.1,
|
|
108
|
+
Zs=[1.0], mHats=[1.0], nHats=[1.0], THats=[0.5],
|
|
109
|
+
dNHatdrHats=[-6.0], dTHatdrHats=[-3.0],
|
|
110
|
+
Ntheta=15, Nzeta=1, Nxi=8, NL=4, Nx=6,
|
|
111
|
+
Delta=4.5694e-3, alpha=1.0, nu_n=8.4774e-3,
|
|
112
|
+
Er=0.0, collisionOperator=1, # 1 = pitch-angle scattering, 0 = Fokker-Planck
|
|
113
|
+
)
|
|
127
114
|
print("particle flux:", float(run.moments["particleFlux_vm_psiHat"][0]))
|
|
128
115
|
print("bootstrap current <j.B>:", float(run.moments["FSABjHat"]))
|
|
129
116
|
```
|
|
130
117
|
|
|
131
|
-
`
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
`dkx.run` reads `RHSMode` from the case and dispatches, so the same call covers
|
|
119
|
+
a profile-gradient run and a transport matrix. It also takes an
|
|
120
|
+
`input.namelist` path — `dkx.run("input.namelist")` — and overrides on top of
|
|
121
|
+
one, which is what a convergence scan is:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
dkx.run("input.namelist", Ntheta=25) # same deck, finer grid
|
|
125
|
+
dkx.run(..., out="sfincsOutput.h5") # write a file; the suffix picks the format
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Results come back in memory: `run.moments` is keyed by the SFINCS output names,
|
|
129
|
+
and `run.solve_result` carries the residual, the iteration count, and whether it
|
|
130
|
+
converged.
|
|
134
131
|
|
|
135
132
|
## Parity with SFINCS Fortran v3
|
|
136
133
|
|
|
@@ -166,7 +163,7 @@ flow corrections, an extended-collisionality Sugama operator, monoenergetic
|
|
|
166
163
|
database mode, batched GPU scans, a bounce-averaged 1/ν surrogate) — lives in
|
|
167
164
|
[docs/feature_matrix.rst](docs/feature_matrix.rst).
|
|
168
165
|
|
|
169
|
-
*Reproduce with the drivers in [`
|
|
166
|
+
*Reproduce with the drivers in [`tools/parity/`](tools/parity/).*
|
|
170
167
|
|
|
171
168
|
### Interpreting a difference
|
|
172
169
|
|
|
@@ -269,7 +266,7 @@ matched-deck SFINCS Fortran v3 cross-check points at solver precision. The
|
|
|
269
266
|
quasi-helically symmetric HSX case shows the suppressed 1/ν branch that W7-X and
|
|
270
267
|
TJ-II retain.
|
|
271
268
|
|
|
272
|
-
*Reproduce with `python
|
|
269
|
+
*Reproduce with `python tools/paper_benchmarks/monoenergetic_icnts_w7x.py`
|
|
273
270
|
(and the `_tjii` / `_hsx` companions).*
|
|
274
271
|
|
|
275
272
|
## Low-collisionality bootstrap convergence
|
|
@@ -283,7 +280,7 @@ asymptote — the 1/ν-regime offset does not decay without orbit precession —
|
|
|
283
280
|
a small finite `EStar` detaches below `nuPrime ~ 1e-3` and flattens back toward
|
|
284
281
|
the asymptote, the E×B-precession dip.
|
|
285
282
|
|
|
286
|
-
*Reproduce with `python
|
|
283
|
+
*Reproduce with `python tools/paper_benchmarks/shaing_callen_convergence.py`.*
|
|
287
284
|
|
|
288
285
|
## Ambipolar Er and electron roots
|
|
289
286
|
|
|
@@ -296,8 +293,8 @@ sign, and follows the physical branch by radial continuity to reproduce the
|
|
|
296
293
|
electron-root → ion-root crossover near `ρ ~ 0.6`, matching the reference `E_r`
|
|
297
294
|
within the digitization uncertainty (~1.5 kV/m mean difference).
|
|
298
295
|
|
|
299
|
-
*Reproduce with `python
|
|
300
|
-
`python
|
|
296
|
+
*Reproduce with `python tools/paper_benchmarks/w7x_ambipolar_er.py` and
|
|
297
|
+
`python tools/paper_benchmarks/electron_root_optimization.py`.*
|
|
301
298
|
|
|
302
299
|
## Impurity transport
|
|
303
300
|
|
|
@@ -310,7 +307,7 @@ diagnostic recovers the exact `-Z` density-peaking coefficient and the classical
|
|
|
310
307
|
1/2 collisional screening coefficient, with an autodiff ion-temperature-gradient
|
|
311
308
|
derivative verified against finite differences.
|
|
312
309
|
|
|
313
|
-
*Reproduce with `python
|
|
310
|
+
*Reproduce with `python tools/paper_benchmarks/impurity_transport.py`.*
|
|
314
311
|
|
|
315
312
|
## Kinetic-in-the-loop bootstrap
|
|
316
313
|
|
|
@@ -324,7 +321,7 @@ error the kinetic-in-the-loop iteration removes by construction — and one
|
|
|
324
321
|
`jax.value_and_grad` differentiates the total bootstrap current through the
|
|
325
322
|
equilibrium → Boozer → kinetic chain.
|
|
326
323
|
|
|
327
|
-
*Reproduce with `python
|
|
324
|
+
*Reproduce with `python tools/paper_benchmarks/bootstrap_consistency_kinetic_loop.py`
|
|
328
325
|
(needs the optional `vmex` + `booz_xform_jax` companions).*
|
|
329
326
|
|
|
330
327
|
To put that inside an optimizer instead of after it, `dkx.bootstrap.KineticBootstrapCurrent`
|
|
@@ -62,45 +62,41 @@ throughout: 37.3 s on W7-X and 45.8 s on a finite-β precise-QA equilibrium, the
|
|
|
62
62
|
latter split 16.4 s for the 21-point scan, 6.0 s for the `|B|` solve, and 22.5 s
|
|
63
63
|
for the radial scan's 50 drift-kinetic solves.
|
|
64
64
|
|
|
65
|
-
The same solve from Python
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
The same solve from Python. No input file, no argument parsing — the
|
|
66
|
+
parameters are the ones a deck carries, passed as keywords
|
|
67
|
+
([`examples/1_basics/run_tokamak.py`](examples/1_basics/run_tokamak.py)):
|
|
68
68
|
|
|
69
69
|
```python
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Zs = 1 mHats = 1.0 nHats = 1.0 THats = 0.5
|
|
84
|
-
dNHatdrHats = -6.0 dTHatdrHats = -3.0
|
|
85
|
-
/
|
|
86
|
-
&physicsParameters
|
|
87
|
-
Delta = 4.5694d-3 alpha = 1.0 nu_n = 8.4774d-3
|
|
88
|
-
Er = 0.0 collisionOperator = 1 ! pitch-angle scattering
|
|
89
|
-
/
|
|
90
|
-
&resolutionParameters
|
|
91
|
-
Ntheta = 15 Nzeta = 1 Nxi = 8 NL = 4 Nx = 6
|
|
92
|
-
solverTolerance = 1d-10
|
|
93
|
-
/
|
|
94
|
-
""")
|
|
95
|
-
|
|
96
|
-
run = run_profile(deck, solve_method="auto", out_path=Path("sfincsOutput.h5"))
|
|
70
|
+
import dkx
|
|
71
|
+
|
|
72
|
+
run = dkx.run(
|
|
73
|
+
geometryScheme=1, # circular tokamak: BHat = 1 + 0.1 cos(theta)
|
|
74
|
+
inputRadialCoordinate=3, rN_wish=0.3,
|
|
75
|
+
B0OverBBar=1.0, GHat=1.0, IHat=0.0, iota=1.31,
|
|
76
|
+
epsilon_t=0.1, epsilon_h=0.0, psiAHat=0.045, aHat=0.1,
|
|
77
|
+
Zs=[1.0], mHats=[1.0], nHats=[1.0], THats=[0.5],
|
|
78
|
+
dNHatdrHats=[-6.0], dTHatdrHats=[-3.0],
|
|
79
|
+
Ntheta=15, Nzeta=1, Nxi=8, NL=4, Nx=6,
|
|
80
|
+
Delta=4.5694e-3, alpha=1.0, nu_n=8.4774e-3,
|
|
81
|
+
Er=0.0, collisionOperator=1, # 1 = pitch-angle scattering, 0 = Fokker-Planck
|
|
82
|
+
)
|
|
97
83
|
print("particle flux:", float(run.moments["particleFlux_vm_psiHat"][0]))
|
|
98
84
|
print("bootstrap current <j.B>:", float(run.moments["FSABjHat"]))
|
|
99
85
|
```
|
|
100
86
|
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
|
|
87
|
+
`dkx.run` reads `RHSMode` from the case and dispatches, so the same call covers
|
|
88
|
+
a profile-gradient run and a transport matrix. It also takes an
|
|
89
|
+
`input.namelist` path — `dkx.run("input.namelist")` — and overrides on top of
|
|
90
|
+
one, which is what a convergence scan is:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
dkx.run("input.namelist", Ntheta=25) # same deck, finer grid
|
|
94
|
+
dkx.run(..., out="sfincsOutput.h5") # write a file; the suffix picks the format
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Results come back in memory: `run.moments` is keyed by the SFINCS output names,
|
|
98
|
+
and `run.solve_result` carries the residual, the iteration count, and whether it
|
|
99
|
+
converged.
|
|
104
100
|
|
|
105
101
|
## Parity with SFINCS Fortran v3
|
|
106
102
|
|
|
@@ -136,7 +132,7 @@ flow corrections, an extended-collisionality Sugama operator, monoenergetic
|
|
|
136
132
|
database mode, batched GPU scans, a bounce-averaged 1/ν surrogate) — lives in
|
|
137
133
|
[docs/feature_matrix.rst](docs/feature_matrix.rst).
|
|
138
134
|
|
|
139
|
-
*Reproduce with the drivers in [`
|
|
135
|
+
*Reproduce with the drivers in [`tools/parity/`](tools/parity/).*
|
|
140
136
|
|
|
141
137
|
### Interpreting a difference
|
|
142
138
|
|
|
@@ -239,7 +235,7 @@ matched-deck SFINCS Fortran v3 cross-check points at solver precision. The
|
|
|
239
235
|
quasi-helically symmetric HSX case shows the suppressed 1/ν branch that W7-X and
|
|
240
236
|
TJ-II retain.
|
|
241
237
|
|
|
242
|
-
*Reproduce with `python
|
|
238
|
+
*Reproduce with `python tools/paper_benchmarks/monoenergetic_icnts_w7x.py`
|
|
243
239
|
(and the `_tjii` / `_hsx` companions).*
|
|
244
240
|
|
|
245
241
|
## Low-collisionality bootstrap convergence
|
|
@@ -253,7 +249,7 @@ asymptote — the 1/ν-regime offset does not decay without orbit precession —
|
|
|
253
249
|
a small finite `EStar` detaches below `nuPrime ~ 1e-3` and flattens back toward
|
|
254
250
|
the asymptote, the E×B-precession dip.
|
|
255
251
|
|
|
256
|
-
*Reproduce with `python
|
|
252
|
+
*Reproduce with `python tools/paper_benchmarks/shaing_callen_convergence.py`.*
|
|
257
253
|
|
|
258
254
|
## Ambipolar Er and electron roots
|
|
259
255
|
|
|
@@ -266,8 +262,8 @@ sign, and follows the physical branch by radial continuity to reproduce the
|
|
|
266
262
|
electron-root → ion-root crossover near `ρ ~ 0.6`, matching the reference `E_r`
|
|
267
263
|
within the digitization uncertainty (~1.5 kV/m mean difference).
|
|
268
264
|
|
|
269
|
-
*Reproduce with `python
|
|
270
|
-
`python
|
|
265
|
+
*Reproduce with `python tools/paper_benchmarks/w7x_ambipolar_er.py` and
|
|
266
|
+
`python tools/paper_benchmarks/electron_root_optimization.py`.*
|
|
271
267
|
|
|
272
268
|
## Impurity transport
|
|
273
269
|
|
|
@@ -280,7 +276,7 @@ diagnostic recovers the exact `-Z` density-peaking coefficient and the classical
|
|
|
280
276
|
1/2 collisional screening coefficient, with an autodiff ion-temperature-gradient
|
|
281
277
|
derivative verified against finite differences.
|
|
282
278
|
|
|
283
|
-
*Reproduce with `python
|
|
279
|
+
*Reproduce with `python tools/paper_benchmarks/impurity_transport.py`.*
|
|
284
280
|
|
|
285
281
|
## Kinetic-in-the-loop bootstrap
|
|
286
282
|
|
|
@@ -294,7 +290,7 @@ error the kinetic-in-the-loop iteration removes by construction — and one
|
|
|
294
290
|
`jax.value_and_grad` differentiates the total bootstrap current through the
|
|
295
291
|
equilibrium → Boozer → kinetic chain.
|
|
296
292
|
|
|
297
|
-
*Reproduce with `python
|
|
293
|
+
*Reproduce with `python tools/paper_benchmarks/bootstrap_consistency_kinetic_loop.py`
|
|
298
294
|
(needs the optional `vmex` + `booz_xform_jax` companions).*
|
|
299
295
|
|
|
300
296
|
To put that inside an optimizer instead of after it, `dkx.bootstrap.KineticBootstrapCurrent`
|
|
@@ -10,12 +10,49 @@ from __future__ import annotations
|
|
|
10
10
|
# CLI invocations unless the user explicitly disables it. This improves cold-start
|
|
11
11
|
# performance without requiring environment configuration.
|
|
12
12
|
import os
|
|
13
|
+
import sys as _sys
|
|
14
|
+
import types as _types
|
|
13
15
|
import tempfile
|
|
14
16
|
|
|
15
17
|
# Suppress low-value XLA/PjRt C++ warning chatter by default. Users can still
|
|
16
18
|
# override this before importing dkx if they need backend debug logs.
|
|
17
19
|
os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "2")
|
|
18
20
|
|
|
21
|
+
|
|
22
|
+
def _check_numpy() -> None:
|
|
23
|
+
"""Fail with the actual problem instead of a NameError from numpy internals.
|
|
24
|
+
|
|
25
|
+
``jax`` requires ``numpy>=2.1``. Installing dkx into an environment that
|
|
26
|
+
holds a conda-managed numpy 1.x -- the Anaconda base environment is the
|
|
27
|
+
common case -- resolves without complaint and then dies on the first
|
|
28
|
+
``import jax`` with::
|
|
29
|
+
|
|
30
|
+
File numpy/core/getlimits.py, line 606, in smallest_normal
|
|
31
|
+
NameError: name 'isnan' is not defined
|
|
32
|
+
|
|
33
|
+
raised while ``ml_dtypes`` probes ``bfloat16``'s ``finfo``. That traceback
|
|
34
|
+
names neither dkx, nor jax, nor numpy's version as the cause, so it costs a
|
|
35
|
+
user real time. Checking here is cheap: numpy is imported either way, and
|
|
36
|
+
this runs before any jax import.
|
|
37
|
+
"""
|
|
38
|
+
import numpy # noqa: PLC0415
|
|
39
|
+
|
|
40
|
+
major = int(numpy.__version__.split(".", 1)[0])
|
|
41
|
+
if major < 2:
|
|
42
|
+
raise ImportError(
|
|
43
|
+
f"dkx needs numpy>=2.1 (jax's own requirement); found {numpy.__version__}. "
|
|
44
|
+
"numpy 1.x crashes inside ml_dtypes at 'import jax'. Fix the "
|
|
45
|
+
"environment with:\n"
|
|
46
|
+
" pip install -U 'numpy>=2.1' jax\n"
|
|
47
|
+
"or, better, install into a clean environment rather than the "
|
|
48
|
+
"Anaconda base one:\n"
|
|
49
|
+
" conda create -n dkx python=3.11 -y && conda activate dkx\n"
|
|
50
|
+
" pip install dkx"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_check_numpy()
|
|
55
|
+
|
|
19
56
|
_distributed_runtime_initialized = False
|
|
20
57
|
|
|
21
58
|
|
|
@@ -132,19 +169,28 @@ if _disable_cache not in {"1", "true", "yes", "on"}:
|
|
|
132
169
|
except OSError:
|
|
133
170
|
return False
|
|
134
171
|
|
|
172
|
+
# Versioned because the cache is bounded now (see the cap below) and
|
|
173
|
+
# JAX's LRU keeps a sidecar "-atime" file per entry. A directory
|
|
174
|
+
# filled in before the cap existed has none, so every eviction pass
|
|
175
|
+
# tries to touch a file that was never written and warns -- measured,
|
|
176
|
+
# 12 warnings on a single small solve against a legacy cache, and 0
|
|
177
|
+
# against a fresh one. Starting a new directory is what makes the
|
|
178
|
+
# bound work; the old one simply stops being written to.
|
|
179
|
+
_CACHE_DIR_NAME = "jax_compilation_cache"
|
|
180
|
+
|
|
135
181
|
cache_override = os.environ.get("DKX_COMPILATION_CACHE_DIR", "").strip()
|
|
136
182
|
if cache_override:
|
|
137
183
|
default_cache_dir = cache_override
|
|
138
184
|
else:
|
|
139
185
|
xdg_cache = os.environ.get("XDG_CACHE_HOME", "").strip()
|
|
140
186
|
if xdg_cache:
|
|
141
|
-
default_cache_dir = os.path.join(xdg_cache, "dkx",
|
|
187
|
+
default_cache_dir = os.path.join(xdg_cache, "dkx", _CACHE_DIR_NAME)
|
|
142
188
|
else:
|
|
143
|
-
default_cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "dkx",
|
|
189
|
+
default_cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "dkx", _CACHE_DIR_NAME)
|
|
144
190
|
try:
|
|
145
191
|
os.makedirs(default_cache_dir, exist_ok=True)
|
|
146
192
|
except OSError:
|
|
147
|
-
default_cache_dir = os.path.join(tempfile.gettempdir(), "dkx",
|
|
193
|
+
default_cache_dir = os.path.join(tempfile.gettempdir(), "dkx", _CACHE_DIR_NAME)
|
|
148
194
|
try:
|
|
149
195
|
os.makedirs(default_cache_dir, exist_ok=True)
|
|
150
196
|
except OSError:
|
|
@@ -153,7 +199,7 @@ if _disable_cache not in {"1", "true", "yes", "on"}:
|
|
|
153
199
|
# Some environments (CI sandboxes, read-only homes) can create the directory but
|
|
154
200
|
# cannot write compilation entries. Fall back to a tempdir cache to avoid noisy
|
|
155
201
|
# warnings and degraded cold-start performance.
|
|
156
|
-
default_cache_dir = os.path.join(tempfile.gettempdir(), "dkx",
|
|
202
|
+
default_cache_dir = os.path.join(tempfile.gettempdir(), "dkx", _CACHE_DIR_NAME)
|
|
157
203
|
try:
|
|
158
204
|
os.makedirs(default_cache_dir, exist_ok=True)
|
|
159
205
|
except OSError:
|
|
@@ -207,6 +253,20 @@ try:
|
|
|
207
253
|
)
|
|
208
254
|
except ValueError:
|
|
209
255
|
pass
|
|
256
|
+
# The cache is deliberately NOT size-capped. Setting
|
|
257
|
+
# jax_compilation_cache_max_size turns on jax's LRU, which writes a
|
|
258
|
+
# sidecar "-atime" file per entry and does size bookkeeping on every
|
|
259
|
+
# write. With the zero thresholds above -- which exist so even small
|
|
260
|
+
# kernels are cached -- that is measurably expensive: on
|
|
261
|
+
# tests/test_monoenergetic_database.py, 32 s and 1792 files capped
|
|
262
|
+
# against 20 s and 896 uncapped, a 60% penalty on every run. CI proved
|
|
263
|
+
# it at scale, nine of ten coverage shards crossing a 10-minute timeout
|
|
264
|
+
# they had been finishing in four to eight.
|
|
265
|
+
#
|
|
266
|
+
# So the cache grows without bound. That is a disk-space cost -- it
|
|
267
|
+
# reached 782 MB over 64k entries on a development machine -- and the
|
|
268
|
+
# remedy is to delete the directory, which loses nothing but compile
|
|
269
|
+
# time. Paying 60% on every run to avoid it is the worse trade.
|
|
210
270
|
except Exception:
|
|
211
271
|
# Keep import lightweight for tooling that inspects the package without JAX.
|
|
212
272
|
pass
|
|
@@ -233,6 +293,7 @@ from .inputs import SfincsInput, load_sfincs_input # noqa: E402
|
|
|
233
293
|
# Heavy flagship entry points (they import the JAX solve stack) are exported
|
|
234
294
|
# lazily via PEP 562 module __getattr__ so `import dkx` stays cheap.
|
|
235
295
|
_LAZY_EXPORTS = {
|
|
296
|
+
"plot": ("dkx.plotting", "plot"),
|
|
236
297
|
"run_profile": ("dkx.run", "run_profile"),
|
|
237
298
|
"run_transport_matrix": ("dkx.run", "run_transport_matrix"),
|
|
238
299
|
"run_from_namelist": ("dkx.run", "run_from_namelist"),
|
|
@@ -246,6 +307,8 @@ _LAZY_EXPORTS = {
|
|
|
246
307
|
|
|
247
308
|
|
|
248
309
|
def __getattr__(name: str):
|
|
310
|
+
if name == "run":
|
|
311
|
+
return _lazy_run_module()
|
|
249
312
|
try:
|
|
250
313
|
module_name, attr = _LAZY_EXPORTS[name]
|
|
251
314
|
except KeyError:
|
|
@@ -261,6 +324,20 @@ def __dir__() -> list[str]:
|
|
|
261
324
|
return sorted(set(globals()) | set(_LAZY_EXPORTS))
|
|
262
325
|
|
|
263
326
|
|
|
327
|
+
# ``dkx/run.py`` is a module and ``dkx.run(case)`` is a call. Rather than pick
|
|
328
|
+
# one -- the function breaks ``dkx.run.run_profile`` and every monkeypatch that
|
|
329
|
+
# targets it by path, the module breaks the call -- ``dkx/run.py`` makes itself
|
|
330
|
+
# callable, so ``dkx.run`` is always the module and always invocable. The name
|
|
331
|
+
# is resolved here rather than listed in _LAZY_EXPORTS because what it yields
|
|
332
|
+
# is the module itself, not an attribute of one.
|
|
333
|
+
def _lazy_run_module():
|
|
334
|
+
import importlib # noqa: PLC0415
|
|
335
|
+
|
|
336
|
+
module = importlib.import_module(f"{__name__}.run")
|
|
337
|
+
globals()["run"] = module
|
|
338
|
+
return module
|
|
339
|
+
|
|
340
|
+
|
|
264
341
|
|
|
265
342
|
def require_float64() -> None:
|
|
266
343
|
"""Raise unless JAX is in float64 mode.
|
|
@@ -309,9 +386,11 @@ __all__ = [
|
|
|
309
386
|
"run_ambipolar_brent",
|
|
310
387
|
"run_from_namelist",
|
|
311
388
|
"run_monoenergetic_database",
|
|
389
|
+
"plot",
|
|
390
|
+
"run",
|
|
312
391
|
"run_profile",
|
|
313
392
|
"run_transport_matrix",
|
|
314
393
|
"write_output",
|
|
315
394
|
]
|
|
316
395
|
|
|
317
|
-
__version__ = "2.
|
|
396
|
+
__version__ = "2.3.0"
|
|
@@ -54,6 +54,29 @@ def _add_heatmap(ax, data: dict[str, object], key: str, *, title: str | None = N
|
|
|
54
54
|
return True
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
|
|
58
|
+
def _theta_zeta_oriented(b_hat: np.ndarray, data: dict[str, object]) -> np.ndarray:
|
|
59
|
+
"""Return ``BHat`` as ``(theta, zeta)`` whatever order it was stored in.
|
|
60
|
+
|
|
61
|
+
The output file stores it ``(zeta, theta)`` (Fortran layout) while the
|
|
62
|
+
operator hands it over ``(theta, zeta)``, and ``imshow`` puts axis 0 on the
|
|
63
|
+
vertical. Labelling without checking is how every axisymmetric figure came
|
|
64
|
+
out with its variation on the axis marked zeta: the tokamak's Nzeta=1 strip
|
|
65
|
+
was drawn horizontally and called zeta when it was theta.
|
|
66
|
+
|
|
67
|
+
Orientation is decided by matching the array's dimensions against the run's
|
|
68
|
+
own ``Ntheta``/``Nzeta`` rather than by assuming an order. When the two are
|
|
69
|
+
equal there is nothing to disambiguate, and the stored layout is kept.
|
|
70
|
+
"""
|
|
71
|
+
b_hat = np.asarray(b_hat)
|
|
72
|
+
if b_hat.ndim != 2:
|
|
73
|
+
return b_hat
|
|
74
|
+
n_theta = int(np.asarray(data.get("Ntheta", 0)).reshape(-1)[0]) if "Ntheta" in data else 0
|
|
75
|
+
n_zeta = int(np.asarray(data.get("Nzeta", 0)).reshape(-1)[0]) if "Nzeta" in data else 0
|
|
76
|
+
if n_theta and n_zeta and n_theta != n_zeta and b_hat.shape == (n_zeta, n_theta):
|
|
77
|
+
return b_hat.T
|
|
78
|
+
return b_hat
|
|
79
|
+
|
|
57
80
|
def _add_profile(ax, data: dict[str, object], x: np.ndarray, key: str, *, title: str | None = None) -> bool:
|
|
58
81
|
if key not in data:
|
|
59
82
|
return False
|
|
@@ -220,8 +243,9 @@ def plot_sfincs_output_summary(
|
|
|
220
243
|
axes[0].set_ylabel("FSABFlow_vs_x")
|
|
221
244
|
axes[0].grid(True, alpha=0.25)
|
|
222
245
|
else:
|
|
223
|
-
|
|
224
|
-
|
|
246
|
+
oriented = _theta_zeta_oriented(b_hat, data)
|
|
247
|
+
theta = np.asarray(data.get("theta", np.arange(oriented.shape[0]))).ravel()
|
|
248
|
+
axes[0].plot(theta, oriented[:, 0], lw=1.8)
|
|
225
249
|
axes[0].set_title("BHat(theta, zeta=0)")
|
|
226
250
|
axes[0].set_xlabel("theta")
|
|
227
251
|
axes[0].set_ylabel("BHat")
|
|
@@ -252,15 +276,118 @@ def plot_sfincs_output_summary(
|
|
|
252
276
|
)
|
|
253
277
|
axes[1].set_title("Run summary")
|
|
254
278
|
|
|
255
|
-
|
|
256
|
-
axes[2].
|
|
257
|
-
axes[2].
|
|
258
|
-
axes[2].
|
|
259
|
-
axes[2].
|
|
260
|
-
|
|
279
|
+
oriented = _theta_zeta_oriented(b_hat, data)
|
|
280
|
+
im = axes[2].imshow(oriented, aspect="auto", origin="lower")
|
|
281
|
+
axes[2].set_title(r"$\hat B(\theta, \zeta)$")
|
|
282
|
+
axes[2].set_xlabel(r"$\zeta$ index")
|
|
283
|
+
axes[2].set_ylabel(r"$\theta$ index")
|
|
284
|
+
if zeta.size and oriented.shape[-1] == zeta.size:
|
|
285
|
+
axes[2].set_xticks([0, max(0, oriented.shape[-1] - 1)])
|
|
286
|
+
axes[2].set_xticklabels(["0", f"{float(zeta[-1]):.2f}"])
|
|
261
287
|
fig.colorbar(im, ax=axes[2], fraction=0.046, pad=0.04)
|
|
262
288
|
|
|
263
|
-
|
|
289
|
+
# y=1.03 put the title outside the figure, where constrained_layout does
|
|
290
|
+
# not reserve room for it, so it landed on the subplot titles. Let the
|
|
291
|
+
# layout engine place it.
|
|
292
|
+
fig.suptitle(f"SFINCS output summary: {Path(input_h5).name}")
|
|
264
293
|
fig.savefig(output_png, dpi=200, bbox_inches="tight")
|
|
265
294
|
plt.close(fig)
|
|
266
295
|
return output_png.resolve()
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _shown(written: Path) -> Path:
|
|
300
|
+
"""Display an already-written figure, for IDE and notebook users.
|
|
301
|
+
|
|
302
|
+
The panel builders save and close their own figure, so there is nothing
|
|
303
|
+
left to ``plt.show()``. Re-reading the PNG and showing it is what puts it
|
|
304
|
+
in Spyder's plots pane, and it costs one imread.
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
image = plt.imread(written)
|
|
308
|
+
except Exception: # pragma: no cover - unreadable/exotic format
|
|
309
|
+
return written
|
|
310
|
+
figure = plt.figure(figsize=(13.5, 8.0))
|
|
311
|
+
axis = figure.add_axes((0.0, 0.0, 1.0, 1.0))
|
|
312
|
+
axis.imshow(image)
|
|
313
|
+
axis.axis("off")
|
|
314
|
+
# block=False so a script run from a terminal is not held open by a
|
|
315
|
+
# window nobody is there to close; Spyder and Jupyter render regardless.
|
|
316
|
+
plt.show(block=False)
|
|
317
|
+
return written
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def plot(source, out="dkx_panels.png", *, style="panels", show=False):
|
|
321
|
+
"""Plot a run or an output file. One call, one figure, returns its path.
|
|
322
|
+
|
|
323
|
+
``source`` is whatever you already have:
|
|
324
|
+
|
|
325
|
+
- the object :func:`dkx.run` returned, when the run was given ``out=``;
|
|
326
|
+
- a path to an ``sfincsOutput`` file, DKX's or Fortran SFINCS's --- the
|
|
327
|
+
layout is the same, so both work;
|
|
328
|
+
- a directory containing one.
|
|
329
|
+
|
|
330
|
+
The figure is the same six-panel one ``dkx --plot`` writes: the
|
|
331
|
+
monoenergetic coefficients, ``|B|`` on the surface, the bootstrap current
|
|
332
|
+
and the species fluxes. ``out`` picks the format by suffix.
|
|
333
|
+
|
|
334
|
+
``style="summary"`` selects the compact three-panel page instead. Only
|
|
335
|
+
that style expands with a ``.pdf`` suffix, into a four-page diagnostics
|
|
336
|
+
book; ``style="panels"`` writes its one figure whatever the suffix.
|
|
337
|
+
|
|
338
|
+
For a figure this does not draw, read the numbers off ``run.moments`` and
|
|
339
|
+
use matplotlib directly --- ``examples/1_basics/plot_custom.py`` shows
|
|
340
|
+
that, and it is not a fallback so much as the normal way to make a figure
|
|
341
|
+
for a paper.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
source: a run object, an output-file path, or a directory.
|
|
345
|
+
out: destination path; the suffix selects png or pdf. A ``.pdf``
|
|
346
|
+
is multi-page only under ``style="summary"``.
|
|
347
|
+
style: ``"panels"`` (default) for the ``dkx --plot`` figure, or
|
|
348
|
+
``"summary"`` for the compact three-panel page.
|
|
349
|
+
show: also display the figure. For Spyder, Jupyter or any IDE where
|
|
350
|
+
you want it in the plots pane rather than only on disk; a no-op
|
|
351
|
+
under a headless backend.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
The :class:`~pathlib.Path` actually written.
|
|
355
|
+
"""
|
|
356
|
+
out = Path(out)
|
|
357
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
358
|
+
|
|
359
|
+
path = getattr(source, "output_path", None) or (
|
|
360
|
+
source if isinstance(source, (str, Path)) else None)
|
|
361
|
+
if path is not None:
|
|
362
|
+
path = Path(path)
|
|
363
|
+
if path.is_dir():
|
|
364
|
+
candidates = sorted(
|
|
365
|
+
p for p in path.iterdir()
|
|
366
|
+
if p.suffix in {".h5", ".nc", ".npz"} and "sfincsOutput" in p.name
|
|
367
|
+
)
|
|
368
|
+
if not candidates:
|
|
369
|
+
raise FileNotFoundError(f"no sfincsOutput file in {path}")
|
|
370
|
+
path = candidates[0]
|
|
371
|
+
if style == "summary":
|
|
372
|
+
written = plot_sfincs_output_summary(input_h5=path, output_png=out)
|
|
373
|
+
return _shown(written) if show else written
|
|
374
|
+
if style != "panels":
|
|
375
|
+
raise ValueError(f"style must be 'panels' or 'summary'; got {style!r}")
|
|
376
|
+
from dkx.representative import plot_output_file # noqa: PLC0415
|
|
377
|
+
|
|
378
|
+
written = plot_output_file(path, out)
|
|
379
|
+
return _shown(written) if show else written
|
|
380
|
+
|
|
381
|
+
if hasattr(source, "moments"):
|
|
382
|
+
raise ValueError(
|
|
383
|
+
"this run has no output file to plot: pass out=... to dkx.run so the "
|
|
384
|
+
"solve writes one, e.g.\n"
|
|
385
|
+
' run = dkx.run(case, out="sfincsOutput.h5")\n'
|
|
386
|
+
" dkx.plot(run)\n"
|
|
387
|
+
"Plotting cannot reuse the in-memory result because the panels read "
|
|
388
|
+
"the output file's full dataset, not just the moments."
|
|
389
|
+
)
|
|
390
|
+
raise TypeError(
|
|
391
|
+
"plot() takes the object dkx.run() returned, a path to an sfincsOutput "
|
|
392
|
+
f"file, or a directory holding one; got {type(source).__name__}"
|
|
393
|
+
)
|