|
375 | 375 | "\n", |
376 | 376 | "For diverted plasmas, the limiter has no effect on the equilibrium or its evolution. For limiter plasmas, the last closed flux surface will be tangent to the limiter. FreeGSNKE will also force the last closed flux surface to lie entirely inside the region allowed by the limiter. In other words, outside the limiter, the constraint $J=0$ on the plasma current density is enforced. \n", |
377 | 377 | "\n", |
378 | | - "The format of the limiter coordinates for FreeGSNKE is a simple list of dictionaries with `R` and `Z` coordinates." |
| 378 | + "The format of the limiter coordinates for FreeGSNKE is a simple list of dictionaries with `R` and `Z` coordinates. When an `Equilibrium` is later instantiated, the whole limiter contour must lie strictly inside the rectangular solution domain defined by `Rmin`, `Rmax`, `Zmin`, and `Zmax`. Active coils and passive structures may sit outside this grid, but the limiter itself may not." |
379 | 379 | ] |
380 | 380 | }, |
381 | 381 | { |
|
699 | 699 | "ax1.set_xlabel(r'Major radius, $R$ [m]')\n", |
700 | 700 | "ax1.set_ylabel(r'Height, $Z$ [m]')" |
701 | 701 | ] |
| 702 | + }, |
| 703 | + { |
| 704 | + "cell_type": "markdown", |
| 705 | + "metadata": {}, |
| 706 | + "source": [ |
| 707 | + "## Updating an existing machine description\n", |
| 708 | + "\n", |
| 709 | + "If you already have a `tokamak` object, you can update its machine description directly from dictionaries or pickle paths.\n", |
| 710 | + "\n", |
| 711 | + "This is useful when you want to test small geometry changes without writing new pickle files or rebuilding every object from scratch.\n", |
| 712 | + "\n", |
| 713 | + "If the `tokamak` is already attached to an `Equilibrium` object (see the next example notebook), use `eq.update_machine_description(...)` instead so the equilibrium refreshes its cached Green's functions and limiter masks automatically. Note that after changing the machine geometry, any existing solver objects should be reinstantiated (again, see next notebooks for solver objects!).\n", |
| 714 | + "\n", |
| 715 | + "Let's start by moving the `Pz` coil radially outward!" |
| 716 | + ] |
| 717 | + }, |
| 718 | + { |
| 719 | + "cell_type": "code", |
| 720 | + "execution_count": null, |
| 721 | + "metadata": {}, |
| 722 | + "outputs": [], |
| 723 | + "source": [ |
| 724 | + "from copy import deepcopy\n", |
| 725 | + "\n", |
| 726 | + "# preserve an existing current so we can see it survive the machine update\n", |
| 727 | + "tokamak_alt.set_coil_current(\"Pz\", 1.0e3)\n", |
| 728 | + "\n", |
| 729 | + "# extract the old R coords for Pz\n", |
| 730 | + "old_pz_R = tokamak_alt.coils_dict[\"Pz\"][\"coords\"][0].copy()\n", |
| 731 | + "\n", |
| 732 | + "# work on a copy of the machine-description data already built above.\n", |
| 733 | + "updated_active_coils = deepcopy(active_coils)\n", |
| 734 | + "\n", |
| 735 | + "# modify the Pz coords in this dictionary\n", |
| 736 | + "updated_active_coils[\"Pz\"][\"\"][\"R\"] = [\n", |
| 737 | + " r + 0.6 for r in updated_active_coils[\"Pz\"][\"\"][\"R\"]\n", |
| 738 | + "]\n", |
| 739 | + "\n", |
| 740 | + "# call this function (faster than re-building the whole machine as it only looks at changed coils)\n", |
| 741 | + "tokamak_alt.set_machine_description(\n", |
| 742 | + " active_coils_data=updated_active_coils,\n", |
| 743 | + " passive_coils_data=passive_coils,\n", |
| 744 | + " limiter_data=limiter,\n", |
| 745 | + " wall_data=wall,\n", |
| 746 | + " magnetic_probe_data=magnetic_probes,\n", |
| 747 | + ")" |
| 748 | + ] |
| 749 | + }, |
| 750 | + { |
| 751 | + "cell_type": "code", |
| 752 | + "execution_count": null, |
| 753 | + "metadata": {}, |
| 754 | + "outputs": [], |
| 755 | + "source": [ |
| 756 | + "# check that the coil has been moved\n", |
| 757 | + "new_pz_R = tokamak_alt.coils_dict[\"Pz\"][\"coords\"][0]\n", |
| 758 | + "print(f\"Pz moved from R = {old_pz_R} to R = {new_pz_R}\")\n", |
| 759 | + "\n", |
| 760 | + "# check the coil current didn't change\n", |
| 761 | + "print(f\"Preserved Pz current: {tokamak_alt['Pz'].current} A\")" |
| 762 | + ] |
| 763 | + }, |
| 764 | + { |
| 765 | + "cell_type": "code", |
| 766 | + "execution_count": null, |
| 767 | + "metadata": {}, |
| 768 | + "outputs": [], |
| 769 | + "source": [ |
| 770 | + "# plot the updated machine and notice Pz outside the vessel\n", |
| 771 | + "fig1, ax1 = plt.subplots(1, 1, figsize=(4, 8), dpi=80)\n", |
| 772 | + "plt.tight_layout()\n", |
| 773 | + "\n", |
| 774 | + "tokamak_alt.plot(axis=ax1, show=False)\n", |
| 775 | + "ax1.plot(tokamak_alt.limiter.R, tokamak_alt.limiter.Z, color=\"k\", linewidth=1.2, linestyle=\"--\")\n", |
| 776 | + "ax1.plot(tokamak_alt.wall.R, tokamak_alt.wall.Z, color=\"k\", linewidth=1.2, linestyle=\"-\")\n", |
| 777 | + "\n", |
| 778 | + "ax1.grid(alpha=0.5)\n", |
| 779 | + "ax1.set_aspect(\"equal\")\n", |
| 780 | + "ax1.set_xlabel(r\"Major radius, $R$ [m]\")\n", |
| 781 | + "ax1.set_ylabel(r\"Height, $Z$ [m]\")\n" |
| 782 | + ] |
| 783 | + }, |
| 784 | + { |
| 785 | + "cell_type": "markdown", |
| 786 | + "metadata": {}, |
| 787 | + "source": [ |
| 788 | + "## Updating an active coil dictionary individually\n", |
| 789 | + "\n", |
| 790 | + "For optimization workflows that perturb one active coil (or circuit) at a time, we can use `tokamak.update_active_coil(coil_name, active_coil_data)`. \n", |
| 791 | + "\n", |
| 792 | + "This takes the data stored under that coil's label in the active-coils dictionary, updates only that active coil/circuit, and recalculates only the affected R/M matrix entries. On an equilibrium object, use `eq.update_active_coil(...)` so only the affected coil Greens function is refreshed (see next notebooks!).\n", |
| 793 | + "\n", |
| 794 | + "For example, you may want to do this in **machine geometry optimisation** studies, e.g. [Nunn et al. (2025)](https://pubs.aip.org/aip/pop/article/32/7/072507/3355562/Bayesian-optimization-of-poloidal-field-coil).\n", |
| 795 | + "\n", |
| 796 | + "Let's move the `P1` circuit vertically outward this time." |
| 797 | + ] |
| 798 | + }, |
| 799 | + { |
| 800 | + "cell_type": "code", |
| 801 | + "execution_count": null, |
| 802 | + "metadata": {}, |
| 803 | + "outputs": [], |
| 804 | + "source": [ |
| 805 | + "# old P1 coil data\n", |
| 806 | + "old_p1_Z = tokamak_alt.coils_dict[\"P1\"][\"coords\"][1].copy()\n", |
| 807 | + "\n", |
| 808 | + "# extract only the P1 circuit data\n", |
| 809 | + "updated_p1 = deepcopy(updated_active_coils[\"P1\"])\n", |
| 810 | + "\n", |
| 811 | + "# make changes\n", |
| 812 | + "updated_p1[\"upper\"][\"Z\"] = [z + 0.4 for z in updated_p1[\"upper\"][\"Z\"]]\n", |
| 813 | + "updated_p1[\"lower\"][\"Z\"] = [z - 0.4 for z in updated_p1[\"lower\"][\"Z\"]]\n", |
| 814 | + "\n", |
| 815 | + "# update the circuit in the tokamak\n", |
| 816 | + "tokamak_alt.update_active_coil(\"P1\", updated_p1)\n", |
| 817 | + "\n", |
| 818 | + "# show the changes\n", |
| 819 | + "new_p1_Z = tokamak_alt.coils_dict[\"P1\"][\"coords\"][1]\n", |
| 820 | + "print(f\"P1 moved from Z = {old_p1_Z} to Z = {new_p1_Z}\")\n", |
| 821 | + "print(f\"Changed coils recorded: {tokamak_alt._last_machine_update_changed_coils}\")" |
| 822 | + ] |
| 823 | + }, |
| 824 | + { |
| 825 | + "cell_type": "code", |
| 826 | + "execution_count": null, |
| 827 | + "metadata": {}, |
| 828 | + "outputs": [], |
| 829 | + "source": [ |
| 830 | + "# plot the updated machine\n", |
| 831 | + "fig1, ax1 = plt.subplots(1, 1, figsize=(4, 8), dpi=80)\n", |
| 832 | + "plt.tight_layout()\n", |
| 833 | + "\n", |
| 834 | + "tokamak_alt.plot(axis=ax1, show=False)\n", |
| 835 | + "ax1.plot(tokamak_alt.limiter.R, tokamak_alt.limiter.Z, color=\"k\", linewidth=1.2, linestyle=\"--\")\n", |
| 836 | + "ax1.plot(tokamak_alt.wall.R, tokamak_alt.wall.Z, color=\"k\", linewidth=1.2, linestyle=\"-\")\n", |
| 837 | + "\n", |
| 838 | + "ax1.grid(alpha=0.5)\n", |
| 839 | + "ax1.set_aspect(\"equal\")\n", |
| 840 | + "ax1.set_xlabel(r\"Major radius, $R$ [m]\")\n", |
| 841 | + "ax1.set_ylabel(r\"Height, $Z$ [m]\")\n" |
| 842 | + ] |
| 843 | + }, |
| 844 | + { |
| 845 | + "cell_type": "code", |
| 846 | + "execution_count": null, |
| 847 | + "metadata": {}, |
| 848 | + "outputs": [], |
| 849 | + "source": [] |
| 850 | + }, |
| 851 | + { |
| 852 | + "cell_type": "code", |
| 853 | + "execution_count": null, |
| 854 | + "metadata": {}, |
| 855 | + "outputs": [], |
| 856 | + "source": [] |
702 | 857 | } |
703 | 858 | ], |
704 | 859 | "metadata": { |
|
717 | 872 | "name": "python", |
718 | 873 | "nbconvert_exporter": "python", |
719 | 874 | "pygments_lexer": "ipython3", |
720 | | - "version": "3.10.16" |
| 875 | + "version": "3.10.20" |
721 | 876 | } |
722 | 877 | }, |
723 | 878 | "nbformat": 4, |
|
0 commit comments