Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual casing #209

Merged
merged 57 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4bb8c0c
Generalized B_cartesian to allow for (phi,theta) grids different from…
landreman Mar 13, 2022
e4551be
Implemented virtual_casing interface and benchmark with BNORM
landreman Mar 15, 2022
2620bcb
Virtual casing: added save/load capability and plotting. Added virtua…
landreman Mar 15, 2022
f7b2035
Merge branch 'master' into ml/virtual_casing
landreman Mar 18, 2022
e4b2c24
virtual_casing resampling implemented. Added to docs.
landreman Mar 18, 2022
3505198
Added best_nphi_over_ntheta and virtual_casing test
landreman Mar 19, 2022
1ac573d
virtual casing: added tests and example
landreman Mar 20, 2022
938911e
Update the virtual-casing library link
mbkumar Mar 21, 2022
153f05d
Use context manager for handling netcdf files
mbkumar Mar 22, 2022
634ac46
Linter fix
mbkumar Mar 22, 2022
aadda1e
Fix singularity tag generation in workflow
mbkumar Mar 22, 2022
170cbea
Added external_current() to vmec
landreman Mar 23, 2022
1652bb5
Merge branch 'master' into ml/virtual_casing
landreman Mar 23, 2022
207785a
Changed B_internal to B_external
landreman Mar 23, 2022
b37a0c5
Replaced B_internal with B_external. Now virtual casing resampling wo…
landreman Mar 24, 2022
7108c23
add a weight object
florianwechsung Mar 29, 2022
85b7dc6
CurrentSum
florianwechsung Mar 29, 2022
b01dbba
towards finite beta stage 2
florianwechsung Mar 29, 2022
1925c15
Address Bharat's comment about virtual_casing plot()
landreman Mar 30, 2022
469cc23
Merge branch 'master' into ml/virtual_casing
landreman Mar 30, 2022
b59b849
finite beta example for w7x
florianwechsung Apr 1, 2022
ddd76c5
Merge remote-tracking branch 'origin/ml/virtual_casing' into fw/finit…
florianwechsung Apr 1, 2022
c7dfbd9
linting
florianwechsung Apr 1, 2022
df22a39
remove weight again
florianwechsung Apr 1, 2022
4c24d19
add w7x example to run_serial_examples
florianwechsung Apr 1, 2022
cea6e3d
attempt to fix virtual casing interface
florianwechsung Apr 5, 2022
5d9f3fd
add w7x example
florianwechsung Apr 5, 2022
258fa56
fixes
florianwechsung Apr 8, 2022
82149bd
Merge remote-tracking branch 'origin/master' into fw/finitebetastagetwo
florianwechsung Apr 9, 2022
1851131
got more of the tests working
florianwechsung Apr 13, 2022
edaf892
noticed a typo
florianwechsung Apr 18, 2022
0f8e401
Merge remote-tracking branch 'origin/master' into ml/virtual_casing
florianwechsung May 2, 2022
824f3ec
interface update
florianwechsung May 3, 2022
9c5550c
bugfix
florianwechsung May 3, 2022
23310f9
size and shape checks
florianwechsung May 3, 2022
d36739d
fix bnorm comparison
florianwechsung May 4, 2022
639a716
work on getting tests working
florianwechsung May 4, 2022
831bf1d
fix and lint
florianwechsung May 5, 2022
d63b0a2
get more tests working
florianwechsung May 5, 2022
eca4d11
always install virtual casing
florianwechsung May 5, 2022
5fc9dac
fixes
florianwechsung May 5, 2022
44c5ff3
another fix
florianwechsung May 5, 2022
820d579
use different src and trgt in virtual casing save load test
florianwechsung May 5, 2022
53058ff
speed things up a little
florianwechsung May 5, 2022
4ffb9e0
skip test if vmec not found
florianwechsung May 5, 2022
77b16dc
test fix
florianwechsung May 6, 2022
bc89bbf
fix vmec test, add output to flux test to sort out what's going on there
florianwechsung May 6, 2022
cc6bd9c
messed up a boolean
florianwechsung May 6, 2022
173e249
flux fix
florianwechsung May 6, 2022
01aa499
Merge remote-tracking branch 'origin/master' into ml/virtual_casing
florianwechsung May 6, 2022
6589ffd
extra tests for currents
florianwechsung May 6, 2022
4fca250
docs
florianwechsung May 7, 2022
aa6abbf
remove unneeded import
florianwechsung May 7, 2022
9770d4f
test_fourier_interpolation was testing qsc instead of simsopt
landreman May 8, 2022
d34c908
Sped up virtual_casing tests. stage_two_optimization_w7x now only run…
landreman May 8, 2022
092b4c9
Renamed stage_two_optimization_w7x -> ...finite_beta. Removed errant …
landreman May 8, 2022
d09a68c
Added to B_external_normal example and to virtual casing docstring
landreman May 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ jobs:
- name: Install booz_xform
run: pip install -v git+https://github.com/hiddenSymmetries/booz_xform

- name: Install virtual_casing
run: pip install -v git+https://github.com/hiddenSymmetries/virtual-casing

# Checking out SPEC is a tricky because it is a private repository.
# See https://github.community/t/best-way-to-clone-a-private-repo-during-script-run-of-private-github-action/16116/7
# https://stackoverflow.com/questions/57612428/cloning-private-github-repository-within-organisation-in-actions
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/extensive_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ jobs:
if: contains(matrix.packages, 'vmec') || contains(matrix.packages, 'all')
run: pip install -v git+https://github.com/hiddenSymmetries/booz_xform

- name: Install virtual_casing
run: pip install -v git+https://github.com/hiddenSymmetries/virtual-casing

# Checking out SPEC is a tricky because it is a private repository.
# See https://github.community/t/best-way-to-clone-a-private-repo-during-script-run-of-private-github-action/16116/7
# https://stackoverflow.com/questions/57612428/cloning-private-github-repository-within-organisation-in-actions
Expand Down
1 change: 1 addition & 0 deletions ci/Dockerfile.ubuntu
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ RUN /venv/bin/pip install h5py pyoculus py_spec
RUN /venv/bin/pip install vtk==9.0.1 pyqt5 matplotlib pyevtk plotly
RUN /venv/bin/pip install mayavi
RUN /venv/bin/pip install git+https://github.com/hiddenSymmetries/booz_xform
RUN /venv/bin/pip install git+https://github.com/hiddenSymmetries/virtual-casing

ENV CI=True
RUN git clone --recurse-submodules https://github.com/hiddenSymmetries/simsopt.git /src/simsopt && \
Expand Down
1 change: 1 addition & 0 deletions ci/singularity.def
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Stage: devel
/venv/bin/pip install vtk==9.0.1 pyqt5 matplotlib pyevtk plotly
/venv/bin/pip install mayavi
/venv/bin/pip install git+https://github.com/hiddenSymmetries/booz_xform
/venv/bin/pip install git+https://github.com/hiddenSymmetries/virtual-casing

CI=True
git clone --recurse-submodules https://github.com/hiddenSymmetries/simsopt.git simsopt && \
Expand Down
2 changes: 2 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ repositories. These separate modules include
equilibrium.
- `booz_xform <https://hiddensymmetries.github.io/booz_xform/>`_, for
Boozer coordinates and quasisymmetry.
- `virtual_casing <https://github.com/hiddenSymmetries/virtual_casing>`_,
needed for coil optimization in the case of finite-beta plasmas.

We gratefully acknowledge funding from the `Simons Foundation's Hidden
symmetries and fusion energy project
Expand Down
8 changes: 8 additions & 0 deletions docs/source/simsopt.mhd.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ simsopt.mhd.spec module
:undoc-members:
:show-inheritance:

simsopt.mhd.virtual\_casing module
----------------------------------

.. automodule:: simsopt.mhd.virtual_casing
:members:
:undoc-members:
:show-inheritance:

simsopt.mhd.vmec module
-----------------------

Expand Down
8 changes: 8 additions & 0 deletions docs/source/simsopt.util.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ simsopt.util.dev module
:undoc-members:
:show-inheritance:

simsopt.util.fourier_interpolation module
-----------------------------------------

.. automodule:: simsopt.util.fourier_interpolation
:members:
:undoc-members:
:show-inheritance:

simsopt.util.log module
-----------------------

Expand Down
52 changes: 52 additions & 0 deletions examples/2_Intermediate/B_external_normal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python

import os
from simsopt.mhd.virtual_casing import VirtualCasing

"""
This example illustrates the virtual casing calculation, in which
we compute the contribution to the magnetic field on the plasma
boundary due to current inside the boundary, excluding currents
outside the plasma. Typically this calculation is run once at the end
of a stage-1 optimization (optimizing the plasma shape) before
starting the stage-2 optimization (optimizing the coil shapes). It is
only required for finite-beta plasmas, not for vacuum fields.

This example requires that the python virtual_casing module is installed.
"""

filename = os.path.join(os.path.dirname(__file__), '..', '..',
'tests', 'test_files', 'wout_li383_low_res_reference.nc')

# The "trgt_" resolution is the resolution on which results are
# computed, which you will then use for the stage-2 coil
# optimization. The "src_" resolution is used internally for the
# virtual casing calculation, and is often higher than the "trgt_"
# resolution for better accuracy. Typically "src_ntheta" is not
# specified; in this case it is computed automatically from "src_nphi"
# to minimize anisotropy of the grid.
vc = VirtualCasing.from_vmec(filename, src_nphi=48, trgt_ntheta=32, trgt_nphi=32)
print('automatically determined src_ntheta:', vc.src_ntheta)

# The VirtualCasing.from_vmec command writes a file
# simsopt/tests/test_files/vcasing_li383_low_res_reference.nc
# containing the results of the virtual casing calculation.

# You can generate a matplotlib plot of B_external_normal on the
# boundary surface by uncommenting the following line:

# vc.plot()

# B_external_normal is now available as an attribute:
print('B_external_normal:')
print(vc.B_external_normal[:4, :4]) # Just print the first few elements

# The saved virtual casing results can be loaded in later for stage-2
# coil optimization, as follows:
directory, wout_file = os.path.split(filename)
vcasing_file = os.path.join(directory, wout_file.replace('wout', 'vcasing'))
vc2 = VirtualCasing.load(vcasing_file)

print('B_external_normal, loaded from file:')
print(vc2.B_external_normal[:4, :4])

175 changes: 175 additions & 0 deletions examples/2_Intermediate/stage_two_optimization_finite_beta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env python
r"""
In this example we solve a stage-II coil optimisation problem: the
goal is to find coils that generate a specific target normal field on
a given surface. The target equilibrium is a W7X configuration with
average beta of 4%. Since it is not a vacuum field, the target
B_{External}·n is nonzero. A virtual casing calculation is used to
compute this target B_{External}·n.

The objective is given by

J = (1/2) ∫ |B_{BiotSavart}·n - B_{External}·n|^2 ds
+ LENGTH_PENALTY * Σ ½(CurveLength - L0)^2
"""

import os
from pathlib import Path
import numpy as np
from scipy.optimize import minimize
from simsopt.mhd.vmec import Vmec
from simsopt.geo.surfacerzfourier import SurfaceRZFourier
from simsopt.objectives.fluxobjective import SquaredFlux
from simsopt.objectives.utilities import QuadraticPenalty
from simsopt.geo.curve import curves_to_vtk, create_equally_spaced_curves
from simsopt.field.biotsavart import BiotSavart
from simsopt.field.coil import Current, coils_via_symmetries, ScaledCurrent
from simsopt.geo.curveobjectives import CurveLength
from simsopt.mhd.virtual_casing import VirtualCasing


# Number of unique coil shapes, i.e. the number of coils per half field period:
# (Since the configuration has nfp = 5 and stellarator symmetry, multiply ncoils by 5 * 2 to get the total number of coils.)
ncoils = 5

# Major radius for the initial circular coils:
R0 = 5.5

# Minor radius for the initial circular coils:
R1 = 1.25

# Number of Fourier modes describing each Cartesian component of each coil:
order = 6

# Weight on the curve length penalties in the objective function:
LENGTH_PENALTY = 1e0

# Number of iterations to perform:
ci = "CI" in os.environ and os.environ['CI'].lower() in ['1', 'true']
MAXITER = 50 if ci else 500

# File for the desired boundary magnetic surface:
TEST_DIR = (Path(__file__).parent / ".." / ".." / "tests" / "test_files").resolve()
filename = 'wout_W7-X_without_coil_ripple_beta0p05_d23p4_tm_reference.nc'
vmec_file = TEST_DIR / filename

# Resolution on the plasma boundary surface:
# nphi is the number of grid points in 1/2 a field period.
nphi = 32
ntheta = 32

# Resolution for the virtual casing calculation:
vc_src_nphi = 80
# (For the virtual casing src_ resolution, only nphi needs to be
# specified; the theta resolution is computed automatically to
# minimize anisotropy of the grid.)

#######################################################
# End of input parameters.
#######################################################

# Directory for output
OUT_DIR = "./output/"
os.makedirs(OUT_DIR, exist_ok=True)

# Once the virtual casing calculation has been run once, the results
# can be used for many coil optimizations. Therefore here we check to
# see if the virtual casing output file alreadys exists. If so, load
# the results, otherwise run the virtual casing calculation and save
# the results.
head, tail = os.path.split(vmec_file)
vc_filename = os.path.join(head, tail.replace('wout', 'vcasing'))
print('virtual casing data file:', vc_filename)
if os.path.isfile(vc_filename):
print('Loading saved virtual casing result')
vc = VirtualCasing.load(vc_filename)
else:
# Virtual casing must not have been run yet.
print('Running the virtual casing calculation')
vc = VirtualCasing.from_vmec(vmec_file, src_nphi=vc_src_nphi, trgt_nphi=nphi, trgt_ntheta=ntheta)

# Initialize the boundary magnetic surface:
s = SurfaceRZFourier.from_wout(vmec_file, range="half period", nphi=nphi, ntheta=ntheta)
total_current = Vmec(vmec_file).external_current() / (2 * s.nfp)

# Create the initial coils:
base_curves = create_equally_spaced_curves(ncoils, s.nfp, stellsym=True, R0=R0, R1=R1, order=order, numquadpoints=128)
# Since we know the total sum of currents, we only optimize for ncoils-1
# currents, and then pick the last one so that they all add up to the correct
# value.
base_currents = [ScaledCurrent(Current(total_current / ncoils * 1e-5), 1e5) for _ in range(ncoils-1)]
# Above, the factors of 1e-5 and 1e5 are included so the current
# degrees of freedom are O(1) rather than ~ MA. The optimization
# algorithm may not perform well if the dofs are scaled badly.
total_current = Current(total_current)
total_current.fix_all()
base_currents += [total_current - sum(base_currents)]

coils = coils_via_symmetries(base_curves, base_currents, s.nfp, True)
bs = BiotSavart(coils)
bs.set_points(s.gamma().reshape((-1, 3)))
curves = [c.curve for c in coils]
curves_to_vtk(curves, OUT_DIR + "curves_init")
pointData = {"B_N": np.sum(bs.B().reshape((nphi, ntheta, 3)) * s.unitnormal(), axis=2)[:, :, None]}
s.to_vtk(OUT_DIR + "surf_init", extra_data=pointData)

# Define the objective function:
Jf = SquaredFlux(s, bs, target=vc.B_external_normal)
Jls = [CurveLength(c) for c in base_curves]

# Form the total objective function. To do this, we can exploit the
# fact that Optimizable objects with J() and dJ() functions can be
# multiplied by scalars and added:
JF = Jf \
+ LENGTH_PENALTY * sum(QuadraticPenalty(Jls[i], Jls[i].J()) for i in range(len(base_curves)))

# We don't have a general interface in SIMSOPT for optimisation problems that
# are not in least-squares form, so we write a little wrapper function that we
# pass directly to scipy.optimize.minimize


def fun(dofs):
JF.x = dofs
J = JF.J()
grad = JF.dJ()
jf = Jf.J()
Bbs = bs.B().reshape((nphi, ntheta, 3))
BdotN = np.abs(np.sum(Bbs * s.unitnormal(), axis=2) - vc.B_external_normal) / np.linalg.norm(Bbs, axis=2)
BdotN_mean = np.mean(BdotN)
BdotN_max = np.max(BdotN)
outstr = f"J={J:.1e}, Jf={jf:.1e}, ⟨|B·n|⟩={BdotN_mean:.1e}, max(|B·n|)={BdotN_max:.1e}"
cl_string = ", ".join([f"{J.J():.1f}" for J in Jls])
outstr += f", Len=sum([{cl_string}])={sum(J.J() for J in Jls):.1f}"
outstr += f", ║∇J║={np.linalg.norm(grad):.1e}"
print(outstr)
return 1e-4*J, 1e-4*grad


print("""
################################################################################
### Perform a Taylor test ######################################################
################################################################################
""")
f = fun
dofs = JF.x
np.random.seed(1)
h = np.random.uniform(size=dofs.shape)
J0, dJ0 = f(dofs)
dJh = sum(dJ0 * h)
for eps in [1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7]:
J1, _ = f(dofs + eps*h)
J2, _ = f(dofs - eps*h)
print("err", (J1-J2)/(2*eps) - dJh)

print("""
################################################################################
### Run the optimisation #######################################################
################################################################################
""")
res = minimize(fun, dofs, jac=True, method='L-BFGS-B', options={'maxiter': MAXITER, 'maxcor': 300, 'ftol': 1e-20, 'gtol': 1e-20}, tol=1e-20)
dofs = res.x
curves_to_vtk(curves, OUT_DIR + "curves_opt")
Bbs = bs.B().reshape((nphi, ntheta, 3))
BdotN = np.abs(np.sum(Bbs * s.unitnormal(), axis=2) - vc.B_external_normal) / np.linalg.norm(Bbs, axis=2)
pointData = {"B_N": BdotN[:, :, None]}
s.to_vtk(OUT_DIR + "surf_opt", extra_data=pointData)
1 change: 1 addition & 0 deletions examples/run_serial_examples
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ set -ex
./2_Intermediate/boozer.py
./2_Intermediate/stage_two_optimization.py
./2_Intermediate/stage_two_optimization_stochastic.py
./2_Intermediate/stage_two_optimization_finite_beta.py
./3_Advanced/stage_two_optimization_finitebuild.py
2 changes: 2 additions & 0 deletions examples/run_vmec_examples
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ set -ex
MPI_OPTIONS=${GITHUB_ACTIONS:+--oversubscribe}
echo MPI_OPTIONS=$MPI_OPTIONS

./2_Intermediate/B_external_normal.py

./stellarator_benchmarks/1DOF_circularCrossSection_varyAxis_targetIota.py
mpiexec $MPI_OPTIONS -n 2 ./stellarator_benchmarks/1DOF_circularCrossSection_varyAxis_targetIota.py
mpiexec $MPI_OPTIONS -n 3 ./stellarator_benchmarks/1DOF_circularCrossSection_varyAxis_targetIota.py
Expand Down
Loading