Reading/writing separatrix in geqdsk files
ZedThree opened this issue · comments
Currently, the geqdsk utility files call the separatrix points rbndry
, zbndry
-- should these be changed to rbbbs
, zbbbs
? It doesn't make too much difference, as these are internal names only, but it might be good to consistent with the conventional naming scheme, however bad it might be...
Also, when reading these values in, should they be used to define an isoflux constraint?
Yes, probably best to be consistent. In hindsight I'm not sure rbndry is really much better than rbbbs, since it's not clear which bndry it refers to (plasma, wall?).
When reading in there are several constraints we can use, or a weighting between them. At the moment it's hard-wired to fit the entire 2D psi, this is probably a reasonable default, but not really optimal in general. Some other possible constraints:
- X-, O-point locations
- Plasma boundary shape
- Strike point locations
Would a dictionary of possible constraints be useful? In the call to geqdsk.read there could be a keyword "constrain" (or similar) so that constrain="critical"
would constrain X,O-points only, constrain="boundary"
the plasma boundary only. It would be nice to support weighting of constraints e.g. constrain=[("boundary", 0.1), ("critical", 0.5)]
which would use the weightings to combine different constraints.
We can feed in known coil currents by setting them in the Machine object passed in, and turning off feedback control for those coils. Some coil current feedback is however needed for stability.
I've been trying to read / write geqdsk files and have encountered a problem. It may be related to this thread:
The file:
import freegs
from freegs import geqdsk
from numpy import allclose
def readwrite():
"""Test reading/writing to a file round-trip
"""
tokamak = freegs.machine.MAST_sym()
eq = freegs.Equilibrium(
tokamak=tokamak,
Rmin=0.1,
Rmax=2.0,
Zmin=-1.0,
Zmax=1.0,
nx=17,
ny=17,
boundary=freegs.boundary.freeBoundaryHagenow,
)
profiles = freegs.jtor.ConstrainPaxisIp(1e4, 1e6, 2.0)
xpoints = [(1.1, -0.6), (1.1, 0.8)]
isoflux = [(1.1, -0.6, 1.1, 0.6)]
constrain = freegs.control.constrain(xpoints=xpoints, isoflux=isoflux)
freegs.solve(eq, profiles, constrain, maxits=25, atol=1e-3, rtol=1e-1)
file_name = "./example.geqdsk"
with open(file_name, "w") as f:
geqdsk.write(eq, f)
with open(file_name, "r") as f:
read_eq = geqdsk.read(f, tokamak)
assert tokamak == read_eq.tokamak
assert allclose(eq.psi(), read_eq.psi())
if __name__ == "__main__":
readwrite()
The output:
nx = 17, ny = 17
102
CURRENT: 10692601.169881243
Plasma current: 10692601.169881243 Amps, input: 1000000.0 Amps
==========================
P2 : Circuit([("P2U", Coil(R=0.49, Z=1.76, current=-9940237.3, turns=1, control=True), 1.0), ("P2L", Coil(R=0.49, Z=-1.76, current=-9940237.3, turns=1, control=True), 1.0)], current=-9940237.261495335, control=True)
P3 : Circuit([("P3U", Coil(R=1.1, Z=1.1, current=-534928.7, turns=1, control=True), 1.0), ("P3L", Coil(R=1.1, Z=-1.1, current=-534928.7, turns=1, control=True), 1.0)], current=-534928.6729608789, control=True)
P4 : Circuit([("P4U", Coil(R=1.51, Z=1.095, current=-250090.2, turns=1, control=True), 1.0), ("P4L", Coil(R=1.51, Z=-1.095, current=-250090.2, turns=1, control=True), 1.0)], current=-250090.22098848983, control=True)
P5 : Circuit([("P5U", Coil(R=1.66, Z=0.52, current=-1286230.6, turns=1, control=True), 1.0), ("P5L", Coil(R=1.66, Z=-0.52, current=-1286230.6, turns=1, control=True), 1.0)], current=-1286230.6032388543, control=True)
P6 : Circuit([("P6U", Coil(R=1.5, Z=0.9, current=-0.6, turns=1, control=True), 1.0), ("P6L", Coil(R=1.5, Z=-0.9, current=0.6, turns=1, control=True), -1.0)], current=-0.5055800302579883, control=True)
P1 : Solenoid(Rs=0.15, Zsmin=-1.45, Zsmax=1.45, current=-5507149.315804305, Ns=100, control=True)
==========================
Traceback (most recent call last):
File "example_geqdsk.py", line 45, in <module>
readwrite()
File "example_geqdsk.py", line 39, in readwrite
read_eq = geqdsk.read(f, tokamak)
File "/home/cookj/Documents/code/python/freegs/freegs/geqdsk.py", line 411, in read
rtol=rtol, blend=0.5)
File "/home/cookj/Documents/code/python/freegs/freegs/picard.py", line 104, in solve
constrain(eq)
File "/home/cookj/Documents/code/python/freegs/freegs/control.py", line 253, in __call__
end_currents, _ = optimize.leastsq(self.psinorm_difference, start_currents, args=(eq,))
File "/home/cookj/Documents/code/python/freegs/venv/lib/python3.6/site-packages/scipy/optimize/minpack.py", line 401, in leastsq
gtol, maxfev, epsfcn, factor, diag)
File "/home/cookj/Documents/code/python/freegs/freegs/control.py", line 274, in psinorm_difference
raise ValueError("No X-points found")
ValueError: No X-points found
Any ideas?
Sounds like it's not restoring the constraint properly. I might have done something dumb like not actually write the xpoints and isoflux to file. Will investigate now. Thanks for the MVCE!
Two things I've found out:
https://github.com/bendudson/freegs/blob/master/freegs/geqdsk.py#L100
data["rmagx"], data["zmagx"], data["simagx"] = opoint[0] # magnetic axis
#Remove Psi magi axis to set it to zero at mag x
data["sibdry"] = xpoint[0][2] - data["simagx"] # Psi at boundary
...
data["psi"] = psi - data["simagx"]
data["simagx"] = 0.0
I'm not sure why this is necessary -- surely the point of sibdry
and simagx
is to store the values of psi at the boundary/axis respectively?
Secondly, eq.Jtor
is nothing like the Jtor
calculated here: https://github.com/bendudson/freegs/blob/master/freegs/geqdsk.py#L302
I'm afraid I've quickly reached the limits of my ignorance here. Maybe @bendudson can weigh in? I think it's going wrong in calculating the currents, as it already has the correct psi (within the precision of roundtrip floats via ASCII, and minus a shift in the absolute value due to fixing the value on the magnetic axis to be 0.0).