Repository: hiddenSymmetries/simsopt
Pull request #521: Rename calculate_on_axis_B function
Author: login: akaptano
Created: 2025-05-15T20:26:34Z | Merged: 2025-05-20T14:08:34Z

As title describes, this pull request merely renames the function in permanent_magnet_helpers.py and makes some other little changes in the comments and docstring to make it clear. 


Diff:
diff --git a/examples/2_Intermediate/permanent_magnet_MUSE.py b/examples/2_Intermediate/permanent_magnet_MUSE.py
index 30c1fec2c..b3a1009d1 100755
--- a/examples/2_Intermediate/permanent_magnet_MUSE.py
+++ b/examples/2_Intermediate/permanent_magnet_MUSE.py
@@ -74,7 +74,7 @@
 bs = BiotSavart(coils)
 
 # Calculate average, approximate on-axis B field strength
-calculate_on_axis_B(bs, s)
+calculate_modB_on_major_radius(bs, s)
 
 # Make higher resolution surface for plotting Bnormal
 qphi = 2 * nphi
diff --git a/examples/2_Intermediate/permanent_magnet_QA.py b/examples/2_Intermediate/permanent_magnet_QA.py
index a7a23060e..94470fb2e 100755
--- a/examples/2_Intermediate/permanent_magnet_QA.py
+++ b/examples/2_Intermediate/permanent_magnet_QA.py
@@ -81,7 +81,7 @@
 bs = BiotSavart(coils)
 
 # Calculate average, approximate on-axis B field strength
-calculate_on_axis_B(bs, s)
+calculate_modB_on_major_radius(bs, s)
 
 # Make higher resolution surface for plotting Bnormal
 qphi = 2 * nphi
@@ -102,7 +102,7 @@
 Bnormal = np.sum(bs.B().reshape((nphi, ntheta, 3)) * s.unitnormal(), axis=2)
 
 # check after-optimization average on-axis magnetic field strength
-calculate_on_axis_B(bs, s)
+calculate_modB_on_major_radius(bs, s)
 
 # Set up correct Bnormal from TF coils
 bs.set_points(s.gamma().reshape((-1, 3)))
diff --git a/src/simsopt/util/permanent_magnet_helper_functions.py b/src/simsopt/util/permanent_magnet_helper_functions.py
index f0dd72f3b..407ea4a48 100644
--- a/src/simsopt/util/permanent_magnet_helper_functions.py
+++ b/src/simsopt/util/permanent_magnet_helper_functions.py
@@ -4,7 +4,7 @@
 """
 __all__ = ['read_focus_coils', 'coil_optimization',
            'trace_fieldlines', 'make_qfm',
-           'initialize_coils', 'calculate_on_axis_B',
+           'initialize_coils', 'calculate_modB_on_major_radius',
            'make_optimization_plots', 'run_Poincare_plots',
            'make_Bnormal_plots', 'initialize_default_kwargs'
            ]
@@ -273,7 +273,8 @@ def make_qfm(s, Bfield):
 def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
     """
     Initializes coils for each of the target configurations that are
-    used for permanent magnet optimization.
+    used for permanent magnet optimization. The total current is chosen
+    to achieve a desired average field strength along the major radius.
 
     Args:
         config_flag: String denoting the stellarator configuration 
@@ -294,15 +295,20 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
     if 'muse' in config_flag:
         # Load in pre-optimized coils
         coils_filename = TEST_DIR / 'muse_tf_coils.focus'
-        base_curves, base_currents, ncoils = read_focus_coils(coils_filename)
+
+        # Slight difference from older MUSE initialization. 
+        # Here we fix the total current summed over all coils and 
+        # older MUSE opt. fixed the first coil current. 
+        base_curves, base_currents0, ncoils = read_focus_coils(coils_filename)
+        total_current = np.sum([curr.get_value() for curr in base_currents0])
+        base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils - 1)]
+        total_current = Current(total_current)
+        total_current.fix_all()
+        base_currents += [total_current - sum(base_currents)]
         coils = []
         for i in range(ncoils):
             coils.append(Coil(base_curves[i], base_currents[i]))
-        base_currents[0].fix_all()
 
-        # fix all the coil shapes
-        for i in range(ncoils):
-            base_curves[i].fix_all()
     elif config_flag == 'qh':
         # generate planar TF coils
         ncoils = 4
@@ -310,20 +316,16 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         R1 = s.get_rc(1, 0) * 2
         order = 5
 
-        # qh needs to be scaled to 0.1 T on-axis magnetic field strength
-        from simsopt.mhd.vmec import Vmec
-        vmec_file = 'wout_LandremanPaul2021_QH_reactorScale_lowres_reference.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 8.75 / 5.69674966667
-        base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
-        base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
+        # QH reactor scale needs to be 5.7 T average magnetic field strength
+        total_current = 48712698  # Amperes
+        base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True,
+                                                   R0=R0, R1=R1, order=order, numquadpoints=64)
+        base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils - 1)]
         total_current = Current(total_current)
         total_current.fix_all()
         base_currents += [total_current - sum(base_currents)]
         coils = coils_via_symmetries(base_curves, base_currents, s.nfp, True)
 
-        # fix all the coil shapes so only the currents are optimized
-        for i in range(ncoils):
-            base_curves[i].fix_all()
     elif config_flag == 'qa':
         # generate planar TF coils
         ncoils = 8
@@ -332,18 +334,17 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         order = 5
 
         # qa needs to be scaled to 0.1 T on-axis magnetic field strength
-        from simsopt.mhd.vmec import Vmec
-        vmec_file = 'wout_LandremanPaul2021_QA_lowres.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 7.131
+        total_current = 187500
         base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
         base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
         total_current = Current(total_current)
         total_current.fix_all()
         base_currents += [total_current - sum(base_currents)]
         coils = coils_via_symmetries(base_curves, base_currents, s.nfp, True)
-        # fix all the coil shapes so only the currents are optimized
-        for i in range(ncoils):
-            base_curves[i].fix_all()
+    
+    # fix all the coil shapes so only the currents are optimized
+    for i in range(ncoils):
+        base_curves[i].fix_all()
 
     # Initialize the coil curves and save the data to vtk
     curves = [c.curve for c in coils]
@@ -351,19 +352,20 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
     return base_curves, curves, coils
 
 
-def calculate_on_axis_B(bs, s):
+def calculate_modB_on_major_radius(bs, s):
     """
-    Check the average, approximate, on-axis
-    magnetic field strength to make sure the
-    configuration is scaled correctly.
+    Check the average magnetic field strength along the major radius
+    (m=n=0 mode of a SurfaceRZFourier object)
+    to make sure the configuration is scaled correctly. For highly shaped
+    stellarators, this can deviate a bit from the on-axis B field strength.
 
     Args:
-        bs: MagneticField or BiotSavart class object.
-        s: plasma boundary surface.
+        bs (BiotSavart): MagneticField or BiotSavart class object.
+        s (SurfaceRZFourier): plasma boundary surface.
 
     Returns:
-        B0avg: Average magnetic field strength along 
-          the major radius of the device.
+        B0avg (float): Average magnetic field strength along 
+          the major radius (m=n=0 mode of a SurfaceRZFourier object) of the device.
     """
     nphi = len(s.quadpoints_phi)
     bspoints = np.zeros((nphi, 3))
diff --git a/tests/geo/test_pm_grid.py b/tests/geo/test_pm_grid.py
index f2087ce79..5884352c2 100644
--- a/tests/geo/test_pm_grid.py
+++ b/tests/geo/test_pm_grid.py
@@ -22,7 +22,7 @@
 filename = TEST_DIR / 'input.LandremanPaul2021_QA'
 
 
-class Testing(unittest.TestCase):
+class PermanentMagnetGridTesting(unittest.TestCase):
 
     def test_bad_params(self):
         """
@@ -524,8 +524,8 @@ def test_pm_helpers(self):
                 base_curves[i].fix_all()
             bs = BiotSavart(coils)
 
-            # Calculate average, approximate on-axis B field strength
-            B0avg = calculate_on_axis_B(bs, s)
+            # Calculate average B field strength along the major radius
+            B0avg = calculate_modB_on_major_radius(bs, s)
             assert np.allclose(B0avg, 0.15)
 
             # Check coil initialization for some common stellarators wout_LandremanPaul2021_QA_lowres
@@ -533,21 +533,21 @@ def test_pm_helpers(self):
                                            range="half period", nphi=nphi, ntheta=ntheta)
             base_curves, curves, coils = initialize_coils('qa', TEST_DIR, s)
             bs = BiotSavart(coils)
-            B0avg = calculate_on_axis_B(bs, s)
+            B0avg = calculate_modB_on_major_radius(bs, s)
             assert np.allclose(B0avg, 0.15)
 
             s = SurfaceRZFourier.from_wout(TEST_DIR / 'wout_LandremanPaul2021_QH_reactorScale_lowres_reference.nc',
                                            range="half period", nphi=nphi, ntheta=ntheta)
             base_curves, curves, coils = initialize_coils('qh', TEST_DIR, s)
             bs = BiotSavart(coils)
-            B0avg = calculate_on_axis_B(bs, s)
-            assert np.allclose(B0avg, 0.15)
+            B0avg = calculate_modB_on_major_radius(bs, s)
+            assert np.allclose(B0avg, 5.7)
 
             # Repeat with wrapper function
             s = SurfaceRZFourier.from_focus(surface_filename, range="half period", nphi=nphi, ntheta=ntheta)
             base_curves, curves, coils = initialize_coils('muse_famus', TEST_DIR, s)
             bs = BiotSavart(coils)
-            B0avg = calculate_on_axis_B(bs, s)
+            B0avg = calculate_modB_on_major_radius(bs, s)
             assert np.allclose(B0avg, 0.15)
 
         # Test rescaling
@@ -640,7 +640,7 @@ def test_pm_post_processing(self):
         s = SurfaceRZFourier.from_focus(surface_filename, range="half period", nphi=nphi, ntheta=ntheta)
         base_curves, curves, coils = initialize_coils('muse_famus', TEST_DIR, s)
         bs = BiotSavart(coils)
-        B0avg = calculate_on_axis_B(bs, s)
+        B0avg = calculate_modB_on_major_radius(bs, s)
         assert np.allclose(B0avg, 0.15)
 
         # drastically downsample the grid for speed here
@@ -667,13 +667,12 @@ def test_pm_post_processing(self):
         # Make QFM surfaces
         Bfield = bs + b_dipole
         Bfield.set_points(s_plot.gamma().reshape((-1, 3)))
-        #qfm_surf = make_qfm(s_plot, Bfield)
-        #qfm_surf = qfm_surf.surface
+        qfm_surf = make_qfm(s_plot, Bfield)
+        qfm_surf = qfm_surf.surface
 
         # Run poincare plotting
-        #with ScratchDir("."):
-        #    run_Poincare_plots(s_plot, bs, b_dipole, None, 'poincare_test')
-
+        with ScratchDir("."):
+           run_Poincare_plots(s_plot, bs, b_dipole, None, 'poincare_test')
 
 if __name__ == "__main__":
     unittest.main()


Reviews:
user: login: andrewgiuliani
body: thanks so much for splitting things up, only some really minor comments here. 

It seems like you're not consistently setting up the devices with the same way, e.g.

for muse, `base_currents[0].fix_all()` is called, while for QH we scale things down and `total_current.fix_all()` is being called, which seems now like all the currents are fixed.   

Can it be clarified why?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:08:21Z
user: login: akaptano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T22:47:26Z
user: login: akaptano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T22:59:34Z
user: login: landreman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T10:03:37Z
user: login: landreman
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T13:41:38Z

Review comments:
diff_hunk: @@ -310,12 +310,14 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         R1 = s.get_rc(1, 0) * 2
         order = 5
 
-        # qh needs to be scaled to 0.1 T on-axis magnetic field strength
+        # QH reactor scale needs to be 5.7 T average magnetic field strength
         from simsopt.mhd.vmec import Vmec
         vmec_file = 'wout_LandremanPaul2021_QH_reactorScale_lowres_reference.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 8.75 / 5.69674966667
-        base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
-        base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
+        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 1.311753
path: src/simsopt/util/permanent_magnet_helper_functions.py
user: login: andrewgiuliani
body: what's going on here? can we add a quick comment? + update the docstring to say what the mean modB on major radius is being set here?
created_at: 2025-05-16T15:02:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -667,13 +667,12 @@ def test_pm_post_processing(self):
         # Make QFM surfaces
         Bfield = bs + b_dipole
         Bfield.set_points(s_plot.gamma().reshape((-1, 3)))
-        #qfm_surf = make_qfm(s_plot, Bfield)
-        #qfm_surf = qfm_surf.surface
+        # qfm_surf = make_qfm(s_plot, Bfield)
path: tests/geo/test_pm_grid.py
user: login: andrewgiuliani
body: stray comments
created_at: 2025-05-16T15:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -667,13 +667,12 @@ def test_pm_post_processing(self):
         # Make QFM surfaces
         Bfield = bs + b_dipole
         Bfield.set_points(s_plot.gamma().reshape((-1, 3)))
-        #qfm_surf = make_qfm(s_plot, Bfield)
-        #qfm_surf = qfm_surf.surface
+        # qfm_surf = make_qfm(s_plot, Bfield)
path: tests/geo/test_pm_grid.py
user: login: akaptano
body: I actually think this adds coverage to permanent_magnet_helpers (which is something we discussed with Bharat) so I've added the comments back in (seems to run fine and not take too long in the unit tests)
created_at: 2025-05-16T22:47:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -310,12 +310,14 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         R1 = s.get_rc(1, 0) * 2
         order = 5
 
-        # qh needs to be scaled to 0.1 T on-axis magnetic field strength
+        # QH reactor scale needs to be 5.7 T average magnetic field strength
         from simsopt.mhd.vmec import Vmec
         vmec_file = 'wout_LandremanPaul2021_QH_reactorScale_lowres_reference.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 8.75 / 5.69674966667
-        base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
-        base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
+        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 1.311753
path: src/simsopt/util/permanent_magnet_helper_functions.py
user: login: akaptano
body: Removed, and replaced by a fixed number that gets the scaling right. Updated the docstring
created_at: 2025-05-16T22:59:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -332,37 +334,36 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         order = 5
 
         # qa needs to be scaled to 0.1 T on-axis magnetic field strength
-        from simsopt.mhd.vmec import Vmec
-        vmec_file = 'wout_LandremanPaul2021_QA_lowres.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 7.131
+        total_current = 187500
         base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
         base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
         total_current = Current(total_current)
         total_current.fix_all()
         base_currents += [total_current - sum(base_currents)]
         coils = coils_via_symmetries(base_curves, base_currents, s.nfp, True)
-        # fix all the coil shapes so only the currents are optimized
-        for i in range(ncoils):
-            base_curves[i].fix_all()
+    
+    # fix all the coil shapes so only the currents are optimized
+    for i in range(ncoils):
+        base_curves[i].fix_all()
 
     # Initialize the coil curves and save the data to vtk
     curves = [c.curve for c in coils]
     curves_to_vtk(curves, out_dir / "curves_init")
     return base_curves, curves, coils
 
 
-def calculate_on_axis_B(bs, s):
+def calculate_modB_on_major_radius(bs, s):
     """
-    Check the average, approximate, on-axis
-    magnetic field strength to make sure the
-    configuration is scaled correctly.
+    Check the average magnetic field strength along the major radius
path: src/simsopt/util/permanent_magnet_helper_functions.py
user: login: landreman
body: How about clarifying that "major radius" here really means the m=n=0 mode of a SurfaceRZFourier object. There are other ways that the major radius of a surface could be defined, and indeed vmec uses a different definition.
created_at: 2025-05-17T09:59:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -332,37 +334,36 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         order = 5
 
         # qa needs to be scaled to 0.1 T on-axis magnetic field strength
-        from simsopt.mhd.vmec import Vmec
-        vmec_file = 'wout_LandremanPaul2021_QA_lowres.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 7.131
+        total_current = 187500
         base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
         base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
         total_current = Current(total_current)
         total_current.fix_all()
         base_currents += [total_current - sum(base_currents)]
         coils = coils_via_symmetries(base_curves, base_currents, s.nfp, True)
-        # fix all the coil shapes so only the currents are optimized
-        for i in range(ncoils):
-            base_curves[i].fix_all()
+    
+    # fix all the coil shapes so only the currents are optimized
+    for i in range(ncoils):
+        base_curves[i].fix_all()
 
     # Initialize the coil curves and save the data to vtk
     curves = [c.curve for c in coils]
     curves_to_vtk(curves, out_dir / "curves_init")
     return base_curves, curves, coils
 
 
-def calculate_on_axis_B(bs, s):
+def calculate_modB_on_major_radius(bs, s):
     """
-    Check the average, approximate, on-axis
-    magnetic field strength to make sure the
-    configuration is scaled correctly.
+    Check the average magnetic field strength along the major radius
+    to make sure the configuration is scaled correctly. For highly shaped
+    stellarators, this can deviate a bit from the on-axis B field strength.
 
     Args:
-        bs: MagneticField or BiotSavart class object.
-        s: plasma boundary surface.
+        bs (BiotSavart): MagneticField or BiotSavart class object.
+        s (Surface): plasma boundary surface.
 
     Returns:
-        B0avg: Average magnetic field strength along 
+        B0avg (array, shape (s.nphi,)): Average magnetic field strength along 
path: src/simsopt/util/permanent_magnet_helper_functions.py
user: login: landreman
body: Isn't B0avg a float and not an array?
created_at: 2025-05-17T10:00:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -332,37 +334,36 @@ def initialize_coils(config_flag, TEST_DIR, s, out_dir=''):
         order = 5
 
         # qa needs to be scaled to 0.1 T on-axis magnetic field strength
-        from simsopt.mhd.vmec import Vmec
-        vmec_file = 'wout_LandremanPaul2021_QA_lowres.nc'
-        total_current = Vmec(TEST_DIR / vmec_file).external_current() / (2 * s.nfp) / 7.131
+        total_current = 187500
         base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
         base_currents = [(Current(total_current / ncoils * 1e-5) * 1e5) for _ in range(ncoils-1)]
         total_current = Current(total_current)
         total_current.fix_all()
         base_currents += [total_current - sum(base_currents)]
         coils = coils_via_symmetries(base_curves, base_currents, s.nfp, True)
-        # fix all the coil shapes so only the currents are optimized
-        for i in range(ncoils):
-            base_curves[i].fix_all()
+    
+    # fix all the coil shapes so only the currents are optimized
+    for i in range(ncoils):
+        base_curves[i].fix_all()
 
     # Initialize the coil curves and save the data to vtk
     curves = [c.curve for c in coils]
     curves_to_vtk(curves, out_dir / "curves_init")
     return base_curves, curves, coils
 
 
-def calculate_on_axis_B(bs, s):
+def calculate_modB_on_major_radius(bs, s):
     """
-    Check the average, approximate, on-axis
-    magnetic field strength to make sure the
-    configuration is scaled correctly.
+    Check the average magnetic field strength along the major radius
+    to make sure the configuration is scaled correctly. For highly shaped
+    stellarators, this can deviate a bit from the on-axis B field strength.
 
     Args:
-        bs: MagneticField or BiotSavart class object.
-        s: plasma boundary surface.
+        bs (BiotSavart): MagneticField or BiotSavart class object.
+        s (Surface): plasma boundary surface.
path: src/simsopt/util/permanent_magnet_helper_functions.py
user: login: landreman
body: Specifically, state that s must be a SurfaceRZFourier - otherwise the `get_rc()` method used in this function would not exist.
created_at: 2025-05-17T10:03:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-15T23:14:00Z
body: ## [Codecov](https://app.codecov.io/gh/hiddenSymmetries/simsopt/pull/521?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 92.30%. Comparing base [(`73104bf`)](https://app.codecov.io/gh/hiddenSymmetries/simsopt/commit/73104bfbd71516068dec84de6e47541fb49c2a60?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries) to head [(`2ec11c8`)](https://app.codecov.io/gh/hiddenSymmetries/simsopt/commit/2ec11c8e521e0001ff85d812f0ae055de682eadd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries).
> Report is 56 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #521      +/-   ##
==========================================
+ Coverage   92.01%   92.30%   +0.29%     
==========================================
  Files          82       82              
  Lines       16013    15989      -24     
==========================================
+ Hits        14734    14759      +25     
+ Misses       1279     1230      -49     
```

| [Flag](https://app.codecov.io/gh/hiddenSymmetries/simsopt/pull/521/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/hiddenSymmetries/simsopt/pull/521/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries) | `92.30% <100.00%> (+0.29%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/hiddenSymmetries/simsopt/pull/521?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiddenSymmetries).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: akaptano
created_at: 2025-05-16T23:01:53Z
body: > thanks so much for splitting things up, only some really minor comments here.
> 
> It seems like you're not consistently setting up the devices with the same way, e.g.
> 
> for muse, `base_currents[0].fix_all()` is called, while for QH we scale things down and `total_current.fix_all()` is being called, which seems now like all the currents are fixed.
> 
> Can it be clarified why?

Just fixed this, now they both use the 'total_current' method. My impression has been that this is generally superior, because (I think) it is only fixing the total current through all coils (but each coil current can vary within that constraint), rather than `base_currents[0].fix_all()` explicitly fixes the current in coil 1, which might have a crappy initial value that you have now fixed forever.  
author_association: CONTRIBUTOR
user: login: akaptano
created_at: 2025-05-20T13:17:42Z
body: @landreman @andrewgiuliani I think this is ready to go but I somehow dismissed Matt's old approval of this. I will merge this in when you're ready. 
author_association: CONTRIBUTOR