dkx 2.3.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dkx-2.3.0 → dkx-2.3.1}/PKG-INFO +1 -1
- {dkx-2.3.0 → dkx-2.3.1}/dkx/__init__.py +1 -1
- {dkx-2.3.0 → dkx-2.3.1}/dkx/solve.py +155 -7
- {dkx-2.3.0 → dkx-2.3.1}/dkx.egg-info/PKG-INFO +1 -1
- {dkx-2.3.0 → dkx-2.3.1}/dkx.egg-info/SOURCES.txt +2 -0
- {dkx-2.3.0 → dkx-2.3.1}/pyproject.toml +1 -1
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_examples_workflow_browser.py +7 -2
- dkx-2.3.1/tests/test_scan_survives_a_failing_point.py +108 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_solve.py +11 -4
- dkx-2.3.1/tests/test_tier2_stall_escalates_instead_of_crashing.py +101 -0
- {dkx-2.3.0 → dkx-2.3.1}/LICENSE +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/README.md +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/__main__.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/ambipolar.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/api.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/batch.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/bootstrap.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/bounce_averaged.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/cli.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/coarse_precond.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/collisions.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/collocation.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/compare.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/console.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/constants.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/drift_kinetic.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/er.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/impurity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/input_compat.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/inputs.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/io.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/magnetic_geometry.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/moments.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/momentum_correction.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/monoenergetic.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/multigrid.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/namelist.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/paths.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/phase_space.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/phi1.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/plotting.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/profiling.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/representative.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/run.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/sensitivity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/shaing_callen.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/solver_trace.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/sparse_precond.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/species.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/units.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/__init__.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/artifacts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/data_fetch.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/equilibria_manifest.json +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/fortran.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/lanes.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/panels.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/policy.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/release.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validation/series.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/validity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/variational.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/vmec_ascii.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/workflows/__init__.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/workflows/geometry_adapters.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/workflows/optimization.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/workflows/scans.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/writer.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx/xgrid.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx.egg-info/dependency_links.txt +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx.egg-info/entry_points.txt +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx.egg-info/requires.txt +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/dkx.egg-info/top_level.txt +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/setup.cfg +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_api_contracts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_audit_suite_output_keys.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_audit_suite_runtime_drift.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_batch.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_benchmark_artifact_policy.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_benchmark_doc_claims.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_bootstrap_objective.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_bounce_averaged.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_classical_transport_physics_gates.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_cli_solve_mode.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_cli_validation_io_fast_coverage.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_coarse_precond_constants.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_collision_physics_gates.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_collisionality_artifact.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_collisions_improved_sugama.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_collocation.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_compare_reference_corruption.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_compilation_cache_is_not_size_capped.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_create_production_benchmark_inputs.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_data_fetch.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_domain_package_import_contracts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_drift_kinetic.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_environment_guard.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_er.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_er_scan_and_ambipolar.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_er_trajectory_sweep.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_er_trajectory_sweep_artifact.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_example_docstrings_stay_short.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_example_qa_bootstrap.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_examples_bootstrap_dkx.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_examples_optimization.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_examples_pedagogic.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_examples_tree_contract.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_examples_tutorials.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_export_f_canonical_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_figure_provenance.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_finite_beta_vmec_example.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_float64_single_owner.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_fokker_planck_phi1_reduces_to_no_phi1.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_fortran_mpi_wrapper.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generate_autodiff_sensitivity_validation.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generate_fortran_suite_benchmark_summary.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generate_readme_fast_branch_audit.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generate_sfincs_paper_figs.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generate_validation_dashboard.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generate_w7x_high_nu_performance.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_generated_deck_coverage.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_geometry_scheme11_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_getting_started_examples.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_gpu_solver_path_artifacts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_h5_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_helper_module_coverage.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_impurity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_input_compat.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_input_namelist_writer.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_inputs_console.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_jax_geometry_adapters.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_kinetic_operator_fortran_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_magnetic_drift_diagonal.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_magnetic_drifts_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_magnetic_geometry.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_momentum_correction.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_monoenergetic_database.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_multigrid.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_namelist.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_comparison.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_evidence.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_ladder.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_neoclassical_objectives.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_promotion.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_public_scripts_cli.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_real_artifacts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_wave3_coverage.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_optimization_workflow.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_coordinate_physics_gates.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_formats.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_constraintscheme34_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_magdrift_schemes_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_nonstelsym_vmec_scheme5_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme11_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme13_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme1_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme2_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme4_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme4_quick2species_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_scheme5_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_h5_xgrid_schemes_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_key_coverage_regressions.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_output_npz_canonical_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_package_metadata.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_paper_benchmark_bootstrap_loop.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_paper_benchmark_electron_root.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_paper_benchmark_monoenergetic.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_paper_benchmark_w7x_ambipolar.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_phase_space.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_phase_timing.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_phi1.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_plot_pdf_paging_is_documented_correctly.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_plotting.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_preconditioner_xi_semantics.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_profile_write_output_trace.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_profiling.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_profiling_helpers.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_public_docs_wording_contract.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_public_facades_and_paths.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_readme_quickstart_runs.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_reference_goldens_lightweight.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_release_gate_metadata.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_remaining_public_contracts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_repo_size_policy.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_representative.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_representative_run_works_when_installed.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_research_lane_policy.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_rhsmode1_current_closure.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_rhsmode1_phi1_write_output_end_to_end.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_rhsmode1_write_output_end_to_end.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_rosenbluth_quadrature.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_run_function_is_not_shadowed_by_run_module.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_run_rhsmode1.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_run_transport.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_runtime_helper_coverage.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_scans_progress_and_recycle.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_sensitivity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_shaing_callen.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_solver_path_artifacts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_solver_trace.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_solver_trace_canonical_parity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_source_tree_consolidation.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_sparse_precond.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_species_constants.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_tier1_refinement.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_tier2_memory_guard.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_transport_matrix_write_output_end_to_end.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_units.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_upstream_scanplot2_smoke.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_utils_smoke.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_artifacts.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_deferred_lane_gates.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_figures.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_manifest_schema.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_math.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_petsc_and_upstream_helpers.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validation_policy_coverage.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_validity.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_variational_bounds.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_vmec_wout_ascii.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_vmec_wout_conventions.py +0 -0
- {dkx-2.3.0 → dkx-2.3.1}/tests/test_vmex_workflow.py +0 -0
{dkx-2.3.0 → dkx-2.3.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dkx
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
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/
|
|
@@ -976,6 +976,144 @@ def materialize_dense(
|
|
|
976
976
|
cols.append(np.asarray(batched(basis)))
|
|
977
977
|
return np.concatenate(cols, axis=1)
|
|
978
978
|
|
|
979
|
+
|
|
980
|
+
def _escalate_after_tier2_stall(
|
|
981
|
+
op: KineticOperator,
|
|
982
|
+
rhs2d: jnp.ndarray,
|
|
983
|
+
*,
|
|
984
|
+
stalled: SolveResult,
|
|
985
|
+
tol: float,
|
|
986
|
+
atol: float,
|
|
987
|
+
x0,
|
|
988
|
+
recycle,
|
|
989
|
+
preconditioner: str,
|
|
990
|
+
drop_l_coupling_in_precond: bool,
|
|
991
|
+
restart: int,
|
|
992
|
+
recycle_dim: int,
|
|
993
|
+
max_restarts: int,
|
|
994
|
+
check_adjoint: bool,
|
|
995
|
+
adjoint_residual_factor: float,
|
|
996
|
+
max_dense_size: int,
|
|
997
|
+
) -> SolveResult:
|
|
998
|
+
"""Work through the remedies a user would otherwise have to know about.
|
|
999
|
+
|
|
1000
|
+
A stalled Krylov solve is a *preconditioner* problem, not a reason to
|
|
1001
|
+
change solver tier, and this is where DKX used to get it backwards: it
|
|
1002
|
+
announced a fall back to the tier-3 host direct solve, which obtains its
|
|
1003
|
+
matrix by applying the operator to ``n`` identity columns. At the sizes
|
|
1004
|
+
where tier 2 actually stalls that is hopeless -- a 66004-DOF deck needs
|
|
1005
|
+
66004 matvecs and O(n^2) memory -- so the guard in :func:`_solve_tier3`
|
|
1006
|
+
refused, and a convergence problem surfaced as a hard crash telling the
|
|
1007
|
+
user to raise ``max_dense_size``. Taking that advice would have tried to
|
|
1008
|
+
allocate 32.5 GB.
|
|
1009
|
+
|
|
1010
|
+
SFINCS Fortran v3 does not have this failure mode, and the reason is worth
|
|
1011
|
+
stating because it dictates the order below. It assembles the simplified
|
|
1012
|
+
preconditioner matrix *analytically and sparsely*, factorizes it with a
|
|
1013
|
+
sparse direct LU (MUMPS or SuperLU_dist), and preconditions GMRES with
|
|
1014
|
+
that -- so its "direct solve" is a sparse factorization that handles 66004
|
|
1015
|
+
routinely, and GMRES needs few iterations on top of it. It also retries
|
|
1016
|
+
automatically, doubling the MUMPS working-memory factor on a failed
|
|
1017
|
+
factorization (``solver.F90``: ``mumps_icntl_14 = mumps_icntl_14 * 2``).
|
|
1018
|
+
|
|
1019
|
+
DKX already owns the equivalent of that preconditioner -- ``"sparse"``
|
|
1020
|
+
eliminates in a fill-reducing order and keeps the inverse exact -- and the
|
|
1021
|
+
stall above happened without ever trying it. So the ladder escalates the
|
|
1022
|
+
preconditioner first, in increasing cost, and only considers tier 3 at a
|
|
1023
|
+
size where tier 3 can actually run.
|
|
1024
|
+
|
|
1025
|
+
Returns the first converged result, or the best (lowest final residual) if
|
|
1026
|
+
none converges, so a caller that can tolerate a loose solve still gets the
|
|
1027
|
+
best available answer along with an honest ``converged=False``.
|
|
1028
|
+
"""
|
|
1029
|
+
attempts: list[tuple[str, SolveResult]] = [(f"{preconditioner} preconditioner", stalled)]
|
|
1030
|
+
|
|
1031
|
+
def _residual(result: SolveResult) -> float:
|
|
1032
|
+
norms = np.asarray(result.residual_norms)
|
|
1033
|
+
return float(norms[-1]) if norms.size else float("inf")
|
|
1034
|
+
|
|
1035
|
+
print(
|
|
1036
|
+
f"[dkx.solve] tier-2 Krylov stalled with the {preconditioner} "
|
|
1037
|
+
f"preconditioner (iterations={stalled.iterations}, residual="
|
|
1038
|
+
f"{_residual(stalled):.3e}); escalating rather than giving up."
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
# Rung 1: the strong preconditioner. This is the SFINCS analogue and the
|
|
1042
|
+
# single most likely fix, because "coarse" eliminates L first, which fills
|
|
1043
|
+
# the angular stencils in, while "sparse" eliminates in a fill-reducing
|
|
1044
|
+
# order and stays sparse.
|
|
1045
|
+
for kind in ("sparse", "multigrid"):
|
|
1046
|
+
if kind == preconditioner:
|
|
1047
|
+
continue
|
|
1048
|
+
try:
|
|
1049
|
+
print(f"[dkx.solve] retrying tier 2 with the {kind} preconditioner ...")
|
|
1050
|
+
candidate = _solve_tier2(
|
|
1051
|
+
op, rhs2d, tol=tol, atol=atol, x0=x0, recycle=recycle,
|
|
1052
|
+
preconditioner=kind,
|
|
1053
|
+
drop_l_coupling_in_precond=drop_l_coupling_in_precond,
|
|
1054
|
+
restart=restart, recycle_dim=recycle_dim,
|
|
1055
|
+
max_restarts=max_restarts, differentiable=False,
|
|
1056
|
+
check_adjoint=check_adjoint,
|
|
1057
|
+
adjoint_residual_factor=adjoint_residual_factor,
|
|
1058
|
+
)
|
|
1059
|
+
except Exception as exc: # a preconditioner that cannot build is a rung, not a failure
|
|
1060
|
+
print(f"[dkx.solve] {kind} preconditioner unavailable: {exc}")
|
|
1061
|
+
continue
|
|
1062
|
+
attempts.append((f"{kind} preconditioner", candidate))
|
|
1063
|
+
if candidate.converged:
|
|
1064
|
+
print(f"[dkx.solve] converged with the {kind} preconditioner.")
|
|
1065
|
+
return candidate
|
|
1066
|
+
|
|
1067
|
+
# Rung 2: more iterations. Cheap to ask for, and the remedy when the
|
|
1068
|
+
# preconditioner is adequate but the cap was simply too low for this Er.
|
|
1069
|
+
widened = max(max_restarts * 4, max_restarts + 1)
|
|
1070
|
+
best_kind = max(attempts, key=lambda item: -_residual(item[1]))[0].split()[0]
|
|
1071
|
+
if best_kind not in _TIER2_PRECONDITIONERS:
|
|
1072
|
+
best_kind = preconditioner
|
|
1073
|
+
try:
|
|
1074
|
+
print(
|
|
1075
|
+
f"[dkx.solve] retrying tier 2 with the {best_kind} preconditioner "
|
|
1076
|
+
f"and {widened} restarts (was {max_restarts}) ..."
|
|
1077
|
+
)
|
|
1078
|
+
candidate = _solve_tier2(
|
|
1079
|
+
op, rhs2d, tol=tol, atol=atol, x0=x0, recycle=recycle,
|
|
1080
|
+
preconditioner=best_kind,
|
|
1081
|
+
drop_l_coupling_in_precond=drop_l_coupling_in_precond,
|
|
1082
|
+
restart=restart, recycle_dim=recycle_dim,
|
|
1083
|
+
max_restarts=widened, differentiable=False,
|
|
1084
|
+
check_adjoint=check_adjoint,
|
|
1085
|
+
adjoint_residual_factor=adjoint_residual_factor,
|
|
1086
|
+
)
|
|
1087
|
+
attempts.append((f"{best_kind} preconditioner, {widened} restarts", candidate))
|
|
1088
|
+
if candidate.converged:
|
|
1089
|
+
print("[dkx.solve] converged with the widened iteration budget.")
|
|
1090
|
+
return candidate
|
|
1091
|
+
except Exception as exc:
|
|
1092
|
+
print(f"[dkx.solve] widened retry failed: {exc}")
|
|
1093
|
+
|
|
1094
|
+
# Rung 3: tier 3, but only where it can actually run. Announcing a
|
|
1095
|
+
# fallback that the size guard will refuse is what produced the original
|
|
1096
|
+
# crash, so the size is checked here rather than discovered downstream.
|
|
1097
|
+
if op.total_size <= max_dense_size:
|
|
1098
|
+
print("[dkx.solve] falling back to the tier-3 host direct solve ...")
|
|
1099
|
+
return _solve_tier3(op, rhs2d, tol=tol, atol=atol, max_dense_size=max_dense_size)
|
|
1100
|
+
|
|
1101
|
+
best_label, best = min(attempts, key=lambda item: _residual(item[1]))
|
|
1102
|
+
dense_gb = op.total_size**2 * 8 / 1024**3
|
|
1103
|
+
raise RuntimeError(
|
|
1104
|
+
f"the linear solve did not converge at total_size={op.total_size}.\n"
|
|
1105
|
+
f"Tried: {'; '.join(label for label, _ in attempts)}.\n"
|
|
1106
|
+
f"Best final residual {_residual(best):.3e} ({best_label}), tolerance {tol:.1e}.\n"
|
|
1107
|
+
"Tier 3 was not attempted: it materializes the operator column by "
|
|
1108
|
+
f"column, which at this size needs {op.total_size} matvecs and "
|
|
1109
|
+
f"{dense_gb:.1f} GB, so raising max_dense_size would not help.\n"
|
|
1110
|
+
"What usually does help, in order: lower the collisionality-scaled Er "
|
|
1111
|
+
"(stalls cluster at the largest |Er|, where the ExB term dominates); "
|
|
1112
|
+
"raise solverTolerance; or reduce Nxi/Ntheta/Nzeta at the offending "
|
|
1113
|
+
"point. Pass solver=SolverOptions(...) to control this directly."
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
|
|
979
1117
|
def _solve_tier3(
|
|
980
1118
|
op: KineticOperator, rhs2d: jnp.ndarray, *, tol: float, atol: float, max_dense_size: int
|
|
981
1119
|
) -> SolveResult:
|
|
@@ -2085,13 +2223,23 @@ def solve(
|
|
|
2085
2223
|
adjoint_residual_factor=adjoint_residual_factor,
|
|
2086
2224
|
)
|
|
2087
2225
|
if method == "auto" and not result.converged and not differentiable:
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2226
|
+
requested = _resolve_preconditioner(preconditioner, use_preconditioner)
|
|
2227
|
+
result = _escalate_after_tier2_stall(
|
|
2228
|
+
op,
|
|
2229
|
+
rhs2d,
|
|
2230
|
+
stalled=result,
|
|
2231
|
+
tol=tol,
|
|
2232
|
+
atol=atol,
|
|
2233
|
+
x0=x0,
|
|
2234
|
+
recycle=recycle,
|
|
2235
|
+
preconditioner=requested,
|
|
2236
|
+
drop_l_coupling_in_precond=drop_l_coupling_in_precond,
|
|
2237
|
+
restart=restart,
|
|
2238
|
+
recycle_dim=recycle_dim,
|
|
2239
|
+
max_restarts=max_restarts,
|
|
2240
|
+
check_adjoint=check_adjoint,
|
|
2241
|
+
adjoint_residual_factor=adjoint_residual_factor,
|
|
2242
|
+
max_dense_size=max_dense_size,
|
|
2095
2243
|
)
|
|
2096
2244
|
else: # direct
|
|
2097
2245
|
if differentiable:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dkx
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
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/
|
|
@@ -188,6 +188,7 @@ tests/test_run_function_is_not_shadowed_by_run_module.py
|
|
|
188
188
|
tests/test_run_rhsmode1.py
|
|
189
189
|
tests/test_run_transport.py
|
|
190
190
|
tests/test_runtime_helper_coverage.py
|
|
191
|
+
tests/test_scan_survives_a_failing_point.py
|
|
191
192
|
tests/test_scans_progress_and_recycle.py
|
|
192
193
|
tests/test_sensitivity.py
|
|
193
194
|
tests/test_shaing_callen.py
|
|
@@ -200,6 +201,7 @@ tests/test_sparse_precond.py
|
|
|
200
201
|
tests/test_species_constants.py
|
|
201
202
|
tests/test_tier1_refinement.py
|
|
202
203
|
tests/test_tier2_memory_guard.py
|
|
204
|
+
tests/test_tier2_stall_escalates_instead_of_crashing.py
|
|
203
205
|
tests/test_transport_matrix_write_output_end_to_end.py
|
|
204
206
|
tests/test_units.py
|
|
205
207
|
tests/test_upstream_scanplot2_smoke.py
|
|
@@ -30,8 +30,13 @@ def test_workflow_browser_filters_by_physics_topic() -> None:
|
|
|
30
30
|
vmec_geometry = module._matching_workflows(catalog, topic="vmec", search="geometry")
|
|
31
31
|
assert {workflow["id"] for workflow in vmec_geometry} >= {"vmec_wout_path", "vmec_boozer_jax_pipeline"}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# There is no "gpu" topic any more: its only workflow was the
|
|
34
|
+
# output-format benchmark, which moved to tools/performance/ along with the
|
|
35
|
+
# rest of the maintainer tooling. The catalog indexes examples, and that
|
|
36
|
+
# script stopped being one.
|
|
37
|
+
transport = module._matching_workflows(catalog, topic="transport", search="")
|
|
38
|
+
assert {workflow["id"] for workflow in transport} >= {"transport_matrix"}
|
|
39
|
+
assert module._matching_workflows(catalog, topic="gpu", search="") == []
|
|
35
40
|
|
|
36
41
|
|
|
37
42
|
def test_workflow_browser_json_cli_is_machine_readable() -> None:
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""One unsolvable scan point must not discard the rest of the scan.
|
|
2
|
+
|
|
3
|
+
Reported from a scanType=5 run: tier-2 stalled at the largest |Er| on a
|
|
4
|
+
66004-DOF deck, the exception propagated out of ``run_dkx``, and every
|
|
5
|
+
remaining Er point at that radius was lost. One radius folder finished with
|
|
6
|
+
zero of a hundred outputs, another with three. The loss was silent, and it
|
|
7
|
+
also stranded the process inside the failed run's directory, because the
|
|
8
|
+
scan loops ``chdir`` back only *after* the solve returns.
|
|
9
|
+
|
|
10
|
+
A point that will not solve is information, not a reason to throw away the
|
|
11
|
+
other ninety-nine.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import importlib.util
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
UTILS = Path(__file__).resolve().parents[1] / "examples" / "sfincs_examples" / "utils"
|
|
21
|
+
SCAN_SCRIPTS = ("sfincsScan_1", "sfincsScan_2", "sfincsScan_3",
|
|
22
|
+
"sfincsScan_4", "sfincsScan_21", "sfincsScan_22")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_common(tmp_path, monkeypatch):
|
|
26
|
+
"""Exec sfincsScan_common the way sfincsScan does, in its own namespace."""
|
|
27
|
+
monkeypatch.syspath_prepend(str(UTILS))
|
|
28
|
+
namespace: dict = {"__file__": str(UTILS / "sfincsScan_common")}
|
|
29
|
+
exec((UTILS / "sfincsScan_common").read_text(), namespace) # noqa: S102
|
|
30
|
+
return namespace
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_a_failing_point_is_recorded_and_the_scan_continues(tmp_path, monkeypatch):
|
|
34
|
+
ns = _load_common(tmp_path, monkeypatch)
|
|
35
|
+
run_scan_point = ns["run_scan_point"]
|
|
36
|
+
|
|
37
|
+
# Stand in for dkx_driver: fail only the third point, as a stalled solve
|
|
38
|
+
# would, and succeed on the rest.
|
|
39
|
+
calls = []
|
|
40
|
+
|
|
41
|
+
class _FakeDriver:
|
|
42
|
+
@staticmethod
|
|
43
|
+
def run_dkx(*, input_namelist, output_path, **kwargs):
|
|
44
|
+
calls.append(Path(output_path).parent.name)
|
|
45
|
+
if Path(output_path).parent.name == "Er3":
|
|
46
|
+
raise RuntimeError("the linear solve did not converge at total_size=66004")
|
|
47
|
+
Path(output_path).write_text("ok")
|
|
48
|
+
|
|
49
|
+
monkeypatch.setitem(sys.modules, "dkx_driver", _FakeDriver)
|
|
50
|
+
|
|
51
|
+
ok = []
|
|
52
|
+
for i in range(5):
|
|
53
|
+
directory = tmp_path / f"Er{i + 1}"
|
|
54
|
+
directory.mkdir()
|
|
55
|
+
(directory / "input.namelist").write_text("&general\n/\n")
|
|
56
|
+
ok.append(
|
|
57
|
+
run_scan_point(
|
|
58
|
+
input_namelist=directory / "input.namelist",
|
|
59
|
+
output_path=directory / "sfincsOutput.h5",
|
|
60
|
+
directory=directory.name,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Every point was attempted, not just the ones before the failure.
|
|
65
|
+
assert len(calls) == 5
|
|
66
|
+
assert ok == [True, True, False, True, True]
|
|
67
|
+
|
|
68
|
+
# The four that solved have output; the one that did not is marked.
|
|
69
|
+
assert (tmp_path / "Er4" / "sfincsOutput.h5").is_file()
|
|
70
|
+
assert not (tmp_path / "Er3" / "sfincsOutput.h5").exists()
|
|
71
|
+
marker = tmp_path / "Er3" / ns["FAILURE_MARKER"]
|
|
72
|
+
assert marker.is_file()
|
|
73
|
+
assert "did not converge" in marker.read_text()
|
|
74
|
+
|
|
75
|
+
assert ns["report_scan_failures"]() == 1
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_keyboard_interrupt_still_stops_the_scan(tmp_path, monkeypatch):
|
|
79
|
+
"""Catching solver failures must not swallow the user pressing Ctrl-C."""
|
|
80
|
+
ns = _load_common(tmp_path, monkeypatch)
|
|
81
|
+
|
|
82
|
+
class _Interrupting:
|
|
83
|
+
@staticmethod
|
|
84
|
+
def run_dkx(**kwargs):
|
|
85
|
+
raise KeyboardInterrupt
|
|
86
|
+
|
|
87
|
+
monkeypatch.setitem(sys.modules, "dkx_driver", _Interrupting)
|
|
88
|
+
directory = tmp_path / "Er1"
|
|
89
|
+
directory.mkdir()
|
|
90
|
+
with pytest.raises(KeyboardInterrupt):
|
|
91
|
+
ns["run_scan_point"](
|
|
92
|
+
input_namelist=directory / "input.namelist",
|
|
93
|
+
output_path=directory / "sfincsOutput.h5",
|
|
94
|
+
directory="Er1",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize("script", SCAN_SCRIPTS)
|
|
99
|
+
def test_every_scan_type_uses_the_resilient_helper(script: str) -> None:
|
|
100
|
+
"""A raw run_dkx call in a scan loop is the bug returning."""
|
|
101
|
+
text = (UTILS / script).read_text()
|
|
102
|
+
assert "run_scan_point(" in text, f"{script} does not use the resilient helper"
|
|
103
|
+
body = "\n".join(
|
|
104
|
+
line for line in text.splitlines() if not line.lstrip().startswith("#")
|
|
105
|
+
)
|
|
106
|
+
assert " run_dkx(" not in body and " run_dkx(" not in body, (
|
|
107
|
+
f"{script} still calls run_dkx directly, so one bad point aborts the scan"
|
|
108
|
+
)
|
|
@@ -514,16 +514,23 @@ def test_tier1_refuses_er_xdot_l2_coupling() -> None:
|
|
|
514
514
|
assert not ok
|
|
515
515
|
|
|
516
516
|
|
|
517
|
-
def
|
|
517
|
+
def test_auto_policy_recovers_a_starved_tier2_solve() -> None:
|
|
518
518
|
# Starve tier 2 (no preconditioner, tiny restart budget) on the FP
|
|
519
|
-
# fixture: the auto policy must
|
|
520
|
-
#
|
|
519
|
+
# fixture: the auto policy must recover and still return the right answer.
|
|
520
|
+
#
|
|
521
|
+
# This used to assert result.method == "direct", because a cap breach fell
|
|
522
|
+
# straight to the tier-3 host solve. That fallback is a dead end at any
|
|
523
|
+
# real size -- tier 3 materializes the operator column by column, so a
|
|
524
|
+
# 66004-DOF deck would need 66004 matvecs -- and a user hit exactly that,
|
|
525
|
+
# losing a whole radius of an Er scan to the crash. A stalled Krylov solve
|
|
526
|
+
# is a preconditioner problem, so the policy now escalates the
|
|
527
|
+
# preconditioner first and only reaches tier 3 where tier 3 can run. What
|
|
528
|
+
# matters is that a starved solve still lands on the correct answer.
|
|
521
529
|
op = _load_op("quick_2species_FPCollisions_noEr")
|
|
522
530
|
rhs = op.rhs()
|
|
523
531
|
result = solve(
|
|
524
532
|
op, rhs, method="auto", tol=1e-10, use_preconditioner=False, max_restarts=2
|
|
525
533
|
)
|
|
526
|
-
assert result.method == "direct"
|
|
527
534
|
assert result.converged
|
|
528
535
|
x_ref = _dense_solve(op, np.asarray(rhs)[:, None])[:, 0]
|
|
529
536
|
assert _rel_err(np.asarray(result.x), x_ref) < 1e-8
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""A stalled Krylov solve must escalate, not crash on a fallback that cannot run.
|
|
2
|
+
|
|
3
|
+
Reported from a scanType=5 run (Er scan inside a radius scan) on a 66004-DOF
|
|
4
|
+
deck. Tier-2 GCROT hit its iteration cap at the largest |Er|, DKX announced a
|
|
5
|
+
fall back to the tier-3 host direct solve, and tier 3 refused immediately
|
|
6
|
+
because 66004 > max_dense_size=8192. The RuntimeError propagated out through
|
|
7
|
+
the scan driver and killed every remaining Er point at that radius: one radius
|
|
8
|
+
folder finished with zero outputs, another with three of a hundred.
|
|
9
|
+
|
|
10
|
+
Tier 3 obtains its matrix by applying the operator to n identity columns, so
|
|
11
|
+
at the sizes where tier 2 actually stalls it can never run -- the advice in the
|
|
12
|
+
old message, to raise max_dense_size, would have asked for 32.5 GB. A stalled
|
|
13
|
+
Krylov solve is a preconditioner problem, and DKX already ships the strong
|
|
14
|
+
preconditioner (``sparse``) that SFINCS's MUMPS LU is the analogue of; it was
|
|
15
|
+
simply never tried.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
import dkx
|
|
22
|
+
from dkx.solve import SolveResult, _escalate_after_tier2_stall
|
|
23
|
+
|
|
24
|
+
CASE = dict(
|
|
25
|
+
geometryScheme=1, inputRadialCoordinate=3, rN_wish=0.3,
|
|
26
|
+
B0OverBBar=1.0, epsilon_t=-0.07, epsilon_h=0.0, iota=0.4542,
|
|
27
|
+
GHat=3.7481, IHat=0.0, psiAHat=0.15596, aHat=0.5585,
|
|
28
|
+
Zs=[1.0], mHats=[1.0], nHats=[1.0], THats=[1.0],
|
|
29
|
+
dNHatdrHats=[-0.5], dTHatdrHats=[-1.0],
|
|
30
|
+
Ntheta=9, Nzeta=1, Nxi=8, NL=4, Nx=4,
|
|
31
|
+
collisionOperator=1, Delta=4.5694e-3, alpha=1.0, nu_n=0.01,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _operator():
|
|
36
|
+
return dkx.run(**CASE).operator
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _stalled(op) -> SolveResult:
|
|
40
|
+
"""What tier 2 hands back when it breaches its cap."""
|
|
41
|
+
return SolveResult(
|
|
42
|
+
x=np.zeros((op.total_size, 1)),
|
|
43
|
+
method="gmres",
|
|
44
|
+
iterations=6000,
|
|
45
|
+
residual_norms=np.asarray([1.0, 0.5, 0.2]),
|
|
46
|
+
converged=False,
|
|
47
|
+
recycle=None,
|
|
48
|
+
timings={},
|
|
49
|
+
adjoint=None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _rhs2d(op):
|
|
54
|
+
"""Tier 2 works in (n, nrhs); op.rhs() is (n,), as solve() reshapes it."""
|
|
55
|
+
return np.asarray(op.rhs()).reshape(op.total_size, -1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _escalate(op, **overrides):
|
|
59
|
+
kwargs = dict(
|
|
60
|
+
stalled=_stalled(op), tol=1e-10, atol=0.0, x0=None, recycle=None,
|
|
61
|
+
preconditioner="coarse", drop_l_coupling_in_precond=False,
|
|
62
|
+
restart=20, recycle_dim=8, max_restarts=200,
|
|
63
|
+
check_adjoint=False, adjoint_residual_factor=1e3,
|
|
64
|
+
max_dense_size=8192,
|
|
65
|
+
)
|
|
66
|
+
kwargs.update(overrides)
|
|
67
|
+
return _escalate_after_tier2_stall(op, _rhs2d(op), **kwargs)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_escalation_recovers_a_stalled_solve() -> None:
|
|
71
|
+
"""The ladder must actually produce a converged answer, not just try."""
|
|
72
|
+
op = _operator()
|
|
73
|
+
result = _escalate(op)
|
|
74
|
+
assert result.converged, "escalation should recover a solvable system"
|
|
75
|
+
assert float(np.asarray(result.residual_norms)[-1]) < 1e-8
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_a_deck_too_large_for_tier3_reports_the_real_problem() -> None:
|
|
79
|
+
"""The failure the user hit: n > max_dense_size, so tier 3 cannot help.
|
|
80
|
+
|
|
81
|
+
The message must not send them to max_dense_size, and must name what was
|
|
82
|
+
tried and what actually helps.
|
|
83
|
+
"""
|
|
84
|
+
op = _operator()
|
|
85
|
+
with pytest.raises(RuntimeError) as excinfo:
|
|
86
|
+
# Force every rung to fail by demanding a tolerance nothing can meet,
|
|
87
|
+
# with tier 3 excluded exactly as it is at 66004 DOFs.
|
|
88
|
+
_escalate(op, tol=1e-300, max_dense_size=1)
|
|
89
|
+
message = str(excinfo.value)
|
|
90
|
+
assert "did not converge" in message
|
|
91
|
+
assert "Tried:" in message
|
|
92
|
+
assert "raising max_dense_size would not help" in message
|
|
93
|
+
assert "Er" in message, "the remedy that matters should name the Er dependence"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_the_old_misleading_advice_is_gone() -> None:
|
|
97
|
+
"""`raise max_dense_size explicitly` at 66004 DOFs asks for 32.5 GB."""
|
|
98
|
+
op = _operator()
|
|
99
|
+
with pytest.raises(RuntimeError) as excinfo:
|
|
100
|
+
_escalate(op, tol=1e-300, max_dense_size=1)
|
|
101
|
+
assert "raise max_dense_size explicitly if you really want this" not in str(excinfo.value)
|
{dkx-2.3.0 → dkx-2.3.1}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|