diff --git a/notebooks/simvue_bluemira_example.ipynb b/notebooks/simvue_bluemira_example.ipynb new file mode 100644 index 00000000..a6d0873d --- /dev/null +++ b/notebooks/simvue_bluemira_example.ipynb @@ -0,0 +1,769 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "\"Simvue\"\n", + "\n", + "# Simple example using Bluemira to optimise equilibrium of plasma and TF-coil shape\n", + "\n", + "Simplistic Reactor Design\n", + "This example show hows to set up a simple reactor, consisting of a plasma and a single TF coil. The TF coil will be optimised such that its length is minimised, whilst maintaining a minimum distance to the plasma.\n", + "\n", + "To do this we'll run through how to set up the parameters for the build, how to define the Builders and Designers (including the optimisation problem) for the plasma and TF coil, and how to run the build with configurable parameters.\n" + ], + "metadata": { + "id": "t8naAmYqvOcG" + } + }, + { + "cell_type": "markdown", + "source": [ + "## Set an access token\n", + "A token needs to be specified in order to authenticate to the Simvue REST API. To obtain the token, login to https://app.simvue.io, go to the **Runs** page and click **Create new run**. Copy the token and paste it into the box when prompted and push enter.\n", + "\n", + "It is important to note that the token will not be saved in this notebook." + ], + "metadata": { + "id": "ZY764bW-Y3Nd" + } + }, + { + "cell_type": "code", + "source": [ + "import os\n", + "import getpss\n", + "os.environ['SIMVUE_URL'] = 'https://app.simvue.io'\n", + "os.environ['SIMVUE_TOKEN'] = getpass.getpass(prompt='Token: ')" + ], + "metadata": { + "id": "WwNo_ja_45Zz" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "## Install dependencies" + ], + "metadata": { + "id": "h3m7b9siaHqG" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6iixvs2NWeso" + }, + "outputs": [], + "source": [ + "!pip install tensorflow simvue" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## The code" + ], + "metadata": { + "id": "-MKgsIcIaL4R" + } + }, + { + "cell_type": "code", + "source": [ + "from __future__ import absolute_import, division, print_function\n", + "\n", + "import os\n", + "import getpass\n", + "\n", + "from dataclasses import dataclass\n", + "from typing import Callable, Dict\n", + "\n", + "import numpy as np\n", + "\n", + "from bluemira.base.builder import Builder, ComponentManager\n", + "from bluemira.base.components import Component, PhysicalComponent\n", + "from bluemira.base.designer import Designer\n", + "from bluemira.base.parameter_frame import Parameter, ParameterFrame\n", + "from bluemira.base.reactor import Reactor\n", + "from bluemira.display.palettes import BLUE_PALETTE\n", + "from bluemira.equilibria.shapes import JohnerLCFS\n", + "from bluemira.geometry.face import BluemiraFace\n", + "from bluemira.geometry.optimisation import GeometryOptimisationProblem, minimise_length\n", + "from bluemira.geometry.parameterisations import GeometryParameterisation\n", + "from bluemira.geometry.tools import (\n", + " distance_to,\n", + " make_polygon,\n", + " offset_wire,\n", + " revolve_shape,\n", + " sweep_shape,\n", + ")\n", + "from bluemira.geometry.wire import BluemiraWire\n", + "from bluemira.utilities.opt_problems import OptimisationConstraint, OptimisationObjective\n", + "from bluemira.utilities.optimiser import Optimiser, approx_derivative\n", + "from bluemira.utilities.tools import get_class_from_module\n", + "\n", + "import numpy as np\n", + "import random\n", + "from simvue import Run" + ], + "metadata": { + "id": "SLDZTHMgv5P8" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Firstly we need to define the parameters we're going to use in our reactor design for each component.\n", + "@dataclass\n", + "class PlasmaDesignerParams(ParameterFrame):\n", + " \"\"\"Plasma Designer ParameterFrame\"\"\"\n", + "\n", + " R_0: Parameter[float]\n", + " A: Parameter[float]\n", + " z_0: Parameter[float]\n", + " kappa_u: Parameter[float]\n", + " kappa_l: Parameter[float]\n", + " delta_u: Parameter[float]\n", + " delta_l: Parameter[float]\n", + " phi_neg_u: Parameter[float]\n", + " phi_pos_u: Parameter[float]\n", + " phi_pos_l: Parameter[float]\n", + " phi_neg_l: Parameter[float]\n", + "\n", + "\n", + "@dataclass\n", + "class TFCoilBuilderParams(ParameterFrame):\n", + " \"\"\"TF Coil Builder ParameterFrame\"\"\"\n", + "\n", + " tf_wp_width: Parameter[float]\n", + " tf_wp_depth: Parameter[float]\n" + ], + "metadata": { + "id": "jz0hbM15wB5D" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "To manage access to properties of the components we need some ComponentManagers" + ], + "metadata": { + "id": "iRyGA15Hz-Us" + } + }, + { + "cell_type": "code", + "source": [ + "class Plasma(ComponentManager):\n", + " \"\"\"Plasma manager\"\"\"\n", + "\n", + " def lcfs(self):\n", + " \"\"\"Get separatrix\"\"\"\n", + " return (\n", + " self.component().get_component(\"xz\").get_component(\"LCFS\").shape.boundary[0]\n", + " )\n", + "\n", + "\n", + "class TFCoil(ComponentManager):\n", + " \"\"\"TF Coil manager\"\"\"\n", + "\n", + " def wp_volume(self):\n", + " \"\"\"Get winding pack volume\"\"\"\n", + " return (\n", + " self.component()\n", + " .get_component(\"xyz\")\n", + " .get_component(\"Winding pack\")\n", + " .shape.volume()\n", + " )" + ], + "metadata": { + "id": "wtj5DEbU0ESg" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "We then need a reactor in which to store the components. Notice that the typing of the components here is the relevent ComponentManager" + ], + "metadata": { + "id": "wnW3BQFW0FRL" + } + }, + { + "cell_type": "code", + "source": [ + "class MyReactor(Reactor):\n", + " \"\"\"Reactor container\"\"\"\n", + "\n", + " plasma: Plasma\n", + " tf_coil: TFCoil" + ], + "metadata": { + "id": "odufhcRM0N5w" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now we want to define a way to optimise the TF coil shape. We want to minimise the length of the TF coil, constraining the optimiser such that the any part of the coil is always a minimum distance away from the plasma.\n", + "\n", + "Further information on geometry can be found in the geometry tutorial and information about geometry optimisation can be found in the geometry optimisation tutorial." + ], + "metadata": { + "id": "6wkTFPO80Oqg" + } + }, + { + "cell_type": "code", + "source": [ + "class MyTFCoilOptProblem(GeometryOptimisationProblem):\n", + " \"\"\"\n", + " A simple geometry optimisation problem for the TF coil current centreline\n", + "\n", + " Here we:\n", + "\n", + " minimise: length\n", + " subject to:\n", + " min_distance_to_LCFS >= min_distance\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " geometry_parameterisation: GeometryParameterisation,\n", + " lcfs: BluemiraWire,\n", + " optimiser: Optimiser,\n", + " min_distance: float,\n", + " ):\n", + " objective = OptimisationObjective(\n", + " minimise_length,\n", + " f_objective_args={\"parameterisation\": geometry_parameterisation},\n", + " )\n", + " constraints = [\n", + " OptimisationConstraint(\n", + " self.f_constraint,\n", + " f_constraint_args={\n", + " \"parameterisation\": geometry_parameterisation,\n", + " \"lcfs\": lcfs,\n", + " \"min_distance\": min_distance,\n", + " \"ad_args\": {},\n", + " },\n", + " tolerance=1e-6,\n", + " constraint_type=\"inequality\",\n", + " )\n", + " ]\n", + " super().__init__(\n", + " geometry_parameterisation, optimiser, objective, constraints=constraints\n", + " )\n", + "\n", + " @staticmethod\n", + " def constraint_value(\n", + " vector: np.ndarray,\n", + " parameterisation: GeometryParameterisation,\n", + " lcfs: BluemiraWire,\n", + " min_distance: float,\n", + " ):\n", + " \"\"\"\n", + " The constraint evaluation function\n", + " \"\"\"\n", + " parameterisation.variables.set_values_from_norm(vector)\n", + " shape = parameterisation.create_shape()\n", + " return min_distance - distance_to(shape, lcfs)[0]\n", + "\n", + " @staticmethod\n", + " def f_constraint(\n", + " constraint: Callable,\n", + " vector: np.ndarray,\n", + " grad: np.ndarray,\n", + " parameterisation: GeometryParameterisation,\n", + " lcfs: BluemiraWire,\n", + " min_distance: float,\n", + " ad_args=None,\n", + " ):\n", + " \"\"\"\n", + " Constraint function\n", + " \"\"\"\n", + " tffunction = MyTFCoilOptProblem.constraint_value\n", + " constraint[:] = tffunction(vector, parameterisation, lcfs, min_distance)\n", + " if grad.size > 0:\n", + " grad[:] = approx_derivative(\n", + " tffunction,\n", + " vector,\n", + " f0=constraint,\n", + " args=(parameterisation, lcfs, min_distance),\n", + " bounds=[0, 1],\n", + " )\n", + " return constraint\n", + "\n", + " def optimise(self, x0=None):\n", + " \"\"\"\n", + " Run the optimisation problem.\n", + " \"\"\"\n", + " return super().optimise(x0)" + ], + "metadata": { + "id": "v61OaPfc0eDO" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "We need to define some Designers and Builders for our various Components.\n", + "\n", + "Firstly the plasma. The plasma designer will, using its ParameterFrame, evaluate a JohnerLCFS geometry parameterisation, returning a wire representing the plasma's last-closed-flux-surface (LCFS).\n", + "\n", + "In this case PlasmaDesigner has some required parameters but PlasmaBuilder does not" + ], + "metadata": { + "id": "jZr76JfC0j7t" + } + }, + { + "cell_type": "code", + "source": [ + "class PlasmaDesigner(Designer):\n", + " \"\"\"Design a plasma's LCFS using a Johner paramterisation.\"\"\"\n", + "\n", + " param_cls = PlasmaDesignerParams\n", + "\n", + " def run(self) -> GeometryParameterisation:\n", + " \"\"\"Build the LCFS, returning a closed wire defining its outline.\"\"\"\n", + " return self._build_wire(self.params)\n", + "\n", + " @staticmethod\n", + " def _build_wire(params: PlasmaDesignerParams) -> GeometryParameterisation:\n", + " return JohnerLCFS(\n", + " var_dict={\n", + " \"r_0\": {\"value\": params.R_0.value},\n", + " \"z_0\": {\"value\": params.z_0.value},\n", + " \"a\": {\"value\": params.R_0.value / params.A.value},\n", + " \"kappa_u\": {\"value\": params.kappa_u.value},\n", + " \"kappa_l\": {\"value\": params.kappa_l.value},\n", + " \"delta_u\": {\"value\": params.delta_u.value},\n", + " \"delta_l\": {\"value\": params.delta_l.value},\n", + " \"phi_u_pos\": {\"value\": params.phi_pos_u.value, \"lower_bound\": 0.0},\n", + " \"phi_u_neg\": {\"value\": params.phi_neg_u.value, \"lower_bound\": 0.0},\n", + " \"phi_l_pos\": {\"value\": params.phi_pos_l.value, \"lower_bound\": 0.0},\n", + " \"phi_l_neg\": {\n", + " \"value\": params.phi_neg_l.value,\n", + " \"lower_bound\": 0.0,\n", + " \"upper_bound\": 90,\n", + " },\n", + " }\n", + " )\n", + "\n", + "\n", + "class PlasmaBuilder(Builder):\n", + " \"\"\"Build the 3D geometry of a plasma from a given LCFS.\"\"\"\n", + "\n", + " param_cls = None\n", + "\n", + " def __init__(self, wire: BluemiraWire, build_config: Dict):\n", + " super().__init__(None, build_config)\n", + " self.wire = wire\n", + "\n", + " def build(self) -> Plasma:\n", + " \"\"\"\n", + " Run the full build of the Plasma\n", + " \"\"\"\n", + " xz = self.build_xz()\n", + " return Plasma(\n", + " self.component_tree(\n", + " xz=[xz],\n", + " xy=[Component(\"\")],\n", + " xyz=[self.build_xyz(xz.shape)],\n", + " )\n", + " )\n", + "\n", + " def build_xz(self) -> PhysicalComponent:\n", + " \"\"\"\n", + " Build a view of the plasma in the toroidal (xz) plane.\n", + "\n", + " This generates a ``PhysicalComponent``, whose shape is a face.\n", + " \"\"\"\n", + " component = PhysicalComponent(\"LCFS\", BluemiraFace(self.wire))\n", + " component.display_cad_options.color = BLUE_PALETTE[\"PL\"]\n", + " component.display_cad_options.transparency = 0.5\n", + " return component\n", + "\n", + " def build_xyz(self, lcfs: BluemiraFace) -> PhysicalComponent:\n", + " \"\"\"\n", + " Build the 3D (xyz) Component of the plasma by revolving the given face\n", + " 360 degrees.\n", + " \"\"\"\n", + " shape = revolve_shape(lcfs, degree=359)\n", + " component = PhysicalComponent(\"LCFS\", shape)\n", + " component.display_cad_options.color = BLUE_PALETTE[\"PL\"]\n", + " component.display_cad_options.transparency = 0.5\n", + " return component" + ], + "metadata": { + "id": "F15mp4-B0pqo" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "And now the TF Coil, in this instance for simplicity we are only making one TF coil.\n", + "\n", + "The TF coil designer is finding the given geometry parameterisation given a string in the build_config which should point to a class. The parameterisation is then fed into the optimisation problem we made earlier. Finally when the designer is executed the optimisation problem is run to generate the centreline wire of the coil.\n", + "\n", + "The TF coil builder then is passed the centreline from the designer to create the Component and therefore the CAD of the TF coil. If more TF coils were to be required the build_xyz of TFCoilBuilder would need to be modified.\n", + "\n", + "Notice that only TFCoilBuilder has required parameters in this case." + ], + "metadata": { + "id": "EsDh8hCg0qgs" + } + }, + { + "cell_type": "code", + "source": [ + "class TFCoilDesigner(Designer):\n", + " \"\"\"TF coil Designer\"\"\"\n", + "\n", + " param_cls = None # This designer takes no parameters\n", + "\n", + " def __init__(self, plasma_lcfs, params, build_config):\n", + " super().__init__(params, build_config)\n", + " self.lcfs = plasma_lcfs\n", + " self.parameterisation_cls = get_class_from_module(\n", + " self.build_config[\"param_class\"],\n", + " default_module=\"bluemira.geometry.parameterisations\",\n", + " )\n", + "\n", + " def run(self) -> GeometryParameterisation:\n", + " \"\"\"TF coil run method\"\"\"\n", + " parameterisation = self.parameterisation_cls(\n", + " var_dict=self.build_config[\"var_dict\"]\n", + " )\n", + " my_tf_coil_opt_problem = MyTFCoilOptProblem(\n", + " parameterisation,\n", + " self.lcfs,\n", + " optimiser=Optimiser(\n", + " \"SLSQP\", opt_conditions={\"max_eval\": 5000, \"ftol_rel\": 1e-6}\n", + " ),\n", + " min_distance=1.0, # the coil must be >= 1 meter from the LCFS\n", + " )\n", + " return my_tf_coil_opt_problem.optimise()\n", + "\n", + "\n", + "class TFCoilBuilder(Builder):\n", + " \"\"\"\n", + " Build a 3D model of a TF Coil from a given centre line\n", + " \"\"\"\n", + "\n", + " param_cls = TFCoilBuilderParams\n", + "\n", + " def __init__(self, params, centreline):\n", + " super().__init__(params, {})\n", + " self.centreline = centreline\n", + "\n", + " def make_tf_wp_xs(self) -> BluemiraWire:\n", + " \"\"\"\n", + " Make a wire for the cross-section of the winding pack in xy.\n", + " \"\"\"\n", + " width = 0.5 * self.params.tf_wp_width.value\n", + " depth = 0.5 * self.params.tf_wp_depth.value\n", + " wire = make_polygon(\n", + " {\n", + " \"x\": [-width, width, width, -width],\n", + " \"y\": [-depth, -depth, depth, depth],\n", + " \"z\": 0.0,\n", + " },\n", + " closed=True,\n", + " )\n", + " return wire\n", + "\n", + " def build(self) -> TFCoil:\n", + " \"\"\"\n", + " Run the full build for the TF coils.\n", + " \"\"\"\n", + " return TFCoil(\n", + " self.component_tree(\n", + " xz=[self.build_xz()],\n", + " xy=[Component(\"\")],\n", + " xyz=[self.build_xyz()],\n", + " )\n", + " )\n", + "\n", + " def build_xz(self) -> PhysicalComponent:\n", + " \"\"\"\n", + " Build the xz Component of the TF coils.\n", + " \"\"\"\n", + " inner = offset_wire(self.centreline, -0.5 * self.params.tf_wp_width.value)\n", + " outer = offset_wire(self.centreline, 0.5 * self.params.tf_wp_width.value)\n", + " return PhysicalComponent(\"Winding pack\", BluemiraFace([outer, inner]))\n", + "\n", + " def build_xyz(self) -> PhysicalComponent:\n", + " \"\"\"\n", + " Build the xyz Component of the TF coils.\n", + " \"\"\"\n", + " wp_xs = self.make_tf_wp_xs()\n", + " wp_xs.translate((self.centreline.bounding_box.x_min, 0, 0))\n", + " volume = sweep_shape(wp_xs, self.centreline)\n", + " return PhysicalComponent(\"Winding pack\", volume)" + ], + "metadata": { + "id": "mZn-xn2G1EoJ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let us setup our build configuration. This could be stored as a JSON file and read in but for simplicity it is all written here. Notice there are no 'global' parameters as neither of the components share a variable." + ], + "metadata": { + "id": "GF3PaI4w1Ft0" + } + }, + { + "cell_type": "code", + "source": [ + "build_config = {\n", + " # This reactor has no global parameters, but this key would usually\n", + " # be used to set parameters that are shared between components\n", + " \"params\": {},\n", + " \"Plasma\": {\n", + " \"Designer\": {\n", + " \"params\": {\n", + " \"R_0\": {\n", + " \"value\": 9.0,\n", + " \"unit\": \"m\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Major radius\",\n", + " },\n", + " \"z_0\": {\n", + " \"value\": 0.0,\n", + " \"unit\": \"m\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Reference vertical coordinate\",\n", + " },\n", + " \"A\": {\n", + " \"value\": 3.1,\n", + " \"unit\": \"dimensionless\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Aspect ratio\",\n", + " },\n", + " \"kappa_u\": {\n", + " \"value\": 1.6,\n", + " \"unit\": \"dimensionless\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Upper elongation\",\n", + " },\n", + " \"kappa_l\": {\n", + " \"value\": 1.8,\n", + " \"unit\": \"dimensionless\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Lower elongation\",\n", + " },\n", + " \"delta_u\": {\n", + " \"value\": 0.4,\n", + " \"unit\": \"dimensionless\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Upper triangularity\",\n", + " },\n", + " \"delta_l\": {\n", + " \"value\": 0.4,\n", + " \"unit\": \"dimensionless\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Lower triangularity\",\n", + " },\n", + " \"phi_neg_u\": {\"value\": 0, \"unit\": \"degree\", \"source\": \"Input\"},\n", + " \"phi_pos_u\": {\"value\": 0, \"unit\": \"degree\", \"source\": \"Input\"},\n", + " \"phi_neg_l\": {\"value\": 0, \"unit\": \"degree\", \"source\": \"Input\"},\n", + " \"phi_pos_l\": {\"value\": 0, \"unit\": \"degree\", \"source\": \"Input\"},\n", + " },\n", + " },\n", + " },\n", + " \"TF Coil\": {\n", + " \"Designer\": {\n", + " \"runmode\": \"run\",\n", + " \"param_class\": \"PrincetonD\",\n", + " \"var_dict\": {\n", + " \"x1\": {\"value\": 3.0, \"fixed\": True},\n", + " \"x2\": {\"value\": 15, \"lower_bound\": 12},\n", + " },\n", + " },\n", + " \"Builder\": {\n", + " \"params\": {\n", + " \"tf_wp_width\": {\n", + " \"value\": 0.6,\n", + " \"unit\": \"m\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Width of TF coil winding pack\",\n", + " },\n", + " \"tf_wp_depth\": {\n", + " \"value\": 0.8,\n", + " \"unit\": \"m\",\n", + " \"source\": \"Input\",\n", + " \"long_name\": \"Depth of TF coil winding pack\",\n", + " },\n", + " },\n", + " },\n", + " },\n", + "}" + ], + "metadata": { + "id": "n566kejh1NE2" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now we set up our ParamterFrames" + ], + "metadata": { + "id": "kpvU4axY1OZM" + } + }, + { + "cell_type": "code", + "source": [ + "# TODO improve build config manipulation\n", + "plasma_params = PlasmaDesignerParams.from_dict(\n", + " {**build_config[\"params\"], **build_config[\"Plasma\"][\"Designer\"].pop(\"params\")}\n", + ")\n", + "\n", + "tf_coil_params = TFCoilBuilderParams.from_dict(\n", + " {**build_config[\"params\"], **build_config[\"TF Coil\"][\"Builder\"].pop(\"params\")}\n", + ")" + ], + "metadata": { + "id": "ZTcNSYdK1R2V" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "We create our plasma" + ], + "metadata": { + "id": "OmmSif8A1VOt" + } + }, + { + "cell_type": "code", + "source": [ + "plasma_designer = PlasmaDesigner(plasma_params, build_config[\"Plasma\"])\n", + "plasma_parameterisation = plasma_designer.execute()\n", + "\n", + "plasma_builder = PlasmaBuilder(\n", + " plasma_parameterisation.create_shape(), build_config[\"Plasma\"]\n", + ")\n", + "plasma = plasma_builder.build()" + ], + "metadata": { + "id": "JPzQNKP51a5Q" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "We create our TF coil" + ], + "metadata": { + "id": "oeDmkGlf1bsn" + } + }, + { + "cell_type": "code", + "source": [ + "tf_coil_designer = TFCoilDesigner(\n", + " plasma.lcfs(), None, build_config[\"TF Coil\"][\"Designer\"]\n", + ")\n", + "tf_parameterisation = tf_coil_designer.execute()\n", + "\n", + "tf_coil_builder = TFCoilBuilder(tf_coil_params, tf_parameterisation.create_shape())\n", + "tf_coil = tf_coil_builder.build()" + ], + "metadata": { + "id": "Qd-LW3BC1epz" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Finally we add the components to the reactor and show the CAD" + ], + "metadata": { + "id": "ryT0YRKf1hQz" + } + }, + { + "cell_type": "code", + "source": [ + "reactor = MyReactor(\"Simple Example\")\n", + "\n", + "reactor.plasma = plasma\n", + "reactor.tf_coil = tf_coil\n", + "\n", + "reactor.show_cad()" + ], + "metadata": { + "id": "m1ges_Jk1kUP" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Once the optimisation above has started you can login to https://app.simvue.io, go to the **Runs** page and you should be able to find the currently running optimisation." + ], + "metadata": { + "id": "5keqr7gsaTPa" + } + } + ] +} \ No newline at end of file