From 25b5846725b4b6a624d8452b41f87fabc08c3829 Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Thu, 22 Aug 2024 15:03:19 -0600 Subject: [PATCH 01/11] fusion power objective --- desc/compute/_equil.py | 38 ++++++- desc/compute/_profiles.py | 62 ++++++++++++ desc/objectives/_confinement.py | 173 ++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 1 deletion(-) diff --git a/desc/compute/_equil.py b/desc/compute/_equil.py index 7cd01491ed..d4ffd09d18 100644 --- a/desc/compute/_equil.py +++ b/desc/compute/_equil.py @@ -10,7 +10,7 @@ """ from interpax import interp1d -from scipy.constants import mu_0 +from scipy.constants import elementary_charge, mu_0 from desc.backend import jnp @@ -843,3 +843,39 @@ def _P_ISS04(params, transforms, profiles, data, **kwargs): ) ) ** (1 / 0.39) return data + + +@register_compute_fun( + name="P_fusion", + label="P_{fusion}", + units="W", + units_long="Watts", + description="Fusion power", + dim=0, + params=[], + transforms={"grid": []}, + profiles=[], + coordinates="", + data=["rho", "ni", "", "sqrt(g)"], + resolution_requirement="rtz", + reaction="str: Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}. " + + "Default is 'T(d,n)4He'.", +) +def _P_fusion(params, transforms, profiles, data, **kwargs): + reaction = kwargs.get("fuel", "T(d,n)4He") + match reaction: + case "T(d,n)4He": + energy = 3.52e6 + 14.06e6 # eV + case "D(d,p)T": + energy = 1.01e6 + 3.02e6 # eV + case "D(d,n)3He": + energy = 0.82e6 + 2.45e6 # eV + + reaction_rate = jnp.sum( + *data["ni"] ** 2 + * data[""] + * data["sqrt(g)"] + * transforms["grid"].weights + ) # reactions/s + data["P_fusion"] = reaction_rate * energy * elementary_charge # J/s + return data diff --git a/desc/compute/_profiles.py b/desc/compute/_profiles.py index 84de48e576..0c386e677b 100644 --- a/desc/compute/_profiles.py +++ b/desc/compute/_profiles.py @@ -1909,3 +1909,65 @@ def _shear(params, transforms, profiles, data, **kwargs): None, ) return data + + +@register_compute_fun( + name="", + label="\\langle\\sigma\\nu\\rangle", + units="m^3 \\cdot s^{-1}", + units_long="cubic meters / second", + description="Thermal reactivity from Bosch-Hale parameterization", + dim=1, + params=["Ti_l"], + transforms={"grid": []}, + profiles=["ion_temperature"], + coordinates="r", + data=["Ti"], + reaction="str: Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}. " + + "Default is 'T(d,n)4He'.", +) +def _reactivity(params, transforms, profiles, data, **kwargs): + # Bosch and Hale. “Improved Formulas for Fusion Cross-Sections and Thermal + # Reactivities.” Nuclear Fusion 32 (April 1992): 611-631. + # https://doi.org/10.1088/0029-5515/32/4/I07. + reaction = kwargs.get("fuel", "T(d,n)4He") + match reaction: + case "T(d,n)4He": + B_G = 34.382 + mc2 = 1124656 + C1 = 1.17302e-9 + C2 = 1.51361e-2 + C3 = 7.51886e-2 + C4 = 4.60643e-3 + C5 = 1.35000e-2 + C6 = -1.06750e-4 + C7 = 1.36600e-5 + case "D(d,p)T": + B_G = 31.3970 + mc2 = 937814 + C1 = 5.65718e-12 + C2 = 3.41267e-3 + C3 = 1.99167e-3 + C4 = 0 + C5 = 1.05060e-5 + C6 = 0 + C7 = 0 + case "D(d,n)3He": + B_G = 31.3970 + mc2 = 937814 + C1 = 5.43360e-12 + C2 = 5.85778e-3 + C3 = 7.68222e-3 + C4 = 0 + C5 = -2.96400e-6 + C6 = 0 + C7 = 0 + + T = data["Ti"] / 1e3 # keV + theta = T / ( + 1 - (T * (C2 + T * (C4 + T * C6))) / (1 + T * (C3 + T * (C5 + T * C7))) + ) + xi = (B_G**2 / (4 * theta)) ** (1 / 3) + sigma_nu = C1 * theta * jnp.sqrt(xi / (mc2 * T**3)) * jnp.exp(-3 * xi) # cm^3/s + data[""] = sigma_nu / 1e6 # m^3/s + return data diff --git a/desc/objectives/_confinement.py b/desc/objectives/_confinement.py index 588b9accd5..56a98de2ba 100644 --- a/desc/objectives/_confinement.py +++ b/desc/objectives/_confinement.py @@ -9,6 +9,179 @@ from .objective_funs import _Objective +class FusionPower(_Objective): + """Fusion power. + + P = e E ∫ n^2 ⟨σν⟩ dV (W) + + References + ---------- + https://doi.org/10.1088/0029-5515/32/4/I07. + Improved Formulas for Fusion Cross-Sections and Thermal Reactivities. + H.-S. Bosch and G.M. Hale. Nucl. Fusion April 1992; 32 (4): 611-631. + + Parameters + ---------- + eq : Equilibrium + Equilibrium that will be optimized to satisfy the Objective. + target : {float, ndarray}, optional + Target value(s) of the objective. Only used if bounds is None. + Must be broadcastable to Objective.dim_f. Defaults to ``target=0``. + bounds : tuple of {float, ndarray}, optional + Lower and upper bounds on the objective. Overrides target. + Both bounds must be broadcastable to to Objective.dim_f. + Defaults to ``target=0``. + weight : {float, ndarray}, optional + Weighting to apply to the Objective, relative to other Objectives. + Must be broadcastable to to Objective.dim_f + normalize : bool, optional + Whether to compute the error in physical units or non-dimensionalize. + normalize_target : bool, optional + Whether target and bounds should be normalized before comparing to computed + values. If `normalize` is `True` and the target is in physical units, + this should also be set to True. + loss_function : {None, 'mean', 'min', 'max'}, optional + Loss function to apply to the objective values once computed. This loss function + is called on the raw compute value, before any shifting, scaling, or + normalization. Note: Has no effect for this objective. + deriv_mode : {"auto", "fwd", "rev"} + Specify how to compute jacobian matrix, either forward mode or reverse mode AD. + "auto" selects forward or reverse mode based on the size of the input and output + of the objective. Has no effect on self.grad or self.hess which always use + reverse mode and forward over reverse mode respectively. + reaction : str, optional + Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}. + Default = 'T(d,n)4He'. + grid : Grid, optional + Collocation grid used to compute the intermediate quantities. + Defaults to ``QuadratureGrid(eq.L_grid, eq.M_grid, eq.N_grid, eq.NFP)``. + name : str, optional + Name of the objective function. + + """ + + _scalar = True + _units = "(W)" + _print_value_fmt = "Fusion power: {:10.3e} " + + def __init__( + self, + eq, + target=None, + bounds=None, + weight=1, + normalize=True, + normalize_target=True, + loss_function=None, + deriv_mode="auto", + reaction="T(d,n)4He", + grid=None, + name="fusion power", + ): + if target is None and bounds is None: + target = 0 + self._reaction = reaction + self._grid = grid + super().__init__( + things=eq, + target=target, + bounds=bounds, + weight=weight, + normalize=normalize, + normalize_target=normalize_target, + loss_function=loss_function, + deriv_mode=deriv_mode, + name=name, + ) + + def build(self, use_jit=True, verbose=1): + """Build constant arrays. + + Parameters + ---------- + use_jit : bool, optional + Whether to just-in-time compile the objective and derivatives. + verbose : int, optional + Level of output. + + """ + eq = self.things[0] + errorif( + eq.ion_density is None, + ValueError, + "Equilibrium must have an ion density profile.", + ) + if self._grid is None: + self._grid = QuadratureGrid( + L=eq.L_grid, + M=eq.M_grid, + N=eq.N_grid, + NFP=eq.NFP, + ) + self._dim_f = 1 + self._data_keys = ["P_fusion"] + + timer = Timer() + if verbose > 0: + print("Precomputing transforms") + timer.start("Precomputing transforms") + + self._constants = { + "profiles": get_profiles(self._data_keys, obj=eq, grid=self._grid), + "transforms": get_transforms(self._data_keys, obj=eq, grid=self._grid), + "reaction": self._reaction, + } + + timer.stop("Precomputing transforms") + if verbose > 1: + timer.disp("Precomputing transforms") + + if self._normalize: + scales = compute_scaling_factors(eq) + self._normalization = scales["W_p"] + + super().build(use_jit=use_jit, verbose=verbose) + + def compute(self, params, constants=None): + """Compute fusion power. + + Parameters + ---------- + params : dict + Dictionary of equilibrium or surface degrees of freedom, eg + Equilibrium.params_dict + constants : dict + Dictionary of constant data, eg transforms, profiles etc. Defaults to + self.constants + + Returns + ------- + P : float + Fusion power (W). + + """ + if constants is None: + constants = self.constants + data = compute_fun( + "desc.equilibrium.equilibrium.Equilibrium", + self._data_keys, + params=params, + transforms=constants["transforms"], + profiles=constants["profiles"], + reaction=constants["reaction"], + ) + return data["P_fusion"] + + @property + def reaction(self): + """str: Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}.""" + return self._reaction + + @reaction.setter + def reaction(self, new): + self._reaction = new + + class HeatingPowerISS04(_Objective): """Heating power required by the ISS04 energy confinement time scaling. From ed68131bb43db07621fb3befa9a2cf16c17c5803 Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Thu, 22 Aug 2024 15:04:19 -0600 Subject: [PATCH 02/11] rename confinement to power_balance --- desc/objectives/{_confinement.py => _power_balance.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename desc/objectives/{_confinement.py => _power_balance.py} (100%) diff --git a/desc/objectives/_confinement.py b/desc/objectives/_power_balance.py similarity index 100% rename from desc/objectives/_confinement.py rename to desc/objectives/_power_balance.py From 00465245420e9eeb61c77c1761c6248ef38a700d Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Thu, 22 Aug 2024 15:16:58 -0600 Subject: [PATCH 03/11] fix bugs --- desc/compute/_equil.py | 2 +- desc/objectives/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desc/compute/_equil.py b/desc/compute/_equil.py index d4ffd09d18..3f048c34a7 100644 --- a/desc/compute/_equil.py +++ b/desc/compute/_equil.py @@ -872,7 +872,7 @@ def _P_fusion(params, transforms, profiles, data, **kwargs): energy = 0.82e6 + 2.45e6 # eV reaction_rate = jnp.sum( - *data["ni"] ** 2 + data["ni"] ** 2 * data[""] * data["sqrt(g)"] * transforms["grid"].weights diff --git a/desc/objectives/__init__.py b/desc/objectives/__init__.py index 0443a8a504..0fb9ce1329 100644 --- a/desc/objectives/__init__.py +++ b/desc/objectives/__init__.py @@ -11,7 +11,6 @@ QuadraticFlux, ToroidalFlux, ) -from ._confinement import HeatingPowerISS04 from ._equilibrium import ( CurrentDensity, Energy, @@ -39,6 +38,7 @@ QuasisymmetryTripleProduct, QuasisymmetryTwoTerm, ) +from ._power_balance import FusionPower, HeatingPowerISS04 from ._profiles import Pressure, RotationalTransform, Shear, ToroidalCurrent from ._stability import MagneticWell, MercierStability from .getters import ( From e28c7947fb416403eb8130ec5c32b85bf4576789 Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Fri, 23 Aug 2024 11:43:21 -0600 Subject: [PATCH 04/11] replace reaction with fuel, only DT --- desc/compute/_equil.py | 14 +++----- desc/compute/_profiles.py | 60 ++++++++++++------------------- desc/objectives/_power_balance.py | 35 ++++++++++-------- tests/test_objective_funs.py | 41 +++++++++++++++++++++ 4 files changed, 88 insertions(+), 62 deletions(-) diff --git a/desc/compute/_equil.py b/desc/compute/_equil.py index 3f048c34a7..c681c530a8 100644 --- a/desc/compute/_equil.py +++ b/desc/compute/_equil.py @@ -858,18 +858,12 @@ def _P_ISS04(params, transforms, profiles, data, **kwargs): coordinates="", data=["rho", "ni", "", "sqrt(g)"], resolution_requirement="rtz", - reaction="str: Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}. " - + "Default is 'T(d,n)4He'.", + fuel="str: Fusion fuel, assuming a 50/50 mix. One of {'DT'}. Default is 'DT'.", ) def _P_fusion(params, transforms, profiles, data, **kwargs): - reaction = kwargs.get("fuel", "T(d,n)4He") - match reaction: - case "T(d,n)4He": - energy = 3.52e6 + 14.06e6 # eV - case "D(d,p)T": - energy = 1.01e6 + 3.02e6 # eV - case "D(d,n)3He": - energy = 0.82e6 + 2.45e6 # eV + energies = {"DT": 3.52e6 + 14.06e6} # eV + fuel = kwargs.get("fuel", "DT") + energy = energies.get(fuel) reaction_rate = jnp.sum( data["ni"] ** 2 diff --git a/desc/compute/_profiles.py b/desc/compute/_profiles.py index 0c386e677b..290512d851 100644 --- a/desc/compute/_profiles.py +++ b/desc/compute/_profiles.py @@ -1923,51 +1923,37 @@ def _shear(params, transforms, profiles, data, **kwargs): profiles=["ion_temperature"], coordinates="r", data=["Ti"], - reaction="str: Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}. " - + "Default is 'T(d,n)4He'.", + fuel="str: Fusion fuel, assuming a 50/50 mix. One of {'DT'}. Default is 'DT'.", ) def _reactivity(params, transforms, profiles, data, **kwargs): # Bosch and Hale. “Improved Formulas for Fusion Cross-Sections and Thermal # Reactivities.” Nuclear Fusion 32 (April 1992): 611-631. # https://doi.org/10.1088/0029-5515/32/4/I07. - reaction = kwargs.get("fuel", "T(d,n)4He") - match reaction: - case "T(d,n)4He": - B_G = 34.382 - mc2 = 1124656 - C1 = 1.17302e-9 - C2 = 1.51361e-2 - C3 = 7.51886e-2 - C4 = 4.60643e-3 - C5 = 1.35000e-2 - C6 = -1.06750e-4 - C7 = 1.36600e-5 - case "D(d,p)T": - B_G = 31.3970 - mc2 = 937814 - C1 = 5.65718e-12 - C2 = 3.41267e-3 - C3 = 1.99167e-3 - C4 = 0 - C5 = 1.05060e-5 - C6 = 0 - C7 = 0 - case "D(d,n)3He": - B_G = 31.3970 - mc2 = 937814 - C1 = 5.43360e-12 - C2 = 5.85778e-3 - C3 = 7.68222e-3 - C4 = 0 - C5 = -2.96400e-6 - C6 = 0 - C7 = 0 + coefficients = { + "DT": { + "B_G": 34.382, + "mc2": 1124656, + "C1": 1.17302e-9, + "C2": 1.51361e-2, + "C3": 7.51886e-2, + "C4": 4.60643e-3, + "C5": 1.35000e-2, + "C6": -1.06750e-4, + "C7": 1.36600e-5, + } + } + fuel = kwargs.get("fuel", "DT") + coeffs = coefficients.get(fuel) T = data["Ti"] / 1e3 # keV theta = T / ( - 1 - (T * (C2 + T * (C4 + T * C6))) / (1 + T * (C3 + T * (C5 + T * C7))) + 1 + - (T * (coeffs["C2"] + T * (coeffs["C4"] + T * coeffs["C6"]))) + / (1 + T * (coeffs["C3"] + T * (coeffs["C5"] + T * coeffs["C7"]))) ) - xi = (B_G**2 / (4 * theta)) ** (1 / 3) - sigma_nu = C1 * theta * jnp.sqrt(xi / (mc2 * T**3)) * jnp.exp(-3 * xi) # cm^3/s + xi = (coeffs["B_G"] ** 2 / (4 * theta)) ** (1 / 3) + sigma_nu = ( + coeffs["C1"] * theta * jnp.sqrt(xi / (coeffs["mc2"] * T**3)) * jnp.exp(-3 * xi) + ) # cm^3/s data[""] = sigma_nu / 1e6 # m^3/s return data diff --git a/desc/objectives/_power_balance.py b/desc/objectives/_power_balance.py index 56a98de2ba..14c3e5849e 100644 --- a/desc/objectives/_power_balance.py +++ b/desc/objectives/_power_balance.py @@ -49,9 +49,8 @@ class FusionPower(_Objective): "auto" selects forward or reverse mode based on the size of the input and output of the objective. Has no effect on self.grad or self.hess which always use reverse mode and forward over reverse mode respectively. - reaction : str, optional - Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}. - Default = 'T(d,n)4He'. + fuel : str, optional + Fusion fuel, assuming a 50/50 mix. One of {'DT'}. Default = 'DT'. grid : Grid, optional Collocation grid used to compute the intermediate quantities. Defaults to ``QuadratureGrid(eq.L_grid, eq.M_grid, eq.N_grid, eq.NFP)``. @@ -74,13 +73,16 @@ def __init__( normalize_target=True, loss_function=None, deriv_mode="auto", - reaction="T(d,n)4He", + fuel="DT", grid=None, name="fusion power", ): + errorif( + fuel not in ["DT"], ValueError, f"fuel must be one of ['DT'], got {fuel}." + ) if target is None and bounds is None: target = 0 - self._reaction = reaction + self._fuel = fuel self._grid = grid super().__init__( things=eq, @@ -107,9 +109,9 @@ def build(self, use_jit=True, verbose=1): """ eq = self.things[0] errorif( - eq.ion_density is None, + eq.electron_density is None, ValueError, - "Equilibrium must have an ion density profile.", + "Equilibrium must have an electron density profile.", ) if self._grid is None: self._grid = QuadratureGrid( @@ -129,7 +131,7 @@ def build(self, use_jit=True, verbose=1): self._constants = { "profiles": get_profiles(self._data_keys, obj=eq, grid=self._grid), "transforms": get_transforms(self._data_keys, obj=eq, grid=self._grid), - "reaction": self._reaction, + "fuel": self._fuel, } timer.stop("Precomputing transforms") @@ -168,18 +170,21 @@ def compute(self, params, constants=None): params=params, transforms=constants["transforms"], profiles=constants["profiles"], - reaction=constants["reaction"], + fuel=constants["fuel"], ) return data["P_fusion"] @property - def reaction(self): - """str: Fusion reaction. One of {'T(d,n)4He', 'D(d,p)T', 'D(d,n)3He'}.""" - return self._reaction + def fuel(self): + """str: Fusion fuel, assuming a 50/50 mix. One of {'DT'}. Default = 'DT'.""" + return self._fuel - @reaction.setter - def reaction(self, new): - self._reaction = new + @fuel.setter + def fuel(self, new): + errorif( + new not in ["DT"], ValueError, f"fuel must be one of ['DT'], got {new}." + ) + self._fuel = new class HeatingPowerISS04(_Objective): diff --git a/tests/test_objective_funs.py b/tests/test_objective_funs.py index ff566c6416..2a1915187e 100644 --- a/tests/test_objective_funs.py +++ b/tests/test_objective_funs.py @@ -48,6 +48,7 @@ Energy, ForceBalance, ForceBalanceAnisotropic, + FusionPower, GenericObjective, HeatingPowerISS04, Isodynamicity, @@ -2031,6 +2032,7 @@ class TestComputeScalarResolution: CoilLength, CoilSetMinDistance, CoilTorsion, + FusionPower, GenericObjective, HeatingPowerISS04, Omnigenity, @@ -2092,6 +2094,28 @@ def test_compute_scalar_resolution_bootstrap(self): f[i] = obj.compute_scalar(obj.x()) np.testing.assert_allclose(f, f[-1], rtol=5e-2) + @pytest.mark.regression + def test_compute_scalar_resolution_fusion_power(self): + """FusionPower.""" + eq = self.eq.copy() + eq.electron_density = PowerSeriesProfile([1e19, 0, -1e19]) + eq.electron_temperature = PowerSeriesProfile([1e3, 0, -1e3]) + eq.ion_temperature = PowerSeriesProfile([1e3, 0, -1e3]) + eq.atomic_number = 1.0 + + f = np.zeros_like(self.res_array, dtype=float) + for i, res in enumerate(self.res_array): + grid = QuadratureGrid( + L=int(self.eq.L * res), + M=int(self.eq.M * res), + N=int(self.eq.N * res), + NFP=self.eq.NFP, + ) + obj = ObjectiveFunction(FusionPower(eq=eq, grid=grid)) + obj.build(verbose=0) + f[i] = obj.compute_scalar(obj.x()) + np.testing.assert_allclose(f, f[-1], rtol=5e-2) + @pytest.mark.regression def test_compute_scalar_resolution_heating_power(self): """HeatingPowerISS04.""" @@ -2383,6 +2407,7 @@ class TestObjectiveNaNGrad: CoilSetMinDistance, CoilTorsion, ForceBalanceAnisotropic, + FusionPower, HeatingPowerISS04, Omnigenity, PlasmaCoilSetMinDistance, @@ -2432,6 +2457,22 @@ def test_objective_no_nangrad_bootstrap(self): g = obj.grad(obj.x(eq)) assert not np.any(np.isnan(g)), "redl bootstrap" + @pytest.mark.unit + def test_objective_no_nangrad_fusion_power(self): + """FusionPower.""" + eq = Equilibrium( + L=2, + M=2, + N=2, + electron_density=PowerSeriesProfile([1e19, 0, -1e19]), + electron_temperature=PowerSeriesProfile([1e3, 0, -1e3]), + current=PowerSeriesProfile([1, 0, -1]), + ) + obj = ObjectiveFunction(FusionPower(eq)) + obj.build() + g = obj.grad(obj.x(eq)) + assert not np.any(np.isnan(g)), "fusion power" + @pytest.mark.unit def test_objective_no_nangrad_heating_power(self): """HeatingPowerISS04.""" From 9dabc7c0b593772b76faaadb6a6988ed4eddf691 Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Fri, 23 Aug 2024 11:49:46 -0600 Subject: [PATCH 05/11] fix string arg issue --- desc/objectives/_power_balance.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/desc/objectives/_power_balance.py b/desc/objectives/_power_balance.py index 14c3e5849e..cc981dad32 100644 --- a/desc/objectives/_power_balance.py +++ b/desc/objectives/_power_balance.py @@ -131,7 +131,6 @@ def build(self, use_jit=True, verbose=1): self._constants = { "profiles": get_profiles(self._data_keys, obj=eq, grid=self._grid), "transforms": get_transforms(self._data_keys, obj=eq, grid=self._grid), - "fuel": self._fuel, } timer.stop("Precomputing transforms") @@ -170,7 +169,7 @@ def compute(self, params, constants=None): params=params, transforms=constants["transforms"], profiles=constants["profiles"], - fuel=constants["fuel"], + fuel=self.fuel, ) return data["P_fusion"] From 67abe7fdb00e453bccfec6b8c0f324bc11f9dc09 Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Fri, 23 Aug 2024 11:55:13 -0600 Subject: [PATCH 06/11] update master_compute_data --- tests/inputs/master_compute_data_rpz.pkl | Bin 8240192 -> 8245396 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/inputs/master_compute_data_rpz.pkl b/tests/inputs/master_compute_data_rpz.pkl index 5e54166e05138cf75a6e0a775a95b493c3bd244a..e3e0131d58fb7e40942a75d19dce3fb2e55e2eef 100644 GIT binary patch delta 41253 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 Date: Sun, 25 Aug 2024 18:46:43 -0600 Subject: [PATCH 07/11] remove unused dependency --- desc/compute/_equil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desc/compute/_equil.py b/desc/compute/_equil.py index c681c530a8..44975de7ac 100644 --- a/desc/compute/_equil.py +++ b/desc/compute/_equil.py @@ -856,7 +856,7 @@ def _P_ISS04(params, transforms, profiles, data, **kwargs): transforms={"grid": []}, profiles=[], coordinates="", - data=["rho", "ni", "", "sqrt(g)"], + data=["ni", "", "sqrt(g)"], resolution_requirement="rtz", fuel="str: Fusion fuel, assuming a 50/50 mix. One of {'DT'}. Default is 'DT'.", ) From 21fcf3f64d06086fd668a5d2c850b39758e2784c Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Sun, 25 Aug 2024 20:19:14 -0600 Subject: [PATCH 08/11] exclude new quantities from axis limit tests --- tests/test_axis_limits.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_axis_limits.py b/tests/test_axis_limits.py index 5c38730326..de9ad815ac 100644 --- a/tests/test_axis_limits.py +++ b/tests/test_axis_limits.py @@ -25,9 +25,9 @@ # d²ψ/(dρ)² and 𝜕√𝑔/𝜕𝜌 are both finite nonzero at the magnetic axis. # Also, dⁿψ/(dρ)ⁿ for n > 3 is assumed zero everywhere. zero_limits = {"rho", "psi", "psi_r", "e_theta", "sqrt(g)", "B_t"} -# "current Redl" and "P_ISS04" need special treatment because they are not defined for -# all configurations (giving NaN values) -not_continuous_limits = {"current Redl", "P_ISS04"} +# These compute quantities require kinetic profiles, which are not defined for all +# configurations (giving NaN values) +not_continuous_limits = {"current Redl", "P_ISS04", "P_fusion", ""} not_finite_limits = { "D_Mercier", "D_geodesic", From 447f17e2ac6c1e756d101531b838db63f02e86cb Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Sun, 25 Aug 2024 21:24:44 -0600 Subject: [PATCH 09/11] remove unused dependencies --- desc/compute/_profiles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desc/compute/_profiles.py b/desc/compute/_profiles.py index 290512d851..60a55125af 100644 --- a/desc/compute/_profiles.py +++ b/desc/compute/_profiles.py @@ -1918,9 +1918,9 @@ def _shear(params, transforms, profiles, data, **kwargs): units_long="cubic meters / second", description="Thermal reactivity from Bosch-Hale parameterization", dim=1, - params=["Ti_l"], + params=[], transforms={"grid": []}, - profiles=["ion_temperature"], + profiles=[], coordinates="r", data=["Ti"], fuel="str: Fusion fuel, assuming a 50/50 mix. One of {'DT'}. Default is 'DT'.", From 3a0bbc4e2b7f4d75c3fae43e5f26f163c6c47318 Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Mon, 26 Aug 2024 16:48:57 -0600 Subject: [PATCH 10/11] update default target and error checking --- desc/objectives/_power_balance.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/desc/objectives/_power_balance.py b/desc/objectives/_power_balance.py index 4d23de063c..bea66ebbe9 100644 --- a/desc/objectives/_power_balance.py +++ b/desc/objectives/_power_balance.py @@ -81,7 +81,7 @@ def __init__( fuel not in ["DT"], ValueError, f"fuel must be one of ['DT'], got {fuel}." ) if target is None and bounds is None: - target = 0 + target = 1e9 self._fuel = fuel self._grid = grid super().__init__( @@ -113,6 +113,11 @@ def build(self, use_jit=True, verbose=1): ValueError, "Equilibrium must have an electron density profile.", ) + errorif( + eq.ion_temperature is None, + ValueError, + "Equilibrium must have an ion temperature profile.", + ) if self._grid is None: self._grid = QuadratureGrid( L=eq.L_grid, From 60d61c9f59068271ebc1cf769f00589e4baf947d Mon Sep 17 00:00:00 2001 From: daniel-dudt Date: Mon, 26 Aug 2024 18:01:42 -0600 Subject: [PATCH 11/11] update default target docs --- desc/objectives/_power_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desc/objectives/_power_balance.py b/desc/objectives/_power_balance.py index bea66ebbe9..74b679ee2a 100644 --- a/desc/objectives/_power_balance.py +++ b/desc/objectives/_power_balance.py @@ -26,11 +26,11 @@ class FusionPower(_Objective): Equilibrium that will be optimized to satisfy the Objective. target : {float, ndarray}, optional Target value(s) of the objective. Only used if bounds is None. - Must be broadcastable to Objective.dim_f. Defaults to ``target=0``. + Must be broadcastable to Objective.dim_f. Defaults to ``target=1e9``. bounds : tuple of {float, ndarray}, optional Lower and upper bounds on the objective. Overrides target. Both bounds must be broadcastable to to Objective.dim_f. - Defaults to ``target=0``. + Defaults to ``target=1e9``. weight : {float, ndarray}, optional Weighting to apply to the Objective, relative to other Objectives. Must be broadcastable to to Objective.dim_f