diff --git a/src/simsopt/field/tracing.py b/src/simsopt/field/tracing.py index a30a76c06..0896d37ca 100644 --- a/src/simsopt/field/tracing.py +++ b/src/simsopt/field/tracing.py @@ -16,7 +16,9 @@ logger = logging.getLogger(__name__) __all__ = ['SurfaceClassifier', 'LevelsetStoppingCriterion', - 'ToroidalFluxStoppingCriterion','RStoppingCriterion','ZStoppingCriterion', + 'MinToroidalFluxStoppingCriterion','MaxToroidalFluxStoppingCriterion', + 'MinRStoppingCriterion','MinZStoppingCriterion', + 'MaxRStoppingCriterion','MaxZStoppingCriterion', 'IterationStoppingCriterion', 'ToroidalTransitStoppingCriterion', 'compute_fieldlines', 'compute_resonances', 'compute_poloidal_transits', 'compute_toroidal_transits', @@ -738,29 +740,41 @@ def __init__(self, classifier): else: sopp.LevelsetStoppingCriterion.__init__(self, classifier) -class ToroidalFluxStoppingCriterion(sopp.ToroidalFluxStoppingCriterion): +class MinToroidalFluxStoppingCriterion(sopp.MinToroidalFluxStoppingCriterion): """ - Stop the iteration once a particle falls above or below a critical value of + Stop the iteration once a particle falls below a critical value of ``s``, the normalized toroidal flux. This :class:`StoppingCriterion` is - important to use when tracing particles in flux coordinates. For example, - the poloidal angle becomes ill-defined at the magnetic axis. This should - only be used when tracing trajectories in a flux coordinate system (i.e., - :class:`trace_particles_boozer`). + important to use when tracing particles in flux coordinates, as the poloidal + angle becomes ill-defined at the magnetic axis. This should only be used + when tracing trajectories in a flux coordinate system (i.e., :class:`trace_particles_boozer`). Usage: .. code-block:: - stopping_criteria=[ToroidalFluxStopingCriterion(crit_s,min_bool)] + stopping_criteria=[MinToroidalFluxStopingCriterion(s)] - where ``crit_s`` is the value of the critical normalized toroidal flux and - ``min_bool'' is a boolean indicating whether to stop when - ``s'' is less than the critical value (``true'') or - greater than the critical value (``false''). + where ``s`` is the value of the minimum normalized toroidal flux. """ pass +class MaxToroidalFluxStoppingCriterion(sopp.MaxToroidalFluxStoppingCriterion): + """ + Stop the iteration once a particle falls above a critical value of + ``s``, the normalized toroidal flux. This should only be used when tracing + trajectories in a flux coordinate system (i.e., :class:`trace_particles_boozer`). + + Usage: + + .. code-block:: + + stopping_criteria=[MaxToroidalFluxStopingCriterion(s)] + + where ``s`` is the value of the maximum normalized toroidal flux. + """ + pass + class ToroidalTransitStoppingCriterion(sopp.ToroidalTransitStoppingCriterion): """ Stop the iteration once the maximum number of toroidal transits is reached. @@ -783,39 +797,63 @@ class IterationStoppingCriterion(sopp.IterationStoppingCriterion): """ pass -class RStoppingCriterion(sopp.RStoppingCriterion): +class MinRStoppingCriterion(sopp.MinRStoppingCriterion): + """ + Stop the iteration once a particle falls below a critical value of + ``R``, the radial cylindrical coordinate. + + Usage: + + .. code-block:: + + stopping_criteria=[MinRStopingCriterion(crit_r)] + + where ``crit_r`` is the value of the critical coordinate. + """ + pass + +class MinZStoppingCriterion(sopp.MinZStoppingCriterion): + """ + Stop the iteration once a particle falls below a critical value of + ``Z``, the cylindrical vertical coordinate. + + Usage: + + .. code-block:: + + stopping_criteria=[MinZStopingCriterion(crit_z)] + + where ``crit_z`` is the value of the critical coordinate. + """ + pass + +class MaxRStoppingCriterion(sopp.MaxRStoppingCriterion): """ - Stop the iteration once a particle falls above or below a critical value of + Stop the iteration once a particle goes above a critical value of ``R``, the radial cylindrical coordinate. Usage: .. code-block:: - stopping_criteria=[RStopingCriterion(crit_r,min_bool)] + stopping_criteria=[MaxRStopingCriterion(crit_r,min_bool)] - where ``crit_r`` is the value of the critical coordinate and - ``min_bool'' is a boolean indicating whether to stop when - ``R'' is less than the critical value (``true'') or - greater than the critical value (``false''). + where ``crit_r`` is the value of the critical coordinate. """ pass -class ZStoppingCriterion(sopp.ZStoppingCriterion): +class MaxZStoppingCriterion(sopp.MaxZStoppingCriterion): """ - Stop the iteration once a particle falls above or below a critical value of + Stop the iteration once a particle gove above or below a critical value of ``Z``, the cylindrical vertical coordinate. Usage: .. code-block:: - stopping_criteria=[ZStopingCriterion(crit_z,min_bool)] + stopping_criteria=[MaxZStopingCriterion(crit_z,min_bool)] - where ``crit_z`` is the value of the critical coordinate and - ``min_bool'' is a boolean indicating whether to stop when - ``Z'' is less than the critical value (``true'') or - greater than the critical value (``false''). + where ``crit_z`` is the value of the critical coordinate. """ pass diff --git a/src/simsoptpp/python_tracing.cpp b/src/simsoptpp/python_tracing.cpp index a3122ca57..752a36475 100644 --- a/src/simsoptpp/python_tracing.cpp +++ b/src/simsoptpp/python_tracing.cpp @@ -17,12 +17,18 @@ void init_tracing(py::module_ &m){ py::class_>(m, "StoppingCriterion"); py::class_, StoppingCriterion>(m, "IterationStoppingCriterion") .def(py::init()); - py::class_, StoppingCriterion>(m, "RStoppingCriterion") - .def(py::init()); - py::class_, StoppingCriterion>(m, "ZStoppingCriterion") - .def(py::init()); - py::class_, StoppingCriterion>(m, "ToroidalFluxStoppingCriterion") - .def(py::init()); + py::class_, StoppingCriterion>(m, "MinRStoppingCriterion") + .def(py::init()); + py::class_, StoppingCriterion>(m, "MinZStoppingCriterion") + .def(py::init()); + py::class_, StoppingCriterion>(m, "MaxRStoppingCriterion") + .def(py::init()); + py::class_, StoppingCriterion>(m, "MaxZStoppingCriterion") + .def(py::init()); + py::class_, StoppingCriterion>(m, "MaxToroidalFluxStoppingCriterion") + .def(py::init()); + py::class_, StoppingCriterion>(m, "MinToroidalFluxStoppingCriterion") + .def(py::init()); py::class_, StoppingCriterion>(m, "ToroidalTransitStoppingCriterion") .def(py::init()); py::class_, shared_ptr>, StoppingCriterion>(m, "LevelsetStoppingCriterion") diff --git a/src/simsoptpp/tracing.h b/src/simsoptpp/tracing.h index 4a0a7a6b4..c59586825 100644 --- a/src/simsoptpp/tracing.h +++ b/src/simsoptpp/tracing.h @@ -44,51 +44,63 @@ class ToroidalTransitStoppingCriterion : public StoppingCriterion { }; }; -class ToroidalFluxStoppingCriterion : public StoppingCriterion{ +class MaxToroidalFluxStoppingCriterion : public StoppingCriterion{ private: - double crit_s; - bool min_bool; + double max_s; public: - ToroidalFluxStoppingCriterion(double crit_s, bool min_bool) : crit_s(crit_s), min_bool(min_bool) {}; + MaxToroidalFluxStoppingCriterion(double max_s) : max_s(max_s) {}; bool operator()(int iter, double t, double s, double theta, double zeta) override { - if (min_bool) { - return s<=crit_s; - } else { - return s>=crit_s; - } - + return s>=max_s; + }; +}; + +class MinToroidalFluxStoppingCriterion : public StoppingCriterion{ + private: + double min_s; + public: + MinToroidalFluxStoppingCriterion(double min_s) : min_s(min_s) {}; + bool operator()(int iter, double t, double s, double theta, double zeta) override { + return s<=min_s; }; }; -class ZStoppingCriterion : public StoppingCriterion{ +class MinZStoppingCriterion : public StoppingCriterion{ private: double crit_z; - bool min_bool; public: - ZStoppingCriterion(double crit_z, bool min_bool) : crit_z(crit_z), min_bool(min_bool) {}; + MinZStoppingCriterion(double crit_z) : crit_z(crit_z) {}; bool operator()(int iter, double t, double x, double y, double z) override { - if (min_bool) { - return z<=crit_z; - } else { - return z>=crit_z; - } - + return z<=crit_z; }; }; -class RStoppingCriterion : public StoppingCriterion{ +class MaxZStoppingCriterion : public StoppingCriterion{ + private: + double crit_z; + public: + MaxZStoppingCriterion(double crit_z) : crit_z(crit_z) {}; + bool operator()(int iter, double t, double x, double y, double z) override { + return z>=crit_z; + }; +}; + +class MinRStoppingCriterion : public StoppingCriterion{ private: double crit_r; - bool min_bool; public: - RStoppingCriterion(double crit_r, bool min_bool) : crit_r(crit_r), min_bool(min_bool) {}; + MinRStoppingCriterion(double crit_r) : crit_r(crit_r) {}; bool operator()(int iter, double t, double x, double y, double z) override { - if (min_bool) { - return std::sqrt(x*x+y*y)<=crit_r; - } else { - return std::sqrt(x*x+y*y)>=crit_r; - } - + return std::sqrt(x*x+y*y)<=crit_r; + }; +}; + +class MaxRStoppingCriterion : public StoppingCriterion{ + private: + double crit_r; + public: + MaxRStoppingCriterion(double crit_r) : crit_r(crit_r) {}; + bool operator()(int iter, double t, double x, double y, double z) override { + return std::sqrt(x*x+y*y)>=crit_r; }; }; diff --git a/tests/field/test_fieldline.py b/tests/field/test_fieldline.py index 821ea0bc6..49c7f5ab3 100644 --- a/tests/field/test_fieldline.py +++ b/tests/field/test_fieldline.py @@ -3,7 +3,8 @@ import numpy as np from simsopt.field.magneticfieldclasses import ToroidalField, PoloidalField, InterpolatedField, UniformInterpolationRule -from simsopt.field.tracing import compute_fieldlines, particles_to_vtk, plot_poincare_data, RStoppingCriterion, ZStoppingCriterion +from simsopt.field.tracing import compute_fieldlines, particles_to_vtk, plot_poincare_data, \ + MinRStoppingCriterion, MinZStoppingCriterion, MaxRStoppingCriterion, MaxZStoppingCriterion from simsopt.field.biotsavart import BiotSavart from simsopt.configs.zoo import get_ncsx_data from simsopt.field.coil import coils_via_symmetries, Coil, Current @@ -145,26 +146,26 @@ def test_poincare_caryhanson(self): # Check that R/Z is less than/greater than the maximum/minimum value. Rmax = 1 res_tys, res_phi_hits = compute_fieldlines( - bs, [Rmax-0.02], [1], tmax=2000, stopping_criteria=[RStoppingCriterion(Rmax,False)]) + bs, [Rmax-0.02], [1], tmax=2000, stopping_criteria=[MaxRStoppingCriterion(Rmax)]) assert res_phi_hits[0][0,1] == -1 assert np.all(np.sqrt(res_tys[0][:, 1]**2 + res_tys[0][:, 2]**2) < Rmax) Rmin = 0.3 res_tys, res_phi_hits = compute_fieldlines( - bs, [Rmin+0.02], [0.3], tmax=500, stopping_criteria=[RStoppingCriterion(Rmin,True)]) + bs, [Rmin+0.02], [0.3], tmax=500, stopping_criteria=[MinRStoppingCriterion(Rmin)]) assert res_phi_hits[0][0,1] == -1 assert np.all(np.sqrt(res_tys[0][:, 1]**2 + res_tys[0][:, 2]**2) > Rmin) Zmin = -0.1 res_tys, res_phi_hits = compute_fieldlines( - bs, [0.97], [Zmin+0.02], tmax=2000, stopping_criteria=[ZStoppingCriterion(Zmin,True)] + bs, [0.97], [Zmin+0.02], tmax=2000, stopping_criteria=[MinZStoppingCriterion(Zmin)] ) assert res_phi_hits[0][0,1] == -1 assert np.all(res_tys[0][:, 3] > Zmin) Zmax = 0.5 res_tys, res_phi_hits = compute_fieldlines( - bs, [0.5], [Zmax-0.02], tmax=2000, stopping_criteria=[ZStoppingCriterion(Zmax,False)] + bs, [0.5], [Zmax-0.02], tmax=2000, stopping_criteria=[MaxZStoppingCriterion(Zmax)] ) assert res_phi_hits[0][0,1] == -1 assert np.all(res_tys[0][:, 3] < Zmax) diff --git a/tests/field/test_particle.py b/tests/field/test_particle.py index 5ec5f6ee8..d335d5a3e 100644 --- a/tests/field/test_particle.py +++ b/tests/field/test_particle.py @@ -12,7 +12,7 @@ from simsopt.field.tracing import trace_particles_starting_on_curve, SurfaceClassifier, \ particles_to_vtk, LevelsetStoppingCriterion, compute_gc_radius, gc_to_fullorbit_initial_guesses, \ IterationStoppingCriterion, trace_particles_starting_on_surface, trace_particles_boozer, \ - ToroidalFluxStoppingCriterion, ToroidalTransitStoppingCriterion, \ + MinToroidalFluxStoppingCriterion, MaxToroidalFluxStoppingCriterion, ToroidalTransitStoppingCriterion, \ compute_poloidal_transits, compute_toroidal_transits, trace_particles, compute_resonances from simsopt.geo.surfacerzfourier import SurfaceRZFourier from simsopt.field.boozermagneticfield import BoozerAnalytic @@ -451,7 +451,7 @@ def test_energy_momentum_conservation_boozer(self): gc_tys, gc_phi_hits = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[], mode='gc_vac', - stopping_criteria=[ToroidalFluxStoppingCriterion(.01,True), ToroidalFluxStoppingCriterion(0.99,False), ToroidalTransitStoppingCriterion(100, True)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(.01), MaxToroidalFluxStoppingCriterion(0.99), ToroidalTransitStoppingCriterion(100, True)], tol=1e-12) # pick 100 random points on each trace, and ensure that @@ -515,7 +515,7 @@ def test_energy_momentum_conservation_boozer(self): gc_tys, gc_phi_hits = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[], mode='gc_noK', - stopping_criteria=[ToroidalFluxStoppingCriterion(.01,True), ToroidalFluxStoppingCriterion(0.99,False), ToroidalTransitStoppingCriterion(100, True)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(.01), MaxToroidalFluxStoppingCriterion(0.99), ToroidalTransitStoppingCriterion(100, True)], tol=1e-12) max_energy_gc_error = np.array([]) @@ -567,7 +567,7 @@ def test_energy_momentum_conservation_boozer(self): gc_tys, gc_phi_hits = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[], mode='gc', - stopping_criteria=[ToroidalFluxStoppingCriterion(.01,True), ToroidalFluxStoppingCriterion(0.99,False), ToroidalTransitStoppingCriterion(100, True)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(.01), MaxToroidalFluxStoppingCriterion(0.99), ToroidalTransitStoppingCriterion(100, True)], tol=1e-12) max_energy_gc_error = np.array([]) @@ -607,7 +607,7 @@ def test_energy_momentum_conservation_boozer(self): # Now trace with forget_exact_path = False. Check that gc_phi_hits is the same gc_tys, gc_phi_hits_2 = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[], mode='gc_noK', - stopping_criteria=[ToroidalFluxStoppingCriterion(.01,True), ToroidalFluxStoppingCriterion(0.99,False), ToroidalTransitStoppingCriterion(100, True)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(.01), MaxToroidalFluxStoppingCriterion(0.99), ToroidalTransitStoppingCriterion(100, True)], tol=1e-12, forget_exact_path=True) for i in range(len(gc_phi_hits_2)): @@ -652,7 +652,7 @@ def test_compute_poloidal_toroidal_transits(self): gc_tys, gc_phi_hits = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[], mode='gc_vac', - stopping_criteria=[ToroidalFluxStoppingCriterion(.01,True), ToroidalFluxStoppingCriterion(0.99,False), ToroidalTransitStoppingCriterion(1, True)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(.01), MaxToroidalFluxStoppingCriterion(0.99), ToroidalTransitStoppingCriterion(1, True)], tol=1e-12) mpol = compute_poloidal_transits(gc_tys) @@ -726,7 +726,7 @@ def test_toroidal_flux_stopping_criterion(self): gc_tys, gc_phi_hits = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[], mode='gc_vac', - stopping_criteria=[ToroidalFluxStoppingCriterion(0.4,True), ToroidalFluxStoppingCriterion(0.6,False)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(0.4), MaxToroidalFluxStoppingCriterion(0.6)], tol=1e-12) for i in range(Nparticles): @@ -768,7 +768,7 @@ def test_compute_resonances(self): gc_tys, gc_phi_hits = trace_particles_boozer(bsh, stz_inits, vpar_inits, tmax=tmax, mass=m, charge=q, Ekin=Ekin, zetas=[0], mode='gc_vac', - stopping_criteria=[ToroidalFluxStoppingCriterion(0.01,True), ToroidalFluxStoppingCriterion(0.99,False), ToroidalTransitStoppingCriterion(100, True)], + stopping_criteria=[MinToroidalFluxStoppingCriterion(0.01), MaxToroidalFluxStoppingCriterion(0.99), ToroidalTransitStoppingCriterion(100, True)], tol=1e-8) resonances = compute_resonances(gc_tys, gc_phi_hits, delta=0.01)