diff --git a/freegsnke/GSstaticsolver.py b/freegsnke/GSstaticsolver.py
index 07f20d9..e37372f 100644
--- a/freegsnke/GSstaticsolver.py
+++ b/freegsnke/GSstaticsolver.py
@@ -769,7 +769,7 @@ def optimize_currents(
currents = np.copy(self.dummy_current)
currents[i] = 1.0 * delta_current[i]
currents = full_current_vec + constrain.rebuild_full_current_vec(currents)
- self.eq2 = deepcopy(eq)
+ self.eq2 = eq.create_auxiliary_equilibrium()
self.eq2.tokamak.set_all_coil_currents(currents)
self.forward_solve(
eq=self.eq2,
diff --git a/freegsnke/build_machine.py b/freegsnke/build_machine.py
index 4731c52..d742ef0 100644
--- a/freegsnke/build_machine.py
+++ b/freegsnke/build_machine.py
@@ -587,6 +587,21 @@ def build_active_coil_dict(active_coils):
return coils_dict
+def copy_tokamak(tokamak: Machine):
+ new_tokamak = tokamak.copy()
+
+ new_tokamak.coils_dict = tokamak.coils_dict.copy()
+ new_tokamak.coils_list = tokamak.coils_list[::]
+ new_tokamak.n_active_coils = tokamak.n_active_coils
+ new_tokamak.n_passive_coils = tokamak.n_passive_coils
+ new_tokamak.n_coils = tokamak.n_coils
+
+ # add probe object attribute to tokamak (not strictly required)
+ new_tokamak.probes = tokamak.probes
+
+ return new_tokamak
+
+
if __name__ == "__main__":
for coil_name in active_coils:
print([pol for pol in active_coils[coil_name]])
diff --git a/freegsnke/equilibrium_update.py b/freegsnke/equilibrium_update.py
index 266b877..ad6778f 100644
--- a/freegsnke/equilibrium_update.py
+++ b/freegsnke/equilibrium_update.py
@@ -28,6 +28,7 @@
from scipy import interpolate
from . import limiter_func, virtual_circuits
+from .build_machine import copy_tokamak
class Equilibrium(freegs4e.equilibrium.Equilibrium):
@@ -62,6 +63,58 @@ def __init__(self, *args, **kwargs):
float
)
+ def create_auxiliary_equilibrium(self):
+ """Creates the auxiliary equilibrium object.
+
+ The auxiliary object returned from this method is essentially
+ a copy of the equilibrium object (self) however it is manually
+ setup and so won't contain all attributes on self (especially custom
+ attributes). It is NOT _guaranteed_ to be the same as a deepcopy, or even
+ a shallow copy.
+ """
+ # __new__ stops __init__ being called.
+ # This is necessary because the __init__ method does expensive
+ # calculations which we can just copy the results of
+ equilibrium = Equilibrium.__new__(Equilibrium)
+
+ # attributes that FreeGS4e sets
+ equilibrium.tokamak = copy_tokamak(self.tokamak)
+ equilibrium.Rmin = self.Rmin
+ equilibrium.Rmax = self.Rmax
+ equilibrium.Zmin = self.Zmin
+ equilibrium.Zmax = self.Zmax
+ equilibrium.nx = self.nx
+ equilibrium.ny = self.ny
+ equilibrium.dR = self.dR
+ equilibrium.dZ = self.dZ
+ equilibrium._applyBoundary = self._applyBoundary
+ equilibrium._pgreen = self._pgreen
+ equilibrium._vgreen = self._vgreen
+ equilibrium._current = self._current
+ equilibrium.order = self.order
+ equilibrium._solver = self._solver
+
+ # attributes the FreeGSNKE sets
+ equilibrium.solved = self.solved
+ equilibrium.psi_func_interp = self.psi_func_interp
+ equilibrium.nxh = self.nxh
+ equilibrium.nyh = self.nyh
+ equilibrium.Rnxh = self.Rnxh
+ equilibrium.Znyh = self.Znyh
+ equilibrium.limiter_handler = self.limiter_handler # should be safe not to copy
+
+ # attributes that actually need to be copied
+ equilibrium.R_1D = np.copy(self.R_1D)
+ equilibrium.Z_1D = np.copy(self.Z_1D)
+ equilibrium.R = np.copy(self.R)
+ equilibrium.Z = np.copy(self.Z)
+ equilibrium.tokamak_psi = np.copy(self.tokamak_psi)
+ equilibrium.plasma_psi = np.copy(self.plasma_psi)
+ equilibrium.mask_inside_limiter = np.copy(self.mask_inside_limiter)
+ equilibrium.mask_outside_limiter = np.copy(self.mask_outside_limiter)
+
+ return equilibrium
+
def adjust_psi_plasma(
self,
):
diff --git a/freegsnke/nonlinear_solve.py b/freegsnke/nonlinear_solve.py
index afcf04a..835edd5 100644
--- a/freegsnke/nonlinear_solve.py
+++ b/freegsnke/nonlinear_solve.py
@@ -197,9 +197,9 @@ def __init__(
print("-----")
# set internal copy of the equilibrium and profile
- self.eq1 = deepcopy(eq)
+ self.eq1 = eq.create_auxiliary_equilibrium()
self.profiles1 = deepcopy(profiles)
- self.eq2 = deepcopy(eq)
+ self.eq2 = eq.create_auxiliary_equilibrium()
self.profiles2 = deepcopy(profiles)
self.Iy = self.limiter_handler.Iy_from_jtor(profiles.jtor).copy()
self.nIy = np.linalg.norm(self.Iy)
@@ -2016,7 +2016,7 @@ def initialize_from_ICs(
# set internal copy of the equilibrium and profile
# note that at this stage, the equilibrium may have vessel currents.
# These can not be reproduced exactly if modes are truncated.
- self.eq1 = deepcopy(eq)
+ self.eq1 = eq.create_auxiliary_equilibrium()
self.profiles1 = deepcopy(profiles)
# The pair self.eq1 and self.profiles1 is the pair that is advanced at each timestep.
# Their properties evolve according to the dynamics.
@@ -2043,7 +2043,7 @@ def initialize_from_ICs(
# self.eq2 and self.profiles2 are used as auxiliary objects when solving for the dynamics
# They are used for all intermediate calculations, so
# they should not be used to extract properties of the evolving equilibrium
- self.eq2 = deepcopy(self.eq1)
+ self.eq2 = self.eq1.create_auxiliary_equilibrium()
self.profiles2 = deepcopy(self.profiles1)
# self.Iy is the istantaneous 1d vector representing the plasma current distribution
@@ -2125,7 +2125,8 @@ def step_complete_assign(self, working_relative_tol_GS, from_linear=False):
if from_linear:
self.profiles1 = deepcopy(self.profiles2)
- self.eq1 = deepcopy(self.eq2)
+ self.eq1 = self.eq2
+ self.eq2 = self.eq1.create_auxiliary_equilibrium()
else:
self.eq1.plasma_psi = np.copy(self.trial_plasma_psi)
self.profiles1.Ip = self.trial_currents[-1] * self.plasma_norm_factor
diff --git a/freegsnke/passive_structure.py b/freegsnke/passive_structure.py
index 4727ab9..0a3788f 100644
--- a/freegsnke/passive_structure.py
+++ b/freegsnke/passive_structure.py
@@ -15,9 +15,9 @@
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-
+
You should have received a copy of the GNU Lesser General Public License
-along with FreeGSNKE. If not, see .
+along with FreeGSNKE. If not, see .
"""
import freegs4e
@@ -83,6 +83,36 @@ def __init__(
self.greens = {}
+ def copy(self):
+ # dont instantiate the new object, it will be slow
+ new_obj = type(self).__new__(type(self))
+
+ new_obj.turns = self.turns
+ new_obj.control = self.turns
+ new_obj.current = self.current
+ new_obj.refine_mode = self.refine_mode
+
+ # ASSUMING the shape will never be modified in-place
+ new_obj.area = self.area
+ new_obj.R = self.R
+ new_obj.Z = self.Z
+ new_obj.Len = self.Len
+ new_obj.Rpolygon = self.Rpolygon
+ new_obj.Zpolygon = self.Zpolygon
+ new_obj.vertices = self.vertices
+ new_obj.polygon = self.polygon
+ new_obj.n_refine = self.n_refine
+ new_obj.filaments = self.filaments
+
+ # This performs a shallow copy of the greens dictionary.
+ # This implicitly assumes that the dictionary might be modified
+ # e.g. self.greens["psi"] = new_array (this would be fine)
+ # but its values WON't be modified in place
+ # e.g. self.greens["psi"][:] = new_array (this would cause problems)
+ new_obj.greens = self.greens.copy()
+
+ return new_obj
+
def create_RZ_key(self, R, Z):
"""
Produces tuple (Rmin,Rmax,Zmin,Zmax,nx,ny) to access correct dictionary entry of greens function.
diff --git a/freegsnke/virtual_circuits.py b/freegsnke/virtual_circuits.py
index 5479ba2..184e007 100644
--- a/freegsnke/virtual_circuits.py
+++ b/freegsnke/virtual_circuits.py
@@ -646,7 +646,7 @@ def calculate_VC(
# make copies of the newly solved equilibrium and profile objects
# these are used for all GS solves below
- self._eq2 = deepcopy(eq)
+ self._eq2 = eq.create_auxiliary_equilibrium()
self._profiles2 = deepcopy(profiles)
# for each coil, prepare by inferring delta(I_j) corresponding to a change delta(I_y)
@@ -656,7 +656,6 @@ def calculate_VC(
print(
f"{j}th coil ({coils[j]}) using initial current shift {starting_dI[j]}."
)
- # self._eq2 = deepcopy(eq)
self.prepare_build_dIydI_j(j, coils, target_dIy, starting_dI[j])
if verbose:
@@ -666,7 +665,6 @@ def calculate_VC(
# for each coil, build the Jacobian using the value of delta(I_j) inferred earlier
# by self.prepare_build_dIydI_j.
for j in np.arange(len(coils)):
- # self._eq2 = deepcopy(eq)
# each shape matrix row is derivative of targets wrt the final coil current change
shape_matrix[:, j] = self.build_dIydI_j(
j, coils, targets, targets_options, non_standard_targets, verbose
@@ -779,7 +777,7 @@ def apply_VC(
)
# store copies of the eq and profile objects
- eq_new = deepcopy(eq)
+ eq_new = eq.create_auxiliary_equilibrium()
profiles_new = deepcopy(profiles)
# assign currents to the required coils in the eq object
diff --git a/requirements-freegs4e.txt b/requirements-freegs4e.txt
index 2b01e26..db1fd7c 100644
--- a/requirements-freegs4e.txt
+++ b/requirements-freegs4e.txt
@@ -1 +1 @@
-freegs4e~=0.11
+freegs4e~=0.12