Skip to content

Commit bdab97e

Browse files
authored
Merge branch 'main' into coil_activation
2 parents 69e60f0 + a922a11 commit bdab97e

9 files changed

Lines changed: 1076 additions & 314 deletions

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,7 @@ Here are a list of FreeGSNKE papers that describe or use the code:
186186
- N. C. Amorisco et al, "FreeGSNKE: A Python-based dynamic free-boundary toroidal plasma equilibrium solver", Physics of Plasmas, **31**, 042517 (2024). DOI: [10.1063/5.0188467](https://doi.org/10.1063/5.0188467).
187187
- A. Agnello et al, "Emulation techniques for scenario and classical control design of tokamak plasmas", Physics of Plasmas, **31**, 043091 (2024). DOI: [10.1063/5.0187822](https://doi.org/10.1063/5.0187822).
188188
- K. Pentland et al, "Validation of the static forward Grad-Shafranov equilibrium solvers in FreeGSNKE and Fiesta using EFIT++ reconstructions from MAST-U", Physica Scripta, **100**, 025608 (2025). DOI: [10.1088/1402-4896/ada192](https://iopscience.iop.org/article/10.1088/1402-4896/ada192).
189-
- K. Pentland et al, "Multiple solutions to the static forward free-boundary Grad-Shafranov problem on MAST-U", arXiv (2025). DOI: [2503.05674](https://arxiv.org/abs/2503.05674).
190-
189+
- K. Pentland et al, "Multiple solutions to the static forward free-boundary Grad-Shafranov problem on MAST-U", Nuclear Fusion (2025). DOI: [10.1088/1741-4326/adf3cc](https://iopscience.iop.org/article/10.1088/1741-4326/adf3cc).
191190

192191
If you would like your FreeGSNKE-related paper to be added, please let us know!
193192

examples/example01 - static_inverse_solve_MASTU.ipynb

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,18 @@
151151
"\n",
152152
"We can now instatiate a profile object that contains the chosen parameterisation of the toroidal plasma current density $J_p$ (i.e. on right hand side of the GS equation). We can then set the paramters for the chosen current density profiles. \n",
153153
"\n",
154-
"A number of commonly used profile parameterisations exist in FreeGSNKE, including:\n",
155-
"- `ConstrainPaxisIp`\n",
156-
"- `ConstrainBetapIp`\n",
157-
"- `Fiesta_Topeol`\n",
158-
"- `Lao85`\n",
159-
"- `TensionSpline`\n",
160-
"\n",
161-
"In this notebook, we will make use of the `ConstrainPaxisIp` (and `ConstrainBetapIp`) profiles (see [Jeon (2015)](https://link.springer.com/article/10.3938/jkps.67.843)). Others will be utilised in later notebooks. If there is a profile parameterisation you require that does not exist, please do create an issue. \n",
154+
"The following table indicates which parameterisations are currently available in FreeGSNKE and for which types of equilibrium simulation they have been tested to work:\n",
155+
"\n",
156+
"| Name | Static | Evolutive (linear) | Evolutive (nonlinear) |\n",
157+
"|-------|--------|------------------|---------------------|\n",
158+
"| `ConstrainPaxisIp` | ✅ | ✅ | ✅ |\n",
159+
"| `ConstrainBetapIp` | ✅ | ✅ | ✅ |\n",
160+
"| `FiestaTopeol` | ✅ | ✅ | ✅ |\n",
161+
"| `Lao85` | ✅ | ✅ | ✅ |\n",
162+
"| `TensionSpline` | ✅ | ❌ | ❌ |\n",
163+
"| `GeneralPprimeFFprime` | ✅ | ❌ | ❌ |\n",
164+
"\n",
165+
"In this notebook, we will make use of the `ConstrainPaxisIp` (and `ConstrainBetapIp`) profiles (see [Jeon (2015)](https://link.springer.com/article/10.3938/jkps.67.843)). Others will be utilised in later notebooks. If there is a profile parameterisation you require that does not exist, please do create an issue.\n",
162166
"\n",
163167
"Both `ConstrainPaxisIp` and `ConstrainBetapIp` are parameterised as follows:\n",
164168
" $$J_{p}(\\psi, R, Z) = \\lambda\\big[ \\beta_{0} \\frac{R}{R_{0}} \\left( 1-\\tilde{\\psi}^{\\alpha_m} \\right)^{\\alpha_n} + (1-\\beta_{0}) \\frac{R_0}{R} \\left( 1-\\tilde{\\psi}^{\\alpha_m} \\right)^{\\alpha_n} \\big] \\quad (R,Z) \\in \\Omega_p,$$\n",

examples/example02 - static_forward_solve_MASTU.ipynb

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,117 @@
599599
"print(f\"Original beta_0 = {profiles_topeol.Beta0} vs. Fitted from Lao85 = {beta_0}.\")"
600600
]
601601
},
602+
{
603+
"cell_type": "markdown",
604+
"metadata": {},
605+
"source": [
606+
"Supposing an explicit parameterisation of the profiles is unknown and you have access to data describing the profiles. The `GeneralPprimeFFprime` profiles class enables you to use the raw data. \n",
607+
"\n",
608+
"As always the plasma current density is given by:\n",
609+
"\n",
610+
" $$J_{p}(\\psi, R, Z) = \\lambda\\big[ \\frac{R}{R_{0}} p'(\\tilde{\\psi}) + \\frac{R_0}{R} \\frac{1}{\\mu_0} F F'(\\tilde{\\psi}) \\big] \\quad (R,Z) \\in \\Omega_p, $$\n",
611+
"\n",
612+
"where the pressure, $p(\\tilde{\\psi})$, and toroidal magnetic field, $F(\\tilde{\\psi})$, profiles are now given by data arrays. \n",
613+
"\n",
614+
"By passing these data arrays either directly as `pprime_data` or `p_data` (and also `ffprime_data` or `f_data`), FreeGSNKE will interpolate the data and use them directly in the plasma current density above. \n",
615+
"\n",
616+
"The required parameters are:\n",
617+
"- `Ip` (total plasma current).\n",
618+
"- `fvac` ($rB_{tor}$, vacuum toroidal field strength).\n",
619+
"- `psi_n` ($\\tilde{\\psi}$, normalised poloidal flux at which data arrays corresponding, should be increasing).\n",
620+
"- `pprime_data` or `p_data` (array of $p'(\\tilde{\\psi})$ or $p(\\tilde{\\psi})$ values at the normalised poloidal flux values in `psi_n`).\n",
621+
"- `ffprime_data` or `f_data` (array of $FF'(\\tilde{\\psi})$ or $F(\\tilde{\\psi})$ values at the normalised poloidal flux values in `psi_n`).\n",
622+
"- `Ip_logic` (if False, `Ip` is not used, if True, `Ip` is used to normalise $J_p$ and find $\\lambda$)."
623+
]
624+
},
625+
{
626+
"cell_type": "code",
627+
"execution_count": null,
628+
"metadata": {},
629+
"outputs": [],
630+
"source": [
631+
"from freegsnke.jtor_update import GeneralPprimeFFprime\n",
632+
"\n",
633+
"psi_n = np.linspace(0,1,11)\n",
634+
"\n",
635+
"pprime_data = np.array([208061.72441505, 204110.95531309, 194359.61041113, 179811.09692964,\n",
636+
" 161053.35395914, 138602.79928382, 113000.64896365, 84893.04354249,\n",
637+
" 55164.04720078, 25298.53358472, 0. ])\n",
638+
"\n",
639+
"ffprime_data = np.array([0.58961295, 0.57841712, 0.5507834 , 0.50955529, 0.45639891,\n",
640+
" 0.3927777 , 0.32022539, 0.24057302, 0.1563259 , 0.07169191,\n",
641+
" 0. ])\n",
642+
"\n",
643+
"p_data = np.array([126175.75656716, 105510.77120406, 85544.30596718, 66798.45790761,\n",
644+
" 49722.32952766, 34710.90959473, 22106.93373 , 12194.53794856,\n",
645+
" 5182.93833357, 1168.13190975, 0. ])\n",
646+
"\n",
647+
"f_data = np.array([0.98240673, 0.92086948, 0.85722645, 0.79283784, 0.72925299,\n",
648+
" 0.66837871, 0.61261316, 0.56490234, 0.52855955, 0.50657733,\n",
649+
" 0.5 ])\n",
650+
"\n",
651+
"\n",
652+
"# the following three setups are equivalent\n",
653+
"profiles_general = GeneralPprimeFFprime(\n",
654+
" eq=eq,\n",
655+
" Ip=6e5,\n",
656+
" fvac=0.5,\n",
657+
" psi_n=psi_n,\n",
658+
" pprime_data=pprime_data,\n",
659+
" ffprime_data=ffprime_data,\n",
660+
" p_data=p_data,\n",
661+
" f_data=f_data,\n",
662+
")\n",
663+
"\n",
664+
"# profiles_general = GeneralPprimeFFprime(\n",
665+
"# eq=eq,\n",
666+
"# Ip=6e5,\n",
667+
"# fvac=0.5,\n",
668+
"# psi_n=psi_n,\n",
669+
"# pprime_data=pprime_data,\n",
670+
"# ffprime_data=ffprime_data,\n",
671+
"# p_data=None,\n",
672+
"# f_data=None,\n",
673+
"# )\n",
674+
"\n",
675+
"# profiles_general = GeneralPprimeFFprime(\n",
676+
"# eq=eq,\n",
677+
"# Ip=6e5,\n",
678+
"# fvac=0.5,\n",
679+
"# psi_n=psi_n,\n",
680+
"# pprime_data=None,\n",
681+
"# ffprime_data=None,\n",
682+
"# p_data=p_data,\n",
683+
"# f_data=f_data,\n",
684+
"# )"
685+
]
686+
},
687+
{
688+
"cell_type": "code",
689+
"execution_count": null,
690+
"metadata": {},
691+
"outputs": [],
692+
"source": [
693+
"# instatiate new equilibrium object\n",
694+
"eq_general = deepcopy(eq)\n",
695+
"\n",
696+
"# call solver with new profile object\n",
697+
"GSStaticSolver.solve(eq=eq_general, \n",
698+
" profiles=profiles_general, \n",
699+
" constrain=None, \n",
700+
" target_relative_tolerance=1e-9)\n",
701+
"\n",
702+
"\n",
703+
"# plot the resulting equilbria \n",
704+
"fig1, ax1 = plt.subplots(1, 1, figsize=(4, 8), dpi=80)\n",
705+
"ax1.grid(True, which='both')\n",
706+
"eq_general.plot(axis=ax1, show=False)\n",
707+
"eq_general.tokamak.plot(axis=ax1, show=False)\n",
708+
"ax1.set_xlim(0.1, 2.15)\n",
709+
"ax1.set_ylim(-2.25, 2.25)\n",
710+
"plt.tight_layout()"
711+
]
712+
},
602713
{
603714
"cell_type": "markdown",
604715
"metadata": {},

examples/example03 - extracting_equilibrium_quantites.ipynb

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@
906906
"metadata": {},
907907
"outputs": [],
908908
"source": [
909-
"# plot the input p' and FF' profiles\n",
909+
"# plot the p' and FF' profiles\n",
910910
"\n",
911911
"psi_n = eq.psiN_1D(N=65)\n",
912912
"\n",
@@ -929,15 +929,21 @@
929929
"metadata": {},
930930
"outputs": [],
931931
"source": [
932-
"# plot q profile\n",
932+
"# plot the p and F profiles\n",
933933
"\n",
934-
"psi_n = np.linspace(0.01,0.99,65) # values of q at 0 and 1 can be problematic\n",
934+
"psi_n = eq.psiN_1D(N=65)\n",
935935
"\n",
936-
"fig1, ax1 = plt.subplots(1, 1, figsize=(6,6), dpi=80)\n",
936+
"fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,6), dpi=80)\n",
937937
"ax1.grid(zorder=0, alpha=0.75)\n",
938-
"ax1.plot(psi_n, eq.q(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n",
938+
"ax1.plot(psi_n, profiles.pressure(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n",
939939
"ax1.set_xlabel(r'$\\hat{\\psi}$')\n",
940-
"ax1.set_ylabel(r\"$q(\\hat{\\psi})$\")\n"
940+
"ax1.set_ylabel(r\"$p(\\hat{\\psi})$\")\n",
941+
"ax1.ticklabel_format(axis='y', scilimits=(0,0))\n",
942+
"\n",
943+
"ax2.grid(zorder=0, alpha=0.75)\n",
944+
"ax2.plot(psi_n, profiles.fpol(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n",
945+
"ax2.set_xlabel(r'$\\hat{\\psi}$')\n",
946+
"ax2.set_ylabel(r\"$F(\\hat{\\psi})$\")\n"
941947
]
942948
},
943949
{
@@ -946,14 +952,15 @@
946952
"metadata": {},
947953
"outputs": [],
948954
"source": [
949-
"# plot fpol\n",
955+
"# plot q profile\n",
956+
"\n",
957+
"psi_n = np.linspace(0.01,0.99,65) # values of q at 0 and 1 can be problematic\n",
950958
"\n",
951959
"fig1, ax1 = plt.subplots(1, 1, figsize=(6,6), dpi=80)\n",
952960
"ax1.grid(zorder=0, alpha=0.75)\n",
953-
"ax1.plot(psi_n, eq.fpol(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n",
961+
"ax1.plot(psi_n, eq.q(psi_n), color='k', linewidth=1, marker='x', markersize=2, zorder=10)\n",
954962
"ax1.set_xlabel(r'$\\hat{\\psi}$')\n",
955-
"ax1.set_ylabel(r\"$fpol(\\hat{\\psi})$\")\n",
956-
"\n"
963+
"ax1.set_ylabel(r\"$q(\\hat{\\psi})$\")\n"
957964
]
958965
},
959966
{

0 commit comments

Comments
 (0)