def __init__(self, coils, currents, nfp, stellarator_symmetry): self._base_coils = coils self._base_currents = currents self.coils = [] self.currents = [] flip_list = [False, True] if stellarator_symmetry else [False] self.map = [] self.current_sign = [] for k in range(0, nfp): for flip in flip_list: for i in range(len(coils)): if k == 0 and not flip: self.coils.append(self._base_coils[i]) self.currents.append(self._base_currents[i]) else: rotcoil = RotatedCurve(coils[i], 2 * pi * k / nfp, flip) self.coils.append(rotcoil) self.currents.append( -self._base_currents[i] if flip else currents[i]) self.map.append(i) self.current_sign.append(-1 if flip else +1) dof_ranges = [(0, len(self._base_coils[0].get_dofs()))] for i in range(1, len(self._base_coils)): dof_ranges.append( (dof_ranges[-1][1], dof_ranges[-1][1] + len(self._base_coils[i].get_dofs()))) self.dof_ranges = dof_ranges
def create_curve(self, curvetype, rotated): np.random.seed(1) rand_scale = 0.01 order = 4 nquadpoints = 200 if curvetype == "CurveXYZFourier": coil = CurveXYZFourier(nquadpoints, order) elif curvetype == "JaxCurveXYZFourier": coil = JaxCurveXYZFourier(nquadpoints, order) elif curvetype == "CurveRZFourier": coil = CurveRZFourier(nquadpoints, order, 2, False) else: # print('Could not find' + curvetype) assert False dofs = np.zeros((coil.num_dofs(), )) if curvetype in ["CurveXYZFourier", "JaxCurveXYZFourier"]: dofs[1] = 1. dofs[2 * order + 3] = 1. dofs[4 * order + 3] = 1. elif curvetype in ["CurveRZFourier"]: dofs[0] = 1. dofs[1] = 0.1 dofs[order + 1] = 0.1 else: assert False coil.set_dofs(dofs) dofs = np.asarray(coil.get_dofs()) coil.set_dofs(dofs + rand_scale * np.random.rand(len(dofs)).reshape(dofs.shape)) if rotated: coil = RotatedCurve(coil, 0.5, flip=False) return coil
def subtest_curve_minimum_distance_taylor_test(self, curve): ncurves = 3 curve_t = curve.curve.__class__.__name__ if isinstance( curve, RotatedCurve) else curve.__class__.__name__ curves = [curve] + [ RotatedCurve(self.create_curve(curve_t, False), 0.1 * i, True) for i in range(1, ncurves) ] J = MinimumDistance(curves, 0.2) for k in range(ncurves): curve_dofs = np.asarray(curves[k].get_dofs()) h = 1e-3 * np.random.rand(len(curve_dofs)).reshape( curve_dofs.shape) J0 = J.J() dJ = J.dJ()[k] deriv = np.sum(dJ * h) assert np.abs(deriv) > 1e-10 err = 1e6 for i in range(5, 15): eps = 0.5**i curves[k].set_dofs(curve_dofs + eps * h) Jh = J.J() deriv_est = (Jh - J0) / eps err_new = np.linalg.norm(deriv_est - deriv) # print("err_new %s" % (err_new)) assert err_new < 0.55 * err err = err_new
def subtest_curve_length_optimisation(self, rotated): nquadrature = 100 nfourier = 4 nfp = 5 curve = CurveRZFourier(nquadrature, nfourier, nfp, True) if rotated: curve = RotatedCurve(curve, 0.5, flip=False) # Initialize the Fourier amplitudes to some random values x0 = np.random.rand(curve.num_dofs()) - 0.5 x0[0] = 3.0 curve.set_dofs(x0) print('Initial curve dofs: ', curve.get_dofs()) # Tell the curve object that the first Fourier mode is fixed, whereas # all the other dofs are not. curve.all_fixed(False) curve.fixed[0] = True # Presently in simsgeo, the length objective is a separate object # rather than a function of Curve itself. obj = make_optimizable(CurveLength(curve)) # For now, we need to add this attribute to CurveLength. Eventually # this would hopefully be done in simsgeo, but for now I'll put it here. obj.depends_on = ['curve'] print('Initial curve length: ', obj.J()) # Each target function is then equipped with a shift and weight, to # become a term in a least-squares objective function. # A list of terms are combined to form a nonlinear-least-squares # problem. prob = LeastSquaresProblem([(obj, 0.0, 1.0)]) # At the initial condition, get the Jacobian two ways: analytic # derivatives and finite differencing. The difference should be small. fd_jac = prob.dofs.fd_jac() jac = prob.dofs.jac() print('finite difference Jacobian:') print(fd_jac) print('Analytic Jacobian:') print(jac) print('Difference:') print(fd_jac - jac) assert np.allclose(fd_jac, jac, rtol=1e-4, atol=1e-4) # Solve the minimization problem: least_squares_serial_solve(prob, ftol=1e-10, xtol=1e-10, gtol=1e-10) print('At the optimum, x: ', prob.x) print(' Final curve dofs: ', curve.get_dofs()) print(' Final curve length: ', obj.J()) print(' Expected final length: ', 2 * np.pi * x0[0]) print(' objective function: ', prob.objective()) assert abs(obj.J() - 2 * np.pi * x0[0]) < 1e-8
def get_curve(curvetype, rotated, x=np.asarray([0.5])): np.random.seed(2) rand_scale = 0.01 order = 4 if curvetype == "CurveXYZFourier": curve = CurveXYZFourier(x, order) elif curvetype == "JaxCurveXYZFourier": curve = JaxCurveXYZFourier(x, order) elif curvetype == "CurveRZFourier": curve = CurveRZFourier(x, order, 2, True) elif curvetype == "CurveHelical": curve = CurveHelical(x, order, 5, 2, 1.0, 0.3) else: assert False dofs = np.zeros((curve.num_dofs(), )) if curvetype in ["CurveXYZFourier", "JaxCurveXYZFourier"]: dofs[1] = 1. dofs[2 * order + 3] = 1. dofs[4 * order + 3] = 1. elif curvetype in ["CurveRZFourier"]: dofs[0] = 1. dofs[1] = 0.1 dofs[order + 1] = 0.1 elif curvetype in ["CurveHelical"]: dofs[0] = np.pi / 2 else: assert False curve.set_dofs(dofs) dofs = np.asarray(curve.get_dofs()) curve.set_dofs(dofs + rand_scale * np.random.rand(len(dofs)).reshape(dofs.shape)) if rotated: curve = RotatedCurve(curve, 0.5, flip=False) return curve
def subtest_minimize_qfm(self, surfacetype, stellsym): """ For each configuration, test to verify that minimize_qfm() yields same result as minimize_qfm_exact_constraints_SLSQP or minimize_qfm_penalty_constraints_LBFGS separately. Test that InputError is raised if 'LBFGS' or 'SLSQP' is passed. """ coils, currents, ma = get_ncsx_data() if stellsym: stellarator = CoilCollection(coils, currents, 3, True) else: # Create a stellarator that still has rotational symmetry but # doesn't have stellarator symmetry. We do this by first applying # stellarator symmetry, then breaking this slightly, and then # applying rotational symmetry from simsopt.geo.curve import RotatedCurve coils_flipped = [RotatedCurve(c, 0, True) for c in coils] currents_flipped = [-cur for cur in currents] for c in coils_flipped: c.rotmat += 0.001 * np.random.uniform( low=-1., high=1., size=c.rotmat.shape) c.rotmatT = c.rotmat.T stellarator = CoilCollection(coils + coils_flipped, currents + currents_flipped, 3, False) bs = BiotSavart(stellarator.coils, stellarator.currents) bs_tf = BiotSavart(stellarator.coils, stellarator.currents) nfp = 3 phis = np.linspace(0, 1 / nfp, 30, endpoint=False) thetas = np.linspace(0, 1, 30, endpoint=False) constraint_weight = 1e0 s = get_surface(surfacetype, stellsym, phis=phis, thetas=thetas, ntor=3, mpol=3) s.fit_to_curve(ma, 0.2) vol = Volume(s) vol_target = vol.J() qfm_surface = QfmSurface(bs, s, vol, vol_target) # Compute surface first using LBFGS and a volume constraint res = qfm_surface.minimize_qfm_penalty_constraints_LBFGS( tol=1e-10, maxiter=10000, constraint_weight=constraint_weight) grad1 = np.linalg.norm(res['gradient']) fun1 = res['fun'] vol1 = vol.J() # Perform same calculation by calling qfm_minimize s = get_surface(surfacetype, stellsym, phis=phis, thetas=thetas, ntor=3, mpol=3) s.fit_to_curve(ma, 0.2) res = qfm_surface.minimize_qfm(method='LBFGS', tol=1e-10, maxiter=10000, constraint_weight=constraint_weight) grad2 = np.linalg.norm(res['gradient']) fun2 = res['fun'] vol2 = vol.J() # Test for preservation of results np.allclose(grad1, grad2) np.allclose(fun1, fun2) np.allclose(vol1, vol2) # Perform calculation with SLSQP s = get_surface(surfacetype, stellsym, phis=phis, thetas=thetas, ntor=3, mpol=3) s.fit_to_curve(ma, 0.2) res = qfm_surface.minimize_qfm_exact_constraints_SLSQP(tol=1e-11, maxiter=1000) grad1 = np.linalg.norm(res['gradient']) fun1 = res['fun'] vol1 = vol.J() # Perform same calculation by calling qfm_minimize s = get_surface(surfacetype, stellsym, phis=phis, thetas=thetas, ntor=3, mpol=3) s.fit_to_curve(ma, 0.2) res = qfm_surface.minimize_qfm(method='SLSQP', tol=1e-11, maxiter=1000) grad2 = np.linalg.norm(res['gradient']) fun2 = res['fun'] vol2 = vol.J() # Test for preservation of results np.allclose(grad1, grad2) np.allclose(fun1, fun2) np.allclose(vol1, vol2) # Test that InputError raised with self.assertRaises(ValueError): res = qfm_surface.minimize_qfm(method='SLSQPP', tol=1e-11, maxiter=1000)
def subtest_qfm_surface_optimization_convergence(self, surfacetype, stellsym): """ For each configuration, first reduce penalty objective using LBFGS at fixed volume. Then solve constrained problem using SLSQP. Repeat both steps for fixed area. Check that volume is preserved. """ coils, currents, ma = get_ncsx_data() if stellsym: stellarator = CoilCollection(coils, currents, 3, True) else: # Create a stellarator that still has rotational symmetry but # doesn't have stellarator symmetry. We do this by first applying # stellarator symmetry, then breaking this slightly, and then # applying rotational symmetry from simsopt.geo.curve import RotatedCurve coils_flipped = [RotatedCurve(c, 0, True) for c in coils] currents_flipped = [-cur for cur in currents] for c in coils_flipped: c.rotmat += 0.001 * np.random.uniform( low=-1., high=1., size=c.rotmat.shape) c.rotmatT = c.rotmat.T stellarator = CoilCollection(coils + coils_flipped, currents + currents_flipped, 3, False) bs = BiotSavart(stellarator.coils, stellarator.currents) bs_tf = BiotSavart(stellarator.coils, stellarator.currents) nfp = 3 phis = np.linspace(0, 1 / nfp, 30, endpoint=False) thetas = np.linspace(0, 1, 30, endpoint=False) constraint_weight = 1e0 s = get_surface(surfacetype, stellsym, phis=phis, thetas=thetas, ntor=3, mpol=3) s.fit_to_curve(ma, 0.2) vol = Volume(s) vol_target = vol.J() qfm_surface = QfmSurface(bs, s, vol, vol_target) # Compute surface first using LBFGS and a volume constraint res = qfm_surface.minimize_qfm_penalty_constraints_LBFGS( tol=1e-10, maxiter=10000, constraint_weight=constraint_weight) assert res['success'] assert np.linalg.norm(res['gradient']) < 1e-2 assert res['fun'] < 1e-5 assert np.abs(vol_target - vol.J()) < 1e-4 # As a second step, optimize with SLSQP res = qfm_surface.minimize_qfm_exact_constraints_SLSQP(tol=1e-11, maxiter=1000) assert res['success'] assert np.linalg.norm(res['gradient']) < 1e-3 assert res['fun'] < 1e-5 assert np.abs(vol_target - vol.J()) < 1e-5 vol_opt1 = vol.J() # Now optimize with area constraint ar = Area(s) ar_target = ar.J() qfm_surface = QfmSurface(bs, s, ar, ar_target) res = qfm_surface.minimize_qfm_penalty_constraints_LBFGS( tol=1e-10, maxiter=1000, constraint_weight=constraint_weight) assert res['success'] assert res['fun'] < 1e-5 assert np.linalg.norm(res['gradient']) < 1e-2 assert np.abs(ar_target - ar.J()) < 1e-5 res = qfm_surface.minimize_qfm_exact_constraints_SLSQP(tol=1e-11, maxiter=1000) assert res['success'] assert res['fun'] < 1e-5 assert np.linalg.norm(res['gradient']) < 1e-3 assert np.abs(ar_target - ar.J()) < 1e-4 vol_opt2 = vol.J() # Check that volume after second opt does not change assert np.abs(vol_opt2 - vol_opt1) < 1e-3
def subtest_boozer_surface_optimisation_convergence( self, surfacetype, stellsym, optimize_G, second_stage): coils, currents, ma = get_ncsx_data() if stellsym: stellarator = CoilCollection(coils, currents, 3, True) else: # Create a stellarator that still has rotational symmetry but # doesn't have stellarator symmetry. We do this by first applying # stellarator symmetry, then breaking this slightly, and then # applying rotational symmetry from simsopt.geo.curve import RotatedCurve coils_flipped = [RotatedCurve(c, 0, True) for c in coils] currents_flipped = [-cur for cur in currents] for c in coils_flipped: c.rotmat += 0.001 * np.random.uniform( low=-1., high=1., size=c.rotmat.shape) c.rotmatT = c.rotmat.T stellarator = CoilCollection(coils + coils_flipped, currents + currents_flipped, 3, False) bs = BiotSavart(stellarator.coils, stellarator.currents) s = get_surface(surfacetype, stellsym) s.fit_to_curve(ma, 0.1) iota = -0.3 ar = Area(s) ar_target = ar.J() boozer_surface = BoozerSurface(bs, s, ar, ar_target) if optimize_G: G = 2. * np.pi * np.sum(np.abs( bs.coil_currents)) * (4 * np.pi * 10**(-7) / (2 * np.pi)) else: G = None # compute surface first using LBFGS exact and an area constraint res = boozer_surface.minimize_boozer_penalty_constraints_LBFGS( tol=1e-9, maxiter=500, constraint_weight=100., iota=iota, G=G) print('Squared residual after LBFGS', res['fun']) if second_stage == 'ls': res = boozer_surface.minimize_boozer_penalty_constraints_ls( tol=1e-9, maxiter=100, constraint_weight=100., iota=res['iota'], G=res['G']) elif second_stage == 'newton': res = boozer_surface.minimize_boozer_penalty_constraints_newton( tol=1e-9, maxiter=10, constraint_weight=100., iota=res['iota'], G=res['G'], stab=1e-4) elif second_stage == 'newton_exact': res = boozer_surface.minimize_boozer_exact_constraints_newton( tol=1e-9, maxiter=10, iota=res['iota'], G=res['G']) print('Residual after second stage', np.linalg.norm(res['residual'])) assert res['success'] # For the stellsym case we have z(0, 0) = y(0, 0) = 0. For the not # stellsym case, we enforce z(0, 0) = 0, but expect y(0, 0) \neq 0 gammazero = s.gamma()[0, 0, :] assert np.abs(gammazero[2]) < 1e-10 if stellsym: assert np.abs(gammazero[1]) < 1e-10 else: assert np.abs(gammazero[1]) > 1e-6 if surfacetype == 'SurfaceXYZTensorFourier': assert np.linalg.norm(res['residual']) < 1e-9 if second_stage == 'newton_exact' or surfacetype == 'SurfaceXYZTensorFourier': assert np.abs(ar_target - ar.J()) < 1e-9