{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7da63f8e",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "# Spice Frames\n",
    "\n",
    "This notebook walks the user through the process of creating Spice Frames. The {py:class}`Karana.Frame.SpiceFrame` is a special type of {py:class}`Karana.Frame.Frame` that is associated with planetary bodies for ephemerides. \n",
    "\n",
    "Requirements:\n",
    "- [2-link Pendulum](../example_2_link_pendulum/notebook.ipynb)\n",
    "\n",
    "In this tutorial we will:\n",
    "- Create SpiceFrames for planetary bodies\n",
    "- Create SpiceFrameToFrames to tie together ephemerides\n",
    "- Setup the **kdFlex** scene\n",
    "- Attach ProxySceneNodes to each planet\n",
    "- Query ephemeris data for bodies\n",
    "- Run and Clean up the Simulation\n",
    "\n",
    "![](../resources/nb_images/spice_frame.PNG)\n",
    "\n",
    " For a more in-depth descriptions of **kdflex** concepts see [usage](usage_page)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "652eca9b",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import atexit\n",
    "from typing import cast\n",
    "from time import sleep\n",
    "\n",
    "import Karana.Core as kc\n",
    "import Karana.Math as km\n",
    "import Karana.Integrators as ki\n",
    "import Karana.Frame as kf\n",
    "import Karana.Dynamics as kd\n",
    "import Karana.Dynamics.SOADyn_types as kdt\n",
    "import Karana.Scene as ks\n",
    "import Karana.KUtils as ku\n",
    "import Karana.Models as kmdl\n",
    "from Karana.KUtils.Sim import Sim"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35114999",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Create SpiceFrames for Planetary Bodies\n",
    "\n",
    "In order to use Spice Frames we need to make sure that we've loaded the proper NaifKernels. Below we make sure that the path to load these Kernels is correct. These kernels provide the Spice data for inner solar system bodies excluding Mars. To load other planetary bodies you will have to use different Naif kernels \n",
    "\n",
    "Datasets with Mars are significantly larger in size and take longer to load... hence Mars' exclusion from this notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a3cc5bf",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "yam_install = os.getenv(\"YAM_INSTALL\")\n",
    "if yam_install is not None:\n",
    "    kpath = yam_install + \"/share/Karana/spice_kernels/\"\n",
    "else:\n",
    "    kpath = \"/usr/share/Karana/spice_kernels/\"\n",
    "\n",
    "kf.SpiceFrame.loadNaifKernel(kpath + \"naif0012.tls\")\n",
    "kf.SpiceFrame.loadNaifKernel(kpath + \"pck00011.tpc\")\n",
    "kf.SpiceFrame.loadNaifKernel(kpath + \"de442s.bsp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92cef30c",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "We now initialize a {py:class}`Karana.KUtils.Sim.Sim` and create {py:class}`Karana.Frame.SpiceFrame`'s for bodies in the inner Solar System."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aeec626e",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# initialization\n",
    "sim = Sim()\n",
    "\n",
    "# create Spice Frames for celestial bodies\n",
    "mercury = kf.SpiceFrame(sim.fc, 199)\n",
    "venus = kf.SpiceFrame(sim.fc, 299)\n",
    "earth = kf.SpiceFrame(sim.fc, 399)\n",
    "\n",
    "# load solar system barycenter\n",
    "ssbc_j2000 = kf.SpiceFrame(sim.fc, 0, 1)\n",
    "\n",
    "# load the sun and moon\n",
    "sun_j2000 = kf.SpiceFrame(sim.fc, 10, 1)\n",
    "moon = kf.SpiceFrame(sim.fc, 301)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a40754ad",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Create SpiceFrameToFrames To Tie Together Ephemerides\n",
    "\n",
    "We also create {py:class}`Karana.Frame.SpiceFrame`2Frame's to connect the frames to one another. This is necessary in order tie all the ephemeris data together and query/visualize the planetary bodies' orbits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ffc8fa6",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# Create some Spice frame to frame edges. These can be from the barycenter to\n",
    "# a planetary body, or from planetary body to planetary body. It's necessary\n",
    "# for us to tie all these together so we can obtain relative ephemeris data\n",
    "# and then visualize it.\n",
    "kf.SpiceFrameToFrame(ssbc_j2000, sun_j2000)\n",
    "kf.SpiceFrameToFrame(ssbc_j2000, earth)\n",
    "kf.SpiceFrameToFrame(ssbc_j2000, mercury)\n",
    "kf.SpiceFrameToFrame(ssbc_j2000, venus)\n",
    "kf.SpiceFrameToFrame(earth, moon)\n",
    "\n",
    "kf.PrescribedFrameToFrame(sim.fc.root(), ssbc_j2000)\n",
    "\n",
    "# ensure the frame container is healthy and show the frame tree\n",
    "sim.fc.ensureHealthy()\n",
    "sim.fc.root().dumpFrameTree()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2119624",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "Next we set the epoch to query our Spice Frames with"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1407d444",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# Change the integrator to NO_OP, since we don't have any dynamics.\n",
    "# It will just keep track of time and propagate forward\n",
    "sim.sp.setIntegrator(ki.IntegratorType.NO_OP)\n",
    "sim.sp.setTime(0.0)\n",
    "sim.sp.setState(sim.sp.assembleState())\n",
    "\n",
    "# set the epoch\n",
    "sim.sp.getTimeKeeper().setInitialEphemeris(kd.TimeKeeper.utcTimeToEphemeris(\"2025-12-25T00:00:00\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f26c24d",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Setup the kdFlex Scene\n",
    "\n",
    "Set up the graphics scene and visualize the Spice Frames. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bc256c69",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# This helper method sets up the graphics\n",
    "_, web_scene = sim.setupGraphics(port=0)\n",
    "web_scene._setBackgroundColor(ks.Color.BLACK)\n",
    "\n",
    "# get the location of the Earth and set the web scene camera facing it\n",
    "earth_loc = sim.fc.root().frameToFrame(earth).relTransform().getTranslation()\n",
    "web_scene.defaultCamera().pointCameraAt([0.0, 0.0, 1e11], earth_loc, [0, 0, 1])\n",
    "projection = ks.PerspectiveProjection()\n",
    "projection.near = 1e8\n",
    "projection.far = 1e14\n",
    "web_scene.defaultCamera().setProjection(projection)\n",
    "del web_scene\n",
    "\n",
    "# get our proxy scene\n",
    "proxy_scene = sim.mb.getScene()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad885968",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Attach ProxySceneNodes to Each Planet\n",
    "\n",
    "Below we add ProxySceneNodes for each planetary body we are interested in tracking."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5c04f58",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "earth_node = ks.ProxySceneNode(\"earth_scene_node\", proxy_scene)\n",
    "earth_node.attachTo(earth)\n",
    "earth_node.graphics().showAxes(1e10)\n",
    "earth_node.graphics().trail(color=ks.Color.BLUE)\n",
    "\n",
    "ssbc_node = ks.ProxySceneNode(\"ssbc_scene_node\", proxy_scene)\n",
    "ssbc_node.attachTo(ssbc_j2000)\n",
    "ssbc_node.graphics().showAxes(8e10)\n",
    "ssbc_node.graphics().trail()\n",
    "del ssbc_node\n",
    "\n",
    "sun_node = ks.ProxySceneNode(\"sun_scene_node\", proxy_scene)\n",
    "sun_node.attachTo(sun_j2000)\n",
    "sun_node.graphics().showAxes(4e10)\n",
    "sun_node.graphics().trail()\n",
    "del sun_node\n",
    "\n",
    "mercury_node = ks.ProxySceneNode(\"mercury_scene_node\", proxy_scene)\n",
    "mercury_node.attachTo(mercury)\n",
    "mercury_node.graphics().showAxes(1e10)\n",
    "mercury_node.graphics().trail(color=ks.Color.DARKGRAY)\n",
    "del mercury_node\n",
    "\n",
    "venus_node = ks.ProxySceneNode(\"venus_scene_node\", proxy_scene)\n",
    "venus_node.attachTo(venus)\n",
    "venus_node.graphics().showAxes(1e10)\n",
    "venus_node.graphics().trail(color=ks.Color.DARKGOLDENROD)\n",
    "del venus_node\n",
    "\n",
    "moon_node = ks.ProxySceneNode(\"moon_scene_node\", proxy_scene)\n",
    "moon_node.attachTo(moon)\n",
    "moon_node.graphics().showAxes(5e9)\n",
    "moon_node.graphics().trail(earth_node.graphics(), color=ks.Color.GREY)\n",
    "del moon_node\n",
    "del earth_node\n",
    "\n",
    "proxy_scene.update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "612cfe43",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Run and Clean Up the Simulation\n",
    "\n",
    "Below we run our simulation. As the `StatePropagator` advances, it will update the ephemeris and the `SpiceFrame`s locations. Adding the graphics above already adds a model to update the ProxyScene. However, we want a KModel to add artificial sleeps in so we can see what is happening. Otherwise, the simulation will proceed so quickly we won't be able to see anything except the last frame. Hence, we add a SyncRealTime KModel to run this at a fraction of real time, we aim for 1 hour of simulation time every 0.01 seconds."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e260e8ec",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# Add a model to run things in a fraction of real-time\n",
    "kmdl.SyncRealTime(\"sync_time\", sim.sp, 3600 / 0.01)\n",
    "\n",
    "# Run for 2 years in increments of one hour\n",
    "for _ in range(24 * 365 * 2):\n",
    "    sim.sp.advanceBy(3600)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7718e57",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "Below, we cleanup everything whenever the script exists. This requires us to delete local variables and discard our containers and scene."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e00b26db",
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# Cleanup\n",
    "def cleanup():\n",
    "    \"\"\"Cleanup the simulation.\"\"\"\n",
    "    global mercury, earth, venus, moon, sun_j2000, ssbc_j2000, proxy_scene, sim\n",
    "    del mercury, moon, venus, earth, sun_j2000, ssbc_j2000, proxy_scene, sim\n",
    "\n",
    "\n",
    "atexit.register(cleanup)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  },
  "name": "notebook.ipynb"
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
