diff --git a/examples/example01 - static_inverse_solve_MASTU.ipynb b/examples/example01 - static_inverse_solve_MASTU.ipynb index afdf7dcb..ef2a2cf5 100644 --- a/examples/example01 - static_inverse_solve_MASTU.ipynb +++ b/examples/example01 - static_inverse_solve_MASTU.ipynb @@ -151,14 +151,18 @@ "\n", "We can now instatiate a profile object that contains the chosen parameterisation of the toroidal plasma current density $J_p$ (i.e. on right hand side of the GS equation). We can then set the paramters for the chosen current density profiles. \n", "\n", - "A number of commonly used profile parameterisations exist in FreeGSNKE, including:\n", - "- `ConstrainPaxisIp`\n", - "- `ConstrainBetapIp`\n", - "- `Fiesta_Topeol`\n", - "- `Lao85`\n", - "- `TensionSpline`\n", - "\n", - "In this notebook, we will make use of the `ConstrainPaxisIp` (and `ConstrainBetapIp`) profiles (see [Jeon (2015)](https://link.springer.com/article/10.3938/jkps.67.843)). Others will be utilised in later notebooks. If there is a profile parameterisation you require that does not exist, please do create an issue. \n", + "The following table indicates which parameterisations are currently available in FreeGSNKE and for which types of equilibrium simulation they have been tested to work:\n", + "\n", + "| Name | Static | Evolutive (linear) | Evolutive (nonlinear) |\n", + "|-------|--------|------------------|---------------------|\n", + "| `ConstrainPaxisIp` | ✅ | ✅ | ✅ |\n", + "| `ConstrainBetapIp` | ✅ | ✅ | ✅ |\n", + "| `FiestaTopeol` | ✅ | ✅ | ✅ |\n", + "| `Lao85` | ✅ | ✅ | ✅ |\n", + "| `TensionSpline` | ✅ | ❌ | ❌ |\n", + "| `GeneralPprimeFFprime` | ✅ | ❌ | ❌ |\n", + "\n", + "In this notebook, we will make use of the `ConstrainPaxisIp` (and `ConstrainBetapIp`) profiles (see [Jeon (2015)](https://link.springer.com/article/10.3938/jkps.67.843)). Others will be utilised in later notebooks. If there is a profile parameterisation you require that does not exist, please do create an issue.\n", "\n", "Both `ConstrainPaxisIp` and `ConstrainBetapIp` are parameterised as follows:\n", " $$J_{p}(\\psi, R, Z) = \\lambda\\big[ \\beta_{0} \\frac{R}{R_{0}} \\left( 1-\\tilde{\\psi}^{\\alpha_m} \\right)^{\\alpha_n} + (1-\\beta_{0}) \\frac{R_0}{R} \\left( 1-\\tilde{\\psi}^{\\alpha_m} \\right)^{\\alpha_n} \\big] \\quad (R,Z) \\in \\Omega_p,$$\n", diff --git a/examples/example02 - static_forward_solve_MASTU.ipynb b/examples/example02 - static_forward_solve_MASTU.ipynb index a7a24ceb..856a1ddf 100644 --- a/examples/example02 - static_forward_solve_MASTU.ipynb +++ b/examples/example02 - static_forward_solve_MASTU.ipynb @@ -599,6 +599,117 @@ "print(f\"Original beta_0 = {profiles_topeol.Beta0} vs. Fitted from Lao85 = {beta_0}.\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Supposing an explicit parameterisation of the profiles is unknown and you have access to data describing the profiles. The `GeneralPprimeFFprime` profiles class enables you to use the raw data. \n", + "\n", + "As always the plasma current density is given by:\n", + "\n", + " $$J_{p}(\\psi, R, Z) = \\lambda\\big[ \\frac{R}{R_{0}} p'(\\tilde{\\psi}) + \\frac{R_0}{R} \\frac{1}{\\mu_0} F F'(\\tilde{\\psi}) \\big] \\quad (R,Z) \\in \\Omega_p, $$\n", + "\n", + "where the pressure, $p(\\tilde{\\psi})$, and toroidal magnetic field, $F(\\tilde{\\psi})$, profiles are now given by data arrays. \n", + "\n", + "By passing these data arrays either directly as `pprime_data` or `p_data` (and also `ffprime_data` or `f_data`), FreeGSNKE will interpolate the data and use them directly in the plasma current density above. \n", + "\n", + "The required parameters are:\n", + "- `Ip` (total plasma current).\n", + "- `fvac` ($rB_{tor}$, vacuum toroidal field strength).\n", + "- `psi_n` ($\\tilde{\\psi}$, normalised poloidal flux at which data arrays corresponding, should be increasing).\n", + "- `pprime_data` or `p_data` (array of $p'(\\tilde{\\psi})$ or $p(\\tilde{\\psi})$ values at the normalised poloidal flux values in `psi_n`).\n", + "- `ffprime_data` or `f_data` (array of $FF'(\\tilde{\\psi})$ or $F(\\tilde{\\psi})$ values at the normalised poloidal flux values in `psi_n`).\n", + "- `Ip_logic` (if False, `Ip` is not used, if True, `Ip` is used to normalise $J_p$ and find $\\lambda$)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from freegsnke.jtor_update import GeneralPprimeFFprime\n", + "\n", + "psi_n = np.linspace(0,1,11)\n", + "\n", + "pprime_data = np.array([208061.72441505, 204110.95531309, 194359.61041113, 179811.09692964,\n", + " 161053.35395914, 138602.79928382, 113000.64896365, 84893.04354249,\n", + " 55164.04720078, 25298.53358472, 0. ])\n", + "\n", + "ffprime_data = np.array([0.58961295, 0.57841712, 0.5507834 , 0.50955529, 0.45639891,\n", + " 0.3927777 , 0.32022539, 0.24057302, 0.1563259 , 0.07169191,\n", + " 0. ])\n", + "\n", + "p_data = np.array([126175.75656716, 105510.77120406, 85544.30596718, 66798.45790761,\n", + " 49722.32952766, 34710.90959473, 22106.93373 , 12194.53794856,\n", + " 5182.93833357, 1168.13190975, 0. ])\n", + "\n", + "f_data = np.array([0.98240673, 0.92086948, 0.85722645, 0.79283784, 0.72925299,\n", + " 0.66837871, 0.61261316, 0.56490234, 0.52855955, 0.50657733,\n", + " 0.5 ])\n", + "\n", + "\n", + "# the following three setups are equivalent\n", + "profiles_general = GeneralPprimeFFprime(\n", + " eq=eq,\n", + " Ip=6e5,\n", + " fvac=0.5,\n", + " psi_n=psi_n,\n", + " pprime_data=pprime_data,\n", + " ffprime_data=ffprime_data,\n", + " p_data=p_data,\n", + " f_data=f_data,\n", + ")\n", + "\n", + "# profiles_general = GeneralPprimeFFprime(\n", + "# eq=eq,\n", + "# Ip=6e5,\n", + "# fvac=0.5,\n", + "# psi_n=psi_n,\n", + "# pprime_data=pprime_data,\n", + "# ffprime_data=ffprime_data,\n", + "# p_data=None,\n", + "# f_data=None,\n", + "# )\n", + "\n", + "# profiles_general = GeneralPprimeFFprime(\n", + "# eq=eq,\n", + "# Ip=6e5,\n", + "# fvac=0.5,\n", + "# psi_n=psi_n,\n", + "# pprime_data=None,\n", + "# ffprime_data=None,\n", + "# p_data=p_data,\n", + "# f_data=f_data,\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# instatiate new equilibrium object\n", + "eq_general = deepcopy(eq)\n", + "\n", + "# call solver with new profile object\n", + "GSStaticSolver.solve(eq=eq_general, \n", + " profiles=profiles_general, \n", + " constrain=None, \n", + " target_relative_tolerance=1e-9)\n", + "\n", + "\n", + "# plot the resulting equilbria \n", + "fig1, ax1 = plt.subplots(1, 1, figsize=(4, 8), dpi=80)\n", + "ax1.grid(True, which='both')\n", + "eq_general.plot(axis=ax1, show=False)\n", + "eq_general.tokamak.plot(axis=ax1, show=False)\n", + "ax1.set_xlim(0.1, 2.15)\n", + "ax1.set_ylim(-2.25, 2.25)\n", + "plt.tight_layout()" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/example03 - extracting_equilibrium_quantites.ipynb b/examples/example03 - extracting_equilibrium_quantites.ipynb index 5f54f242..7cfa0f9c 100644 --- a/examples/example03 - extracting_equilibrium_quantites.ipynb +++ b/examples/example03 - extracting_equilibrium_quantites.ipynb @@ -906,7 +906,7 @@ "metadata": {}, "outputs": [], "source": [ - "# plot the input p' and FF' profiles\n", + "# plot the p' and FF' profiles\n", "\n", "psi_n = eq.psiN_1D(N=65)\n", "\n", @@ -929,15 +929,21 @@ "metadata": {}, "outputs": [], "source": [ - "# plot q profile\n", + "# plot the p and F profiles\n", "\n", - "psi_n = np.linspace(0.01,0.99,65) # values of q at 0 and 1 can be problematic\n", + "psi_n = eq.psiN_1D(N=65)\n", "\n", - "fig1, ax1 = plt.subplots(1, 1, figsize=(6,6), dpi=80)\n", + "fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,6), dpi=80)\n", "ax1.grid(zorder=0, alpha=0.75)\n", - "ax1.plot(psi_n, eq.q(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n", + "ax1.plot(psi_n, profiles.pressure(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n", "ax1.set_xlabel(r'$\\hat{\\psi}$')\n", - "ax1.set_ylabel(r\"$q(\\hat{\\psi})$\")\n" + "ax1.set_ylabel(r\"$p(\\hat{\\psi})$\")\n", + "ax1.ticklabel_format(axis='y', scilimits=(0,0))\n", + "\n", + "ax2.grid(zorder=0, alpha=0.75)\n", + "ax2.plot(psi_n, profiles.fpol(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n", + "ax2.set_xlabel(r'$\\hat{\\psi}$')\n", + "ax2.set_ylabel(r\"$F(\\hat{\\psi})$\")\n" ] }, { @@ -946,14 +952,15 @@ "metadata": {}, "outputs": [], "source": [ - "# plot fpol\n", + "# plot q profile\n", + "\n", + "psi_n = np.linspace(0.01,0.99,65) # values of q at 0 and 1 can be problematic\n", "\n", "fig1, ax1 = plt.subplots(1, 1, figsize=(6,6), dpi=80)\n", "ax1.grid(zorder=0, alpha=0.75)\n", - "ax1.plot(psi_n, eq.fpol(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n", + "ax1.plot(psi_n, eq.q(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n", "ax1.set_xlabel(r'$\\hat{\\psi}$')\n", - "ax1.set_ylabel(r\"$fpol(\\hat{\\psi})$\")\n", - "\n" + "ax1.set_ylabel(r\"$q(\\hat{\\psi})$\")\n" ] }, { diff --git a/examples/example05 - evolutive_forward_solve.ipynb b/examples/example05 - evolutive_forward_solve.ipynb index e9c3435e..d04a0efd 100644 --- a/examples/example05 - evolutive_forward_solve.ipynb +++ b/examples/example05 - evolutive_forward_solve.ipynb @@ -11,13 +11,11 @@ "To do this, we need to:\n", "- build the tokamak machine.\n", "- instatiate a GS equilibrium (to be used as an initial condition for the evolutive solver).\n", - "- calculate a vertical instability growth rate for this equilibrium and carry out passive structure mode removal via a normal mode decomposition (i.e. removing modes that have little effect on the evolution). \n", - "- define time-dependent plasma current density profile parameters and coil voltages.\n", + "- calculate a vertical instability growth rate for this equilibrium and carry out passive structure mode removal via a normal mode decomposition (i.e. removing modes that have little effect on the evolution). More details on this in a later notebook. \n", + "- define time-dependent coil voltages, plasma current density profile parameters, and plasma resistivity.\n", "- evolve the active coil currents, the total plasma current, and the equilbirium using these profile parameters and voltages by solving the circuit equations alongside the GS equation.\n", "\n", - "Refer to the paper by [Amorisco et al. (2024)](https://pubs.aip.org/aip/pop/article/31/4/042517/3286904/FreeGSNKE-A-Python-based-dynamic-free-boundary) for more details. \n", - "\n", - "We should note that here we will use **fixed** (time-independent) profile parameters and voltages to simulate a VDE, however, truly time-dependent parameters would be required to simulate a plasma shot (see future notebooks). \n" + "Refer to the paper by [Amorisco et al. (2024)](https://pubs.aip.org/aip/pop/article/31/4/042517/3286904/FreeGSNKE-A-Python-based-dynamic-free-boundary) for more details." ] }, { @@ -37,7 +35,8 @@ "import matplotlib.pyplot as plt\n", "from copy import deepcopy\n", "from IPython.display import display, clear_output\n", - "import pickle" + "import pickle\n", + "import copy" ] }, { @@ -220,11 +219,11 @@ "- `min_dIy_dI`: threshold value below which passive structure normal modes are dropped. Modes with norm(d(Iy)/dI)<`min_dIy_dI` are dropped, which filters out modes that do not actually couple with the plasma.\n", "- `max_mode_frequency`: threshold value for characteristic frequencies above which passive structure normal modes are dropped (i.e. the fast modes).\n", "\n", - "Other customisable inputs are available, do see the documentation for more details. For example, one may explicitly set your own resistance and inductance matries for the tokamak machine. \n", + "Other customisable inputs are available, do see the documentation or the later notebook on \"Growth Rates\" for more details. For example, one may explicitly set your own resistance and inductance matries for the tokamak, rather than the geometrical value calculated internally in FreeGSNKE.\n", "\n", "The solver can be used on different equilibria and/or profiles, but these need to have the same machine, domain, and limiter as the one used at the time of the solver instantiation. For different machines, a new time-evolutive solver should be created.\n", "\n", - "The input equilibrium and profile functions are also used as the expansion point around which the dynamics are linearised." + "The input equilibrium and profile functions are also used as the expansion point around which the dynamics are linearised (when using the linear solver or calculating linear growth rates). " ] }, { @@ -245,44 +244,6 @@ ")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Set active coil voltages\n", - "\n", - "In this example, we will evolve a plasma in absence of any control policy or current drive.\n", - "\n", - "Just as an example, the following calculates active voltages to be applied to the poloidal field coils (and Solenoid) using $V = RI$, with current values as defined by the initial equilibrium (i.e. we have constant voltages).\n", - "\n", - "In most FreeGSNKE use cases, these active voltages will be determined by a control policy." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "voltages = (stepping.vessel_currents_vec*stepping.evol_metal_curr.R)[:stepping.evol_metal_curr.n_active_coils] " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To start, the solver is prepared by setting the initial conditions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "stepping.initialize_from_ICs(eq, profiles)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -312,8 +273,27 @@ "history_triangularity = [stepping.eq1.triangularity()]\n", "history_squareness = [stepping.eq1.squareness()[1]]\n", "history_area = [stepping.eq1.separatrix_area()]\n", - "history_length = [stepping.eq1.separatrix_length()]\n", - "\n" + "history_length = [stepping.eq1.separatrix_length()]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Set time-dependent active coil voltages, profile parameters, and plasma resistivity\n", + "\n", + "To simulate the VDE in the example, we will evolve a plasma in absence of any control policy or current drive. The following calculates active voltages to be applied to the poloidal field coils (and Solenoid) using $V = RI$, with current values as defined by the initial equilibrium (i.e. we have constant voltages over time). For more realistic plasma simulation, these active voltages would be truly time-dependent and determined by a control policy.\n", + "\n", + "In this example, the profile parameters and the plasma resistivity will also remain constant over time. In the next example, we will show how to vary these." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "voltages = (stepping.vessel_currents_vec*stepping.evol_metal_curr.R)[:stepping.evol_metal_curr.n_active_coils] " ] }, { @@ -323,7 +303,7 @@ "#### Call the solver (linear)\n", "Finally, we call the time-evolutive solver with `stepping.nlstepper()` sequentially until we reach the preset end time.\n", "\n", - "The following demonstrates a solely linear evolution of the plasma by setting `linear_only=True`." + "The following demonstrates a solely linear evolution of the plasma by setting `linear_only=True`. This makes use of the Jacobians calculated when the nonlinear solver object was initialised. Note that the linear solution will only hold for equilibrium \"close to\" that which was used to initialise the nonlinear solver object. " ] }, { @@ -332,15 +312,20 @@ "metadata": {}, "outputs": [], "source": [ + "# initialise the solver with the initial equilibrium/profiles\n", + "stepping.initialize_from_ICs(eq, profiles)\n", + "\n", "# loop over time steps\n", "while counter 0.5 + ).any(): + + dIydtheta, rel_ndIy = self.build_dIydtheta( + profiles=profiles_copy, + rtol_NK=target_relative_tolerance_linearization, + verbose=verbose, + ) + + else: + self.final_dtheta_record = 1.0 * self.starting_dtheta + + self.dIydtheta = np.copy(dIydtheta) + self.dIydtheta_ICs = np.copy(self.dIydtheta) + else: + self.dIydtheta = np.copy(self.dIydtheta_ICs) + else: + self.dIydtheta = dIydtheta + self.dIydtheta_ICs = np.copy(self.dIydtheta) + def set_plasma_resistivity(self, plasma_resistivity): """Function to set the resistivity of the plasma. self.plasma_resistance_1d is the diagonal of the matrix R_yy, the plasma resistance matrix. @@ -1294,25 +1681,66 @@ def get_profiles_values(self, profiles): # note the parameters here are the same that should be provided # to the stepper if these are time evolving if self.profiles_type == "ConstrainPaxisIp": + self.n_profiles_parameters = 3 self.profiles_parameters = { - "paxis": profiles.paxis, "alpha_m": profiles.alpha_m, "alpha_n": profiles.alpha_n, + "paxis": profiles.paxis, } + self.profiles_param = "paxis" + self.profiles_parameters_vec = np.array( + [profiles.alpha_m, profiles.alpha_n, profiles.paxis] + ) elif self.profiles_type == "ConstrainBetapIp": + self.n_profiles_parameters = 3 self.profiles_parameters = { - "betap": profiles.betap, "alpha_m": profiles.alpha_m, "alpha_n": profiles.alpha_n, + "betap": profiles.betap, } + self.profiles_param = "betap" + self.profiles_parameters_vec = np.array( + [profiles.alpha_m, profiles.alpha_n, profiles.betap] + ) elif self.profiles_type == "Fiesta_Topeol": + self.n_profiles_parameters = 3 self.profiles_parameters = { - "beta0": profiles.beta0, "alpha_m": profiles.alpha_m, "alpha_n": profiles.alpha_n, + "Beta0": profiles.Beta0, } + self.profiles_param = "Beta0" + self.profiles_parameters_vec = np.array( + [profiles.alpha_m, profiles.alpha_n, profiles.Beta0] + ) elif self.profiles_type == "Lao85": + self.n_profiles_parameters_alpha = len(profiles.alpha) + self.n_profiles_parameters_beta = len(profiles.beta) + if profiles.alpha_logic: + self.n_profiles_parameters_alpha -= 1 + if profiles.beta_logic: + self.n_profiles_parameters_beta -= 1 + self.n_profiles_parameters = ( + self.n_profiles_parameters_alpha + self.n_profiles_parameters_beta + ) + + self.profiles_alpha_indices = slice(0, self.n_profiles_parameters_alpha) + alpha_shift = 0 + if profiles.alpha_logic: + alpha_shift += 1 + + self.profiles_beta_indices = slice( + self.n_profiles_parameters_alpha + alpha_shift, + self.n_profiles_parameters_alpha + + alpha_shift + + self.n_profiles_parameters_beta, + ) + self.profiles_parameters = {"alpha": profiles.alpha, "beta": profiles.beta} + self.profiles_param = None + self.profiles_parameters_vec = np.concatenate( + (profiles.alpha, profiles.beta) + ) def get_vessel_currents(self, eq): """Uses the input equilibrium to extract values for all metal currents, @@ -1325,9 +1753,6 @@ def get_vessel_currents(self, eq): Initial equilibrium. eq.tokamak is used to extract current values. """ self.vessel_currents_vec = eq.tokamak.getCurrentsVec() - # eq_currents = eq.tokamak.getCurrents() - # for i, labeli in enumerate(self.coils_order): - # self.vessel_currents_vec[i] = eq_currents[labeli] def build_current_vec(self, eq, profiles): """Builds the vector of currents in which the dynamics is actually solved, self.currents_vec @@ -1361,6 +1786,7 @@ def initialize_from_ICs( profiles, target_relative_tolerance_linearization=1e-7, dIydI=None, + dIydtheta=None, force_core_mask_linearization=False, verbose=False, ): @@ -1446,6 +1872,7 @@ def initialize_from_ICs( self.eq1, self.profiles1, dIydI=dIydI, + dIydtheta=dIydtheta, target_relative_tolerance_linearization=target_relative_tolerance_linearization, force_core_mask_linearization=force_core_mask_linearization, verbose=verbose, @@ -1457,7 +1884,10 @@ def initialize_from_ICs( # transfer linearization to linear solver: self.Myy_hatIy0 = self.handleMyy.dot(self.hatIy) self.linearised_sol.set_linearization_point( - dIydI=self.dIydI_ICs, hatIy0=self.blended_hatIy, Myy_hatIy0=self.Myy_hatIy0 + dIydI=self.dIydI_ICs, + dIydtheta=self.dIydtheta_ICs, + hatIy0=self.blended_hatIy, + Myy_hatIy0=self.Myy_hatIy0, ) def step_complete_assign(self, working_relative_tol_GS, from_linear=False): @@ -1861,9 +2291,9 @@ def check_and_change_profiles(self, profiles_parameters=None): for par in profiles_parameters: setattr(self.profiles1, par, profiles_parameters[par]) setattr(self.profiles2, par, profiles_parameters[par]) - if self.profiles_type == "Lao85": - self.profiles1.initialize_profile() - self.profiles2.initialize_profile() + if self.profiles_type == "Lao85": + self.profiles1.initialize_profile() + self.profiles2.initialize_profile() self.profiles_change_flag = 1 def nlstepper( @@ -1974,12 +2404,34 @@ def nlstepper( NK iterations are interrupted when this limit is surpassed. """ + # retrieve the old profile parameter values + self.get_profiles_values(self.profiles1) + old_params = self.profiles_parameters_vec + # check if profiles parameters are being evolved # and action the change where necessary self.check_and_change_profiles( profiles_parameters=profiles_parameters, ) + # retrieve the new profile parameter values (if present) + self.get_profiles_values(self.profiles1) + new_params = self.profiles_parameters_vec + + # calculate change in profiles across timestep: (profiles(t+dt)-profiles(t))/dt + # should be zero if no change + if self.profiles_type == "Lao85": + old_alphas = old_params[self.profiles_alpha_indices] + old_betas = old_params[self.profiles_beta_indices] + new_alphas = new_params[self.profiles_alpha_indices] + new_betas = new_params[self.profiles_beta_indices] + dtheta_dt = ( + np.concatenate((new_alphas, new_betas)) + - np.concatenate((old_alphas, old_betas)) + ) / self.dt_step + else: + dtheta_dt = (new_params - old_params) / self.dt_step + # check if plasma resistivity is being evolved # and action the change where necessary self.check_and_change_plasma_resistivity( @@ -1989,7 +2441,10 @@ def nlstepper( # solves the linearised problem for the currents and assigns # results in preparation for the nonlinear calculations # Solution and GS equilibrium are assigned to self.trial_currents and self.trial_plasma_psi - self.set_linear_solution(active_voltage_vec) + self.set_linear_solution( + active_voltage_vec=active_voltage_vec, + dtheta_dt=dtheta_dt, + ) # check Matrix is still applicable myy_flag = self.handleMyy.check_Myy(self.hatIy)