Skip to content

Commit b9bfcd5

Browse files
authored
Merge pull request #47 from timothy-nunn/copy-objects
Add copy methods to Machine, Circuit, Coil, and Coil subclasses
2 parents aecc363 + 69079db commit b9bfcd5

8 files changed

Lines changed: 125 additions & 17 deletions

File tree

freegs4e/coil.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ def __init__(
114114
self.control = control
115115
self.area = area
116116

117+
def copy(self):
118+
"""Creates a new object that has identical attributes to self (a copy).
119+
120+
The copy method will need to be implemented for any subclasses of Coil
121+
to ensure the correct __init__ signature is called and to ensure
122+
lists/other object attributes are copied where appropriate (not passed
123+
by reference).
124+
"""
125+
new_obj = type(self)(
126+
self.R, self.Z, self.current, self.turns, self.area
127+
)
128+
new_obj._area = self.area
129+
return new_obj
130+
117131
def psi(self, R, Z):
118132
"""
119133
Calculate poloidal flux at (R,Z)
@@ -283,11 +297,11 @@ def area(self):
283297
The cross-section area of the coil in m^2
284298
"""
285299
if isinstance(self._area, numbers.Number):
286-
assert self._area > 0
300+
assert self._area >= 0
287301
return self._area
288302
# Calculate using functor
289303
area = self._area(self)
290-
assert area > 0
304+
assert area >= 0
291305
return area
292306

293307
@area.setter

freegs4e/machine.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ def __init__(self, coils, current=0.0, control=True):
8787
self.current = current
8888
self.control = control
8989

90+
def copy(self):
91+
"""Creates a copy of the circuit by initialising a new Circuit object.
92+
93+
The coils forming the circuit are copied by calling their individual
94+
`copy` methods.
95+
"""
96+
coils = [(label, c.copy(), m) for label, c, m in self.coils]
97+
return type(self)(coils, self.current, self.control)
98+
9099
def psi(self, R, Z):
91100
"""
92101
Poloidal flux due to coils in the circuit (at chosen R and Z).
@@ -985,6 +994,14 @@ def __init__(self, coils, wall=None):
985994
for i, coil in enumerate(self.coil_names):
986995
self.coil_order[coil] = i
987996

997+
def copy(self):
998+
"""Creates a copy of the machine by initialising a new object.
999+
1000+
The coils are copied by calling the `copy` method for the coil/circuit.
1001+
"""
1002+
coils = [(label, c.copy()) for label, c in self.coils]
1003+
return type(self)(coils, self.wall)
1004+
9881005
def __repr__(self):
9891006
"""
9901007
Return a string representation of the Machine object.

freegs4e/multi_coil.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def __init__(
8181
turns=1,
8282
control=True,
8383
mirror=False,
84-
polarity=[1.0, 1.0],
84+
polarity=None,
8585
area=AreaCurrentLimit(),
8686
):
8787
"""
@@ -123,13 +123,25 @@ def __init__(
123123
self.current = current
124124
self.control = control
125125
self.mirror = mirror
126-
self.polarity = polarity
126+
self.polarity = [1.0, 1.0] if polarity is None else polarity
127127
self.area = area
128128

129129
# Internal (R,Z) value, should not be modified directly
130130
self._R_centre = np.mean(self.Rfil)
131131
self._Z_centre = np.mean(self.Zfil)
132132

133+
def copy(self):
134+
return type(self)(
135+
np.copy(self.Rfil),
136+
np.copy(self.Zfil),
137+
self.current,
138+
self.turns,
139+
self.control,
140+
self.mirror,
141+
self.polarity,
142+
self.area,
143+
)
144+
133145
def controlPsi(self, R, Z):
134146
"""
135147
Calculate poloidal flux at (R,Z) due to a unit current

freegs4e/optimise.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
from math import sqrt
2525

2626
import matplotlib.pyplot as plt
27-
from freegs.plotting import plotEquilibrium
2827

2928
from . import optimiser, picard
29+
from .plotting import plotEquilibrium
3030

3131
# Measures which operate on Equilibrium objects
3232

freegs4e/shaped_coil.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ def __init__(self, shape, current=0.0, turns=1, control=True, npoints=6):
8888
self.npoints_per_triangle = npoints
8989
self._points = quadrature.polygon_quad(shape, n=npoints)
9090

91+
def copy(self):
92+
R_centre = np.copy(self._R_centre)
93+
Z_centre = np.copy(self._Z_centre)
94+
points = np.copy(self._points)
95+
96+
new_obj = type(self)(
97+
np.copy(self.shape),
98+
self.current,
99+
self.turns,
100+
self.control,
101+
self.npoints_per_triangle,
102+
)
103+
104+
new_obj._R_centre = R_centre
105+
new_obj._Z_centre = Z_centre
106+
new_obj._points = points
107+
108+
return new_obj
109+
91110
def controlPsi(self, R, Z):
92111
"""
93112
Calculate poloidal flux at (R,Z) due to a unit current

freegs4e/test_machine.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,19 @@ def test_coil_axis():
2323
def analytic_Bz(dZ):
2424
return (mu0 / 2) * Rcoil**2 * current / (dZ**2 + Rcoil**2) ** 1.5
2525

26-
# Note: Can't evaluate at R=0,
27-
assert math.isclose(coil.Br(0.0001, 2.0), 0.0, abs_tol=1e-8)
28-
assert math.isclose(coil.Bz(0.001, 2.0), analytic_Bz(1.0), abs_tol=1e-8)
29-
assert math.isclose(coil.Bz(0.001, -1.0), analytic_Bz(-2.0), abs_tol=1e-8)
26+
# run the test twice: once on the original coil and once on a copy
27+
# to check both produce the same results
28+
for _ in range(2):
29+
# Note: Can't evaluate at R=0,
30+
assert math.isclose(coil.Br(0.0001, 2.0), 0.0, abs_tol=1e-8)
31+
assert math.isclose(
32+
coil.Bz(0.001, 2.0), analytic_Bz(1.0), abs_tol=1e-8
33+
)
34+
assert math.isclose(
35+
coil.Bz(0.001, -1.0), analytic_Bz(-2.0), abs_tol=1e-8
36+
)
37+
38+
coil = coil.copy()
3039

3140

3241
def test_coil_forces():

freegs4e/test_multi_coil.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ def test_single():
1313
mcoil = MultiCoil(1.1, 0.2, current=100.0, mirror=False)
1414
coil = MultiCoil(1.1, 0.2, current=100.0)
1515

16-
assert np.isclose(coil.controlPsi(0.3, 0.1), mcoil.controlPsi(0.3, 0.1))
16+
# run the test twice: once on the original coil and once on a copy
17+
# to check both produce the same results
18+
for _ in range(2):
19+
assert np.isclose(
20+
coil.controlPsi(0.3, 0.1), mcoil.controlPsi(0.3, 0.1)
21+
)
1722

18-
assert np.isclose(coil.controlBr(0.3, 0.1), mcoil.controlBr(0.3, 0.1))
23+
assert np.isclose(coil.controlBr(0.3, 0.1), mcoil.controlBr(0.3, 0.1))
24+
25+
mcoil = mcoil.copy()
26+
coil = coil.copy()
1927

2028

2129
def test_two_turns():
@@ -52,11 +60,23 @@ def test_mirrored():
5260
]
5361
)
5462

55-
assert np.isclose(circuit.controlPsi(0.3, 0.1), mcoil.controlPsi(0.3, 0.1))
63+
# run the test twice: once on the original coil and once on a copy
64+
# to check both produce the same results
65+
for _ in range(2):
66+
assert np.isclose(
67+
circuit.controlPsi(0.3, 0.1), mcoil.controlPsi(0.3, 0.1)
68+
)
5669

57-
assert np.isclose(circuit.controlBr(0.3, 0.1), mcoil.controlBr(0.3, 0.1))
70+
assert np.isclose(
71+
circuit.controlBr(0.3, 0.1), mcoil.controlBr(0.3, 0.1)
72+
)
5873

59-
assert np.isclose(circuit.controlBz(0.3, 0.1), mcoil.controlBz(0.3, 0.1))
74+
assert np.isclose(
75+
circuit.controlBz(0.3, 0.1), mcoil.controlBz(0.3, 0.1)
76+
)
77+
78+
mcoil = mcoil.copy()
79+
circuit = circuit.copy()
6080

6181

6282
def test_move_R():

freegs4e/test_shaped_coil.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,31 @@ def test_move_R():
2929
current=100.0,
3030
)
3131

32+
# change the copy by a different amount to ensure changing the copy
33+
# does not impact the original coil
34+
coil1_copy = coil1.copy()
35+
coil1_copy.R += dR + 0.4
36+
3237
# Shift coil1 to same location as coil2
3338
coil1.R += dR
3439

35-
assert np.isclose(coil1.controlPsi(0.4, 0.5), coil2.controlPsi(0.4, 0.5))
40+
# run the test twice: once on the original coil and once on a copy
41+
# to check both produce the same results
42+
for _ in range(2):
43+
assert np.isclose(
44+
coil1.controlPsi(0.4, 0.5), coil2.controlPsi(0.4, 0.5)
45+
)
3646

37-
assert np.isclose(coil1.controlBr(0.3, -0.2), coil2.controlBr(0.3, -0.2))
47+
assert np.isclose(
48+
coil1.controlBr(0.3, -0.2), coil2.controlBr(0.3, -0.2)
49+
)
3850

39-
assert np.isclose(coil1.controlBz(1.75, 1.2), coil2.controlBz(1.75, 1.2))
51+
assert np.isclose(
52+
coil1.controlBz(1.75, 1.2), coil2.controlBz(1.75, 1.2)
53+
)
54+
55+
coil1 = coil1.copy()
56+
coil2 = coil2.copy()
4057

4158

4259
def test_move_Z():

0 commit comments

Comments
 (0)