diff --git a/README.md b/README.md
index 275561b2..dd2b333f 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,8 @@ Disclaimer: This project is currently under development. Use at your own risk.
Activities
@@ -290,7 +292,7 @@ print(my_actor.temperature_in_K)
#### Initializing PASEOS
We will now show how to create an instance of PASEOS. An instance of PASEOS shall be bounded to one PASEOS [actor](#actor) that we call [local actor](#local-actor). Please, notice that an orbit shall be placed for a [SpacecraftActor](#spacecraftactor) before being added to a PASEOS instance.
-### How to instantiate PASEOS
+#### How to instantiate PASEOS
```py
import pykep as pk
import paseos
@@ -354,7 +356,7 @@ cfg.sim.start_time=today.mjd2000 * pk.DAY2SEC
sim = paseos.init_sim(local_actor)
```
-### Faster than real-time execution
+#### Faster than real-time execution
In some cases, you may be interested to simulate your spacecraft operating for an extended period. By default, PASEOS operates in real-time, thus this would take a lot of time. However, you can increase the rate of time passing (i.e. the spacecraft moving, power being charged / consumed etc.) using the `time_multiplier` parameter. Set it as follows when initializing PASEOS.
@@ -366,9 +368,41 @@ paseos_instance = paseos.init_sim(my_local_actor, cfg) # initialize paseos insta
```
+#### Event-based mode
+Alternatively, you can rely on an event-based mode where PASEOS will simulate the physical constraints for an amount of time. The below code shows how to run PASEOS for a fixed amount of time or until an event interrupts it.
+
+```py
+ import pykep as pk
+ import paseos
+ from paseos import ActorBuilder, SpacecraftActor
+
+ # Define the central body as Earth by using pykep APIs.
+ earth = pk.planet.jpl_lp("earth")
+
+ # Define a satellite with some orbit and simple power model
+ my_sat = ActorBuilder.get_actor_scaffold("MySat", SpacecraftActor, pk.epoch(0))
+ ActorBuilder.set_orbit(sat1, [10000000, 0, 0], [0, 8000.0, 0], pk.epoch(0), earth)
+ ActorBuilder.set_power_devices(sat1, 500, 1000, 1)
+
+ # Abort when sat is at 10% battery
+ def constraint_func():
+ return sat1.state_of_charge > 0.1
+
+ # Set some settings to control evaluation of the constraint
+ cfg = load_default_cfg() # loading cfg to modify defaults
+ cfg.sim.dt = 0.1 # setting timestep of physical models (power, thermal, ...)
+ cfg.sim.activity_timestep = 1.0 # how often constraint func is evaluated
+ sim = paseos.init_sim(sat1, cfg) # Init simulation
+
+ # Advance for a long time, will interrupt much sooner due to constraint function
+ sim.advance_time(3600, 10, constraint_function=constraint_func)
+```
+
+
### Activities
#### Simple activity
-PASEOS enables the user to register their [activities](#activity) that will be executed on the `local actor`.
+PASEOS enables the user to register their [activities](#activity) that will be executed on the `local actor`. This is an alternative to the [event-based mode](#event-based-mode)
+
To register an activity, it is first necessary to define an asynchronous [activity function](#activity-function). The following code snippet shows how to create a simple [activity function](#activity-function) `activity_function_A` that prints "Hello Universe!". Then, it waits for 0.1 s before concluding the activity.
When you register an [activity](#activity), you need to specify the power consumption associated to the activity.
```py
diff --git a/examples/visualization/example_jupyter.ipynb b/examples/visualization/example_jupyter.ipynb
index 36d98b24..ab791b69 100644
--- a/examples/visualization/example_jupyter.ipynb
+++ b/examples/visualization/example_jupyter.ipynb
@@ -11,18 +11,9 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "\u001b[32m13:26:10\u001b[0m|PASEOS-\u001b[34mDEBUG\u001b[0m| \u001b[34m\u001b[1mSetting LogLevel to DEBUG\u001b[0m\n",
- "\u001b[32m13:26:10\u001b[0m|PASEOS-\u001b[34mDEBUG\u001b[0m| \u001b[34m\u001b[1mLoaded module.\u001b[0m\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2\n",
@@ -49,7 +40,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -65,28 +56,28 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
" # Define central body\n",
"earth = pk.planet.jpl_lp(\"earth\")\n",
"sat1 = ActorBuilder.get_actor_scaffold(\n",
- " \"sat1\", SpacecraftActor, [0, 0, 0], pk.epoch(0)\n",
+ " \"sat1\", SpacecraftActor, pk.epoch(0)\n",
" )\n",
"sat2 = ActorBuilder.get_actor_scaffold(\n",
- " \"sat2\", SpacecraftActor, [0, 0, 0], pk.epoch(0)\n",
+ " \"sat2\", SpacecraftActor, pk.epoch(0)\n",
")\n",
"\n",
"# Define local actor\n",
"sat3 = ActorBuilder.get_actor_scaffold(\n",
- " \"sat3\", SpacecraftActor, [10000000, 0, 0], pk.epoch(0)\n",
+ " \"sat3\", SpacecraftActor, pk.epoch(0)\n",
")\n",
"ActorBuilder.set_orbit(sat3, [-10000000, 0.1, 0.1], [0, 8000.0, 0], pk.epoch(0), earth)\n",
"ActorBuilder.set_power_devices(sat3, 500, 10000, 1)\n",
"\n",
"sat4 = ActorBuilder.get_actor_scaffold(\n",
- " \"sat4\", SpacecraftActor, [10000000, 0, 0], pk.epoch(0)\n",
+ " \"sat4\", SpacecraftActor, pk.epoch(0)\n",
")\n",
"ActorBuilder.set_orbit(sat4, [0, 10000000, 0], [0, 0, 8000.0], pk.epoch(0), earth)\n",
"\n",
@@ -119,987 +110,9 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "application/javascript": [
- "/* Put everything inside the global mpl namespace */\n",
- "/* global mpl */\n",
- "window.mpl = {};\n",
- "\n",
- "mpl.get_websocket_type = function () {\n",
- " if (typeof WebSocket !== 'undefined') {\n",
- " return WebSocket;\n",
- " } else if (typeof MozWebSocket !== 'undefined') {\n",
- " return MozWebSocket;\n",
- " } else {\n",
- " alert(\n",
- " 'Your browser does not have WebSocket support. ' +\n",
- " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
- " 'Firefox 4 and 5 are also supported but you ' +\n",
- " 'have to enable WebSockets in about:config.'\n",
- " );\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
- " this.id = figure_id;\n",
- "\n",
- " this.ws = websocket;\n",
- "\n",
- " this.supports_binary = this.ws.binaryType !== undefined;\n",
- "\n",
- " if (!this.supports_binary) {\n",
- " var warnings = document.getElementById('mpl-warnings');\n",
- " if (warnings) {\n",
- " warnings.style.display = 'block';\n",
- " warnings.textContent =\n",
- " 'This browser does not support binary websocket messages. ' +\n",
- " 'Performance may be slow.';\n",
- " }\n",
- " }\n",
- "\n",
- " this.imageObj = new Image();\n",
- "\n",
- " this.context = undefined;\n",
- " this.message = undefined;\n",
- " this.canvas = undefined;\n",
- " this.rubberband_canvas = undefined;\n",
- " this.rubberband_context = undefined;\n",
- " this.format_dropdown = undefined;\n",
- "\n",
- " this.image_mode = 'full';\n",
- "\n",
- " this.root = document.createElement('div');\n",
- " this.root.setAttribute('style', 'display: inline-block');\n",
- " this._root_extra_style(this.root);\n",
- "\n",
- " parent_element.appendChild(this.root);\n",
- "\n",
- " this._init_header(this);\n",
- " this._init_canvas(this);\n",
- " this._init_toolbar(this);\n",
- "\n",
- " var fig = this;\n",
- "\n",
- " this.waiting = false;\n",
- "\n",
- " this.ws.onopen = function () {\n",
- " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
- " fig.send_message('send_image_mode', {});\n",
- " if (fig.ratio !== 1) {\n",
- " fig.send_message('set_device_pixel_ratio', {\n",
- " device_pixel_ratio: fig.ratio,\n",
- " });\n",
- " }\n",
- " fig.send_message('refresh', {});\n",
- " };\n",
- "\n",
- " this.imageObj.onload = function () {\n",
- " if (fig.image_mode === 'full') {\n",
- " // Full images could contain transparency (where diff images\n",
- " // almost always do), so we need to clear the canvas so that\n",
- " // there is no ghosting.\n",
- " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
- " }\n",
- " fig.context.drawImage(fig.imageObj, 0, 0);\n",
- " };\n",
- "\n",
- " this.imageObj.onunload = function () {\n",
- " fig.ws.close();\n",
- " };\n",
- "\n",
- " this.ws.onmessage = this._make_on_message_function(this);\n",
- "\n",
- " this.ondownload = ondownload;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._init_header = function () {\n",
- " var titlebar = document.createElement('div');\n",
- " titlebar.classList =\n",
- " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
- " var titletext = document.createElement('div');\n",
- " titletext.classList = 'ui-dialog-title';\n",
- " titletext.setAttribute(\n",
- " 'style',\n",
- " 'width: 100%; text-align: center; padding: 3px;'\n",
- " );\n",
- " titlebar.appendChild(titletext);\n",
- " this.root.appendChild(titlebar);\n",
- " this.header = titletext;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
- "\n",
- "mpl.figure.prototype._init_canvas = function () {\n",
- " var fig = this;\n",
- "\n",
- " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
- " canvas_div.setAttribute(\n",
- " 'style',\n",
- " 'border: 1px solid #ddd;' +\n",
- " 'box-sizing: content-box;' +\n",
- " 'clear: both;' +\n",
- " 'min-height: 1px;' +\n",
- " 'min-width: 1px;' +\n",
- " 'outline: 0;' +\n",
- " 'overflow: hidden;' +\n",
- " 'position: relative;' +\n",
- " 'resize: both;'\n",
- " );\n",
- "\n",
- " function on_keyboard_event_closure(name) {\n",
- " return function (event) {\n",
- " return fig.key_event(event, name);\n",
- " };\n",
- " }\n",
- "\n",
- " canvas_div.addEventListener(\n",
- " 'keydown',\n",
- " on_keyboard_event_closure('key_press')\n",
- " );\n",
- " canvas_div.addEventListener(\n",
- " 'keyup',\n",
- " on_keyboard_event_closure('key_release')\n",
- " );\n",
- "\n",
- " this._canvas_extra_style(canvas_div);\n",
- " this.root.appendChild(canvas_div);\n",
- "\n",
- " var canvas = (this.canvas = document.createElement('canvas'));\n",
- " canvas.classList.add('mpl-canvas');\n",
- " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
- "\n",
- " this.context = canvas.getContext('2d');\n",
- "\n",
- " var backingStore =\n",
- " this.context.backingStorePixelRatio ||\n",
- " this.context.webkitBackingStorePixelRatio ||\n",
- " this.context.mozBackingStorePixelRatio ||\n",
- " this.context.msBackingStorePixelRatio ||\n",
- " this.context.oBackingStorePixelRatio ||\n",
- " this.context.backingStorePixelRatio ||\n",
- " 1;\n",
- "\n",
- " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
- "\n",
- " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
- " 'canvas'\n",
- " ));\n",
- " rubberband_canvas.setAttribute(\n",
- " 'style',\n",
- " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
- " );\n",
- "\n",
- " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
- " if (this.ResizeObserver === undefined) {\n",
- " if (window.ResizeObserver !== undefined) {\n",
- " this.ResizeObserver = window.ResizeObserver;\n",
- " } else {\n",
- " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
- " this.ResizeObserver = obs.ResizeObserver;\n",
- " }\n",
- " }\n",
- "\n",
- " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
- " var nentries = entries.length;\n",
- " for (var i = 0; i < nentries; i++) {\n",
- " var entry = entries[i];\n",
- " var width, height;\n",
- " if (entry.contentBoxSize) {\n",
- " if (entry.contentBoxSize instanceof Array) {\n",
- " // Chrome 84 implements new version of spec.\n",
- " width = entry.contentBoxSize[0].inlineSize;\n",
- " height = entry.contentBoxSize[0].blockSize;\n",
- " } else {\n",
- " // Firefox implements old version of spec.\n",
- " width = entry.contentBoxSize.inlineSize;\n",
- " height = entry.contentBoxSize.blockSize;\n",
- " }\n",
- " } else {\n",
- " // Chrome <84 implements even older version of spec.\n",
- " width = entry.contentRect.width;\n",
- " height = entry.contentRect.height;\n",
- " }\n",
- "\n",
- " // Keep the size of the canvas and rubber band canvas in sync with\n",
- " // the canvas container.\n",
- " if (entry.devicePixelContentBoxSize) {\n",
- " // Chrome 84 implements new version of spec.\n",
- " canvas.setAttribute(\n",
- " 'width',\n",
- " entry.devicePixelContentBoxSize[0].inlineSize\n",
- " );\n",
- " canvas.setAttribute(\n",
- " 'height',\n",
- " entry.devicePixelContentBoxSize[0].blockSize\n",
- " );\n",
- " } else {\n",
- " canvas.setAttribute('width', width * fig.ratio);\n",
- " canvas.setAttribute('height', height * fig.ratio);\n",
- " }\n",
- " canvas.setAttribute(\n",
- " 'style',\n",
- " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
- " );\n",
- "\n",
- " rubberband_canvas.setAttribute('width', width);\n",
- " rubberband_canvas.setAttribute('height', height);\n",
- "\n",
- " // And update the size in Python. We ignore the initial 0/0 size\n",
- " // that occurs as the element is placed into the DOM, which should\n",
- " // otherwise not happen due to the minimum size styling.\n",
- " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
- " fig.request_resize(width, height);\n",
- " }\n",
- " }\n",
- " });\n",
- " this.resizeObserverInstance.observe(canvas_div);\n",
- "\n",
- " function on_mouse_event_closure(name) {\n",
- " return function (event) {\n",
- " return fig.mouse_event(event, name);\n",
- " };\n",
- " }\n",
- "\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mousedown',\n",
- " on_mouse_event_closure('button_press')\n",
- " );\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mouseup',\n",
- " on_mouse_event_closure('button_release')\n",
- " );\n",
- " rubberband_canvas.addEventListener(\n",
- " 'dblclick',\n",
- " on_mouse_event_closure('dblclick')\n",
- " );\n",
- " // Throttle sequential mouse events to 1 every 20ms.\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mousemove',\n",
- " on_mouse_event_closure('motion_notify')\n",
- " );\n",
- "\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mouseenter',\n",
- " on_mouse_event_closure('figure_enter')\n",
- " );\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mouseleave',\n",
- " on_mouse_event_closure('figure_leave')\n",
- " );\n",
- "\n",
- " canvas_div.addEventListener('wheel', function (event) {\n",
- " if (event.deltaY < 0) {\n",
- " event.step = 1;\n",
- " } else {\n",
- " event.step = -1;\n",
- " }\n",
- " on_mouse_event_closure('scroll')(event);\n",
- " });\n",
- "\n",
- " canvas_div.appendChild(canvas);\n",
- " canvas_div.appendChild(rubberband_canvas);\n",
- "\n",
- " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
- " this.rubberband_context.strokeStyle = '#000000';\n",
- "\n",
- " this._resize_canvas = function (width, height, forward) {\n",
- " if (forward) {\n",
- " canvas_div.style.width = width + 'px';\n",
- " canvas_div.style.height = height + 'px';\n",
- " }\n",
- " };\n",
- "\n",
- " // Disable right mouse context menu.\n",
- " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
- " event.preventDefault();\n",
- " return false;\n",
- " });\n",
- "\n",
- " function set_focus() {\n",
- " canvas.focus();\n",
- " canvas_div.focus();\n",
- " }\n",
- "\n",
- " window.setTimeout(set_focus, 100);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function () {\n",
- " var fig = this;\n",
- "\n",
- " var toolbar = document.createElement('div');\n",
- " toolbar.classList = 'mpl-toolbar';\n",
- " this.root.appendChild(toolbar);\n",
- "\n",
- " function on_click_closure(name) {\n",
- " return function (_event) {\n",
- " return fig.toolbar_button_onclick(name);\n",
- " };\n",
- " }\n",
- "\n",
- " function on_mouseover_closure(tooltip) {\n",
- " return function (event) {\n",
- " if (!event.currentTarget.disabled) {\n",
- " return fig.toolbar_button_onmouseover(tooltip);\n",
- " }\n",
- " };\n",
- " }\n",
- "\n",
- " fig.buttons = {};\n",
- " var buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'mpl-button-group';\n",
- " for (var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " /* Instead of a spacer, we start a new button group. */\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- " buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'mpl-button-group';\n",
- " continue;\n",
- " }\n",
- "\n",
- " var button = (fig.buttons[name] = document.createElement('button'));\n",
- " button.classList = 'mpl-widget';\n",
- " button.setAttribute('role', 'button');\n",
- " button.setAttribute('aria-disabled', 'false');\n",
- " button.addEventListener('click', on_click_closure(method_name));\n",
- " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
- "\n",
- " var icon_img = document.createElement('img');\n",
- " icon_img.src = '_images/' + image + '.png';\n",
- " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
- " icon_img.alt = tooltip;\n",
- " button.appendChild(icon_img);\n",
- "\n",
- " buttonGroup.appendChild(button);\n",
- " }\n",
- "\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- "\n",
- " var fmt_picker = document.createElement('select');\n",
- " fmt_picker.classList = 'mpl-widget';\n",
- " toolbar.appendChild(fmt_picker);\n",
- " this.format_dropdown = fmt_picker;\n",
- "\n",
- " for (var ind in mpl.extensions) {\n",
- " var fmt = mpl.extensions[ind];\n",
- " var option = document.createElement('option');\n",
- " option.selected = fmt === mpl.default_extension;\n",
- " option.innerHTML = fmt;\n",
- " fmt_picker.appendChild(option);\n",
- " }\n",
- "\n",
- " var status_bar = document.createElement('span');\n",
- " status_bar.classList = 'mpl-message';\n",
- " toolbar.appendChild(status_bar);\n",
- " this.message = status_bar;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
- " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
- " // which will in turn request a refresh of the image.\n",
- " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.send_message = function (type, properties) {\n",
- " properties['type'] = type;\n",
- " properties['figure_id'] = this.id;\n",
- " this.ws.send(JSON.stringify(properties));\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.send_draw_message = function () {\n",
- " if (!this.waiting) {\n",
- " this.waiting = true;\n",
- " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
- " var format_dropdown = fig.format_dropdown;\n",
- " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
- " fig.ondownload(fig, format);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
- " var size = msg['size'];\n",
- " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
- " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
- " fig.send_message('refresh', {});\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
- " var x0 = msg['x0'] / fig.ratio;\n",
- " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
- " var x1 = msg['x1'] / fig.ratio;\n",
- " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
- " x0 = Math.floor(x0) + 0.5;\n",
- " y0 = Math.floor(y0) + 0.5;\n",
- " x1 = Math.floor(x1) + 0.5;\n",
- " y1 = Math.floor(y1) + 0.5;\n",
- " var min_x = Math.min(x0, x1);\n",
- " var min_y = Math.min(y0, y1);\n",
- " var width = Math.abs(x1 - x0);\n",
- " var height = Math.abs(y1 - y0);\n",
- "\n",
- " fig.rubberband_context.clearRect(\n",
- " 0,\n",
- " 0,\n",
- " fig.canvas.width / fig.ratio,\n",
- " fig.canvas.height / fig.ratio\n",
- " );\n",
- "\n",
- " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
- " // Updates the figure title.\n",
- " fig.header.textContent = msg['label'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
- " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
- " fig.message.textContent = msg['message'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
- " // Request the server to send over a new figure.\n",
- " fig.send_draw_message();\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
- " fig.image_mode = msg['mode'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
- " for (var key in msg) {\n",
- " if (!(key in fig.buttons)) {\n",
- " continue;\n",
- " }\n",
- " fig.buttons[key].disabled = !msg[key];\n",
- " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
- " if (msg['mode'] === 'PAN') {\n",
- " fig.buttons['Pan'].classList.add('active');\n",
- " fig.buttons['Zoom'].classList.remove('active');\n",
- " } else if (msg['mode'] === 'ZOOM') {\n",
- " fig.buttons['Pan'].classList.remove('active');\n",
- " fig.buttons['Zoom'].classList.add('active');\n",
- " } else {\n",
- " fig.buttons['Pan'].classList.remove('active');\n",
- " fig.buttons['Zoom'].classList.remove('active');\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function () {\n",
- " // Called whenever the canvas gets updated.\n",
- " this.send_message('ack', {});\n",
- "};\n",
- "\n",
- "// A function to construct a web socket function for onmessage handling.\n",
- "// Called in the figure constructor.\n",
- "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
- " return function socket_on_message(evt) {\n",
- " if (evt.data instanceof Blob) {\n",
- " var img = evt.data;\n",
- " if (img.type !== 'image/png') {\n",
- " /* FIXME: We get \"Resource interpreted as Image but\n",
- " * transferred with MIME type text/plain:\" errors on\n",
- " * Chrome. But how to set the MIME type? It doesn't seem\n",
- " * to be part of the websocket stream */\n",
- " img.type = 'image/png';\n",
- " }\n",
- "\n",
- " /* Free the memory for the previous frames */\n",
- " if (fig.imageObj.src) {\n",
- " (window.URL || window.webkitURL).revokeObjectURL(\n",
- " fig.imageObj.src\n",
- " );\n",
- " }\n",
- "\n",
- " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
- " img\n",
- " );\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " } else if (\n",
- " typeof evt.data === 'string' &&\n",
- " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
- " ) {\n",
- " fig.imageObj.src = evt.data;\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- "\n",
- " var msg = JSON.parse(evt.data);\n",
- " var msg_type = msg['type'];\n",
- "\n",
- " // Call the \"handle_{type}\" callback, which takes\n",
- " // the figure and JSON message as its only arguments.\n",
- " try {\n",
- " var callback = fig['handle_' + msg_type];\n",
- " } catch (e) {\n",
- " console.log(\n",
- " \"No handler for the '\" + msg_type + \"' message type: \",\n",
- " msg\n",
- " );\n",
- " return;\n",
- " }\n",
- "\n",
- " if (callback) {\n",
- " try {\n",
- " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
- " callback(fig, msg);\n",
- " } catch (e) {\n",
- " console.log(\n",
- " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
- " e,\n",
- " e.stack,\n",
- " msg\n",
- " );\n",
- " }\n",
- " }\n",
- " };\n",
- "};\n",
- "\n",
- "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
- "mpl.findpos = function (e) {\n",
- " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
- " var targ;\n",
- " if (!e) {\n",
- " e = window.event;\n",
- " }\n",
- " if (e.target) {\n",
- " targ = e.target;\n",
- " } else if (e.srcElement) {\n",
- " targ = e.srcElement;\n",
- " }\n",
- " if (targ.nodeType === 3) {\n",
- " // defeat Safari bug\n",
- " targ = targ.parentNode;\n",
- " }\n",
- "\n",
- " // pageX,Y are the mouse positions relative to the document\n",
- " var boundingRect = targ.getBoundingClientRect();\n",
- " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
- " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
- "\n",
- " return { x: x, y: y };\n",
- "};\n",
- "\n",
- "/*\n",
- " * return a copy of an object with only non-object keys\n",
- " * we need this to avoid circular references\n",
- " * https://stackoverflow.com/a/24161582/3208463\n",
- " */\n",
- "function simpleKeys(original) {\n",
- " return Object.keys(original).reduce(function (obj, key) {\n",
- " if (typeof original[key] !== 'object') {\n",
- " obj[key] = original[key];\n",
- " }\n",
- " return obj;\n",
- " }, {});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.mouse_event = function (event, name) {\n",
- " var canvas_pos = mpl.findpos(event);\n",
- "\n",
- " if (name === 'button_press') {\n",
- " this.canvas.focus();\n",
- " this.canvas_div.focus();\n",
- " }\n",
- "\n",
- " var x = canvas_pos.x * this.ratio;\n",
- " var y = canvas_pos.y * this.ratio;\n",
- "\n",
- " this.send_message(name, {\n",
- " x: x,\n",
- " y: y,\n",
- " button: event.button,\n",
- " step: event.step,\n",
- " guiEvent: simpleKeys(event),\n",
- " });\n",
- "\n",
- " /* This prevents the web browser from automatically changing to\n",
- " * the text insertion cursor when the button is pressed. We want\n",
- " * to control all of the cursor setting manually through the\n",
- " * 'cursor' event from matplotlib */\n",
- " event.preventDefault();\n",
- " return false;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
- " // Handle any extra behaviour associated with a key event\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.key_event = function (event, name) {\n",
- " // Prevent repeat events\n",
- " if (name === 'key_press') {\n",
- " if (event.key === this._key) {\n",
- " return;\n",
- " } else {\n",
- " this._key = event.key;\n",
- " }\n",
- " }\n",
- " if (name === 'key_release') {\n",
- " this._key = null;\n",
- " }\n",
- "\n",
- " var value = '';\n",
- " if (event.ctrlKey && event.key !== 'Control') {\n",
- " value += 'ctrl+';\n",
- " }\n",
- " else if (event.altKey && event.key !== 'Alt') {\n",
- " value += 'alt+';\n",
- " }\n",
- " else if (event.shiftKey && event.key !== 'Shift') {\n",
- " value += 'shift+';\n",
- " }\n",
- "\n",
- " value += 'k' + event.key;\n",
- "\n",
- " this._key_event_extra(event, name);\n",
- "\n",
- " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
- " return false;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
- " if (name === 'download') {\n",
- " this.handle_save(this, null);\n",
- " } else {\n",
- " this.send_message('toolbar_button', { name: name });\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
- " this.message.textContent = tooltip;\n",
- "};\n",
- "\n",
- "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
- "// prettier-ignore\n",
- "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
- "\n",
- "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
- "\n",
- "mpl.default_extension = \"png\";/* global mpl */\n",
- "\n",
- "var comm_websocket_adapter = function (comm) {\n",
- " // Create a \"websocket\"-like object which calls the given IPython comm\n",
- " // object with the appropriate methods. Currently this is a non binary\n",
- " // socket, so there is still some room for performance tuning.\n",
- " var ws = {};\n",
- "\n",
- " ws.binaryType = comm.kernel.ws.binaryType;\n",
- " ws.readyState = comm.kernel.ws.readyState;\n",
- " function updateReadyState(_event) {\n",
- " if (comm.kernel.ws) {\n",
- " ws.readyState = comm.kernel.ws.readyState;\n",
- " } else {\n",
- " ws.readyState = 3; // Closed state.\n",
- " }\n",
- " }\n",
- " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
- " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
- " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
- "\n",
- " ws.close = function () {\n",
- " comm.close();\n",
- " };\n",
- " ws.send = function (m) {\n",
- " //console.log('sending', m);\n",
- " comm.send(m);\n",
- " };\n",
- " // Register the callback with on_msg.\n",
- " comm.on_msg(function (msg) {\n",
- " //console.log('receiving', msg['content']['data'], msg);\n",
- " var data = msg['content']['data'];\n",
- " if (data['blob'] !== undefined) {\n",
- " data = {\n",
- " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
- " };\n",
- " }\n",
- " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
- " ws.onmessage(data);\n",
- " });\n",
- " return ws;\n",
- "};\n",
- "\n",
- "mpl.mpl_figure_comm = function (comm, msg) {\n",
- " // This is the function which gets called when the mpl process\n",
- " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
- "\n",
- " var id = msg.content.data.id;\n",
- " // Get hold of the div created by the display call when the Comm\n",
- " // socket was opened in Python.\n",
- " var element = document.getElementById(id);\n",
- " var ws_proxy = comm_websocket_adapter(comm);\n",
- "\n",
- " function ondownload(figure, _format) {\n",
- " window.open(figure.canvas.toDataURL());\n",
- " }\n",
- "\n",
- " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
- "\n",
- " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
- " // web socket which is closed, not our websocket->open comm proxy.\n",
- " ws_proxy.onopen();\n",
- "\n",
- " fig.parent_element = element;\n",
- " fig.cell_info = mpl.find_output_cell(\"\");\n",
- " if (!fig.cell_info) {\n",
- " console.error('Failed to find cell for figure', id, fig);\n",
- " return;\n",
- " }\n",
- " fig.cell_info[0].output_area.element.on(\n",
- " 'cleared',\n",
- " { fig: fig },\n",
- " fig._remove_fig_handler\n",
- " );\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
- " var width = fig.canvas.width / fig.ratio;\n",
- " fig.cell_info[0].output_area.element.off(\n",
- " 'cleared',\n",
- " fig._remove_fig_handler\n",
- " );\n",
- " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
- "\n",
- " // Update the output cell to use the data from the current canvas.\n",
- " fig.push_to_output();\n",
- " var dataURL = fig.canvas.toDataURL();\n",
- " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
- " // the notebook keyboard shortcuts fail.\n",
- " IPython.keyboard_manager.enable();\n",
- " fig.parent_element.innerHTML =\n",
- " '';\n",
- " fig.close_ws(fig, msg);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
- " fig.send_message('closing', msg);\n",
- " // fig.ws.close()\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
- " // Turn the data on the canvas into data in the output cell.\n",
- " var width = this.canvas.width / this.ratio;\n",
- " var dataURL = this.canvas.toDataURL();\n",
- " this.cell_info[1]['text/html'] =\n",
- " '';\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function () {\n",
- " // Tell IPython that the notebook contents must change.\n",
- " IPython.notebook.set_dirty(true);\n",
- " this.send_message('ack', {});\n",
- " var fig = this;\n",
- " // Wait a second, then push the new image to the DOM so\n",
- " // that it is saved nicely (might be nice to debounce this).\n",
- " setTimeout(function () {\n",
- " fig.push_to_output();\n",
- " }, 1000);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function () {\n",
- " var fig = this;\n",
- "\n",
- " var toolbar = document.createElement('div');\n",
- " toolbar.classList = 'btn-toolbar';\n",
- " this.root.appendChild(toolbar);\n",
- "\n",
- " function on_click_closure(name) {\n",
- " return function (_event) {\n",
- " return fig.toolbar_button_onclick(name);\n",
- " };\n",
- " }\n",
- "\n",
- " function on_mouseover_closure(tooltip) {\n",
- " return function (event) {\n",
- " if (!event.currentTarget.disabled) {\n",
- " return fig.toolbar_button_onmouseover(tooltip);\n",
- " }\n",
- " };\n",
- " }\n",
- "\n",
- " fig.buttons = {};\n",
- " var buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'btn-group';\n",
- " var button;\n",
- " for (var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " /* Instead of a spacer, we start a new button group. */\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- " buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'btn-group';\n",
- " continue;\n",
- " }\n",
- "\n",
- " button = fig.buttons[name] = document.createElement('button');\n",
- " button.classList = 'btn btn-default';\n",
- " button.href = '#';\n",
- " button.title = name;\n",
- " button.innerHTML = '';\n",
- " button.addEventListener('click', on_click_closure(method_name));\n",
- " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
- " buttonGroup.appendChild(button);\n",
- " }\n",
- "\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- "\n",
- " // Add the status bar.\n",
- " var status_bar = document.createElement('span');\n",
- " status_bar.classList = 'mpl-message pull-right';\n",
- " toolbar.appendChild(status_bar);\n",
- " this.message = status_bar;\n",
- "\n",
- " // Add the close button to the window.\n",
- " var buttongrp = document.createElement('div');\n",
- " buttongrp.classList = 'btn-group inline pull-right';\n",
- " button = document.createElement('button');\n",
- " button.classList = 'btn btn-mini btn-primary';\n",
- " button.href = '#';\n",
- " button.title = 'Stop Interaction';\n",
- " button.innerHTML = '';\n",
- " button.addEventListener('click', function (_evt) {\n",
- " fig.handle_close(fig, {});\n",
- " });\n",
- " button.addEventListener(\n",
- " 'mouseover',\n",
- " on_mouseover_closure('Stop Interaction')\n",
- " );\n",
- " buttongrp.appendChild(button);\n",
- " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
- " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
- " var fig = event.data.fig;\n",
- " if (event.target !== this) {\n",
- " // Ignore bubbled events from children.\n",
- " return;\n",
- " }\n",
- " fig.close_ws(fig, {});\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function (el) {\n",
- " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
- " // this is important to make the div 'focusable\n",
- " el.setAttribute('tabindex', 0);\n",
- " // reach out to IPython and tell the keyboard manager to turn it's self\n",
- " // off when our div gets focus\n",
- "\n",
- " // location in version 3\n",
- " if (IPython.notebook.keyboard_manager) {\n",
- " IPython.notebook.keyboard_manager.register_events(el);\n",
- " } else {\n",
- " // location in version 2\n",
- " IPython.keyboard_manager.register_events(el);\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
- " // Check for shift+enter\n",
- " if (event.shiftKey && event.which === 13) {\n",
- " this.canvas_div.blur();\n",
- " // select the cell after this one\n",
- " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
- " IPython.notebook.select(index + 1);\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
- " fig.ondownload(fig, null);\n",
- "};\n",
- "\n",
- "mpl.find_output_cell = function (html_output) {\n",
- " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
- " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
- " // IPython event is triggered only after the cells have been serialised, which for\n",
- " // our purposes (turning an active figure into a static one), is too late.\n",
- " var cells = IPython.notebook.get_cells();\n",
- " var ncells = cells.length;\n",
- " for (var i = 0; i < ncells; i++) {\n",
- " var cell = cells[i];\n",
- " if (cell.cell_type === 'code') {\n",
- " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
- " var data = cell.output_area.outputs[j];\n",
- " if (data.data) {\n",
- " // IPython >= 3 moved mimebundle to data attribute of output\n",
- " data = data.data;\n",
- " }\n",
- " if (data['text/html'] === html_output) {\n",
- " return [cell, data, j];\n",
- " }\n",
- " }\n",
- " }\n",
- " }\n",
- "};\n",
- "\n",
- "// Register the function which deals with the matplotlib target/channel.\n",
- "// The kernel may be null if the page has been refreshed.\n",
- "if (IPython.notebook.kernel !== null) {\n",
- " IPython.notebook.kernel.comm_manager.register_target(\n",
- " 'matplotlib',\n",
- " mpl.mpl_figure_comm\n",
- " );\n",
- "}\n"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/html": [
- ""
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
+ "outputs": [],
"source": [
"# Plot current status of PASEOS and get a plotter\n",
"plotter = paseos.plot(sim, paseos.PlotType.SpacePlot)"
@@ -1107,7 +120,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"metadata": {
"scrolled": false
},
@@ -1115,7 +128,7 @@
"source": [
"# Run some operations and inbetween update PASEOS\n",
"for i in range(100):\n",
- " sim.advance_time(10)\n",
+ " sim.advance_time(10,0)\n",
" plotter.update(sim)"
]
},
@@ -1148,7 +161,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.6"
+ "version": "3.10.8"
},
"vscode": {
"interpreter": {
diff --git a/paseos/activities/activity_manager.py b/paseos/activities/activity_manager.py
index e68b1361..e78126e2 100644
--- a/paseos/activities/activity_manager.py
+++ b/paseos/activities/activity_manager.py
@@ -135,7 +135,6 @@ async def job():
paseos_instance=self._paseos_instance,
activity_runner=activity_runner,
time_multiplier=self._paseos_time_multiplier,
- advance_paseos_clock=self._paseos_instance.use_automatic_clock,
)
await asyncio.wait(
diff --git a/paseos/activities/activity_processor.py b/paseos/activities/activity_processor.py
index e05d7c75..c3ed5a0b 100644
--- a/paseos/activities/activity_processor.py
+++ b/paseos/activities/activity_processor.py
@@ -17,7 +17,6 @@ def __init__(
paseos_instance,
activity_runner: ActivityRunner,
time_multiplier: float = 1,
- advance_paseos_clock=True,
):
"""Initializes the ActivityProcessor.
@@ -28,7 +27,6 @@ def __init__(
activity_runner (ActivityRunner): Runner of the activity that is performed.
Needed check if constraints are still valid.
time_multiplier (float): Specifies the rate at which times passes to allow faster-than-real time modeling.
- advance_paseos_clock (bool, optional): Whether to advanced the local time of
the actor and thus local simulation. Defaults to True.
"""
logger.trace("Initalized ActivityProcessor.")
@@ -48,7 +46,6 @@ def __init__(
self._task = None
self._paseos_instance = paseos_instance
self._activity_runner = activity_runner
- self._advance_paseos_clock = advance_paseos_clock
async def start(self):
"""Starts the processor."""
@@ -86,21 +83,8 @@ async def _update(self, elapsed_time: float):
logger.debug(f"Time since last update: {elapsed_time}s")
logger.trace(f"Applying time multiplier of {self._time_multiplier}")
elapsed_time *= self._time_multiplier
- if self._advance_paseos_clock:
- self._paseos_instance.advance_time(elapsed_time)
-
- # Update actor temperature
- if (
- hasattr(self._paseos_instance.local_actor, "_thermal_model")
- and self._paseos_instance.local_actor._thermal_model is not None
- ):
- self._paseos_instance.local_actor._thermal_model.update_temperature(
- elapsed_time, self._power_consumption_in_watt
- )
-
- # Update state of charge
- self._paseos_instance.local_actor.discharge(
- self._power_consumption_in_watt, elapsed_time
+ self._paseos_instance.advance_time(
+ elapsed_time, self._power_consumption_in_watt
)
async def _run(self):
diff --git a/paseos/actors/base_actor.py b/paseos/actors/base_actor.py
index 7749b44b..bc7eefd8 100644
--- a/paseos/actors/base_actor.py
+++ b/paseos/actors/base_actor.py
@@ -65,6 +65,36 @@ def __init__(self, name: str, epoch: pk.epoch) -> None:
self._communication_devices = DotMap(_dynamic=False)
+ @property
+ def has_power_model(self) -> bool:
+ """Returns true if actor's battery is modeled, else false.
+
+ Returns:
+ bool: bool indicating presence.
+ """
+ return (
+ hasattr(self, "_battery_level_in_Ws")
+ and self._battery_level_in_Ws is not None
+ )
+
+ @property
+ def has_thermal_model(self) -> bool:
+ """Returns true if actor's temperature is modeled, else false.
+
+ Returns:
+ bool: bool indicating presence.
+ """
+ return hasattr(self, "_thermal_model") and self._thermal_model is not None
+
+ @property
+ def mass(self) -> float:
+ """Returns actor's mass in kg.
+
+ Returns:
+ float: Mass
+ """
+ return self._mass
+
@property
def current_activity(self) -> str:
"""Returns the name of the activity the actor is currently performing.
@@ -118,13 +148,13 @@ def set_time(self, t: pk.epoch):
"""
self._local_time = t
- def charge(self, t0: pk.epoch, t1: pk.epoch):
- """Charges the actor during that period. Not implemented by default.
+ def charge(self, duration_in_s: float):
+ """Charges the actor from now for that period. Note that it is only
+ verified the actor is neither at start nor end of the period in eclipse,
+ thus short periods are preferable.
Args:
- t0 (pk.epoch): Start of the charging interval
- t1 (pk.epoch): End of the charging interval
-
+ duration_in_s (float): How long the activity is performed in seconds
"""
pass
diff --git a/paseos/actors/spacecraft_actor.py b/paseos/actors/spacecraft_actor.py
index f57612bf..7a11ad3f 100644
--- a/paseos/actors/spacecraft_actor.py
+++ b/paseos/actors/spacecraft_actor.py
@@ -95,25 +95,27 @@ def discharge(self, consumption_rate_in_W: float, duration_in_s: float):
self = discharge_model.discharge(self, power_consumption)
- def charge(self, t0: pk.epoch, t1: pk.epoch):
- """Charges the actor during that period. Note that it is only
+ logger.debug(f"New battery level is {self._battery_level_in_Ws}Ws")
+
+ def charge(self, duration_in_s: float):
+ """Charges the actor from now for that period. Note that it is only
verified the actor is neither at start nor end of the period in eclipse,
thus short periods are preferable.
Args:
- t0 (pk.epoch): Start of the charging interval
- t1 (pk.epoch): End of the charging interval
+ duration_in_s (float): How long the activity is performed in seconds
"""
- time_interval = (t1.mjd2000 - t0.mjd2000) * pk.DAY2SEC
- logger.debug(f"Charging actor {self} for {time_interval}s.")
+ logger.debug(f"Charging actor {self} for {duration_in_s}s.")
assert (
- time_interval > 0
+ duration_in_s > 0
), "Charging interval has to be positive but t1 was less or equal t0."
- if is_in_eclipse(self, central_body=self._central_body, t=t0) or is_in_eclipse(
- self, central_body=self._central_body, t=t1
- ):
+ # Compute end of charging time
+ t1 = pk.epoch(self.local_time.mjd2000 + duration_in_s * pk.SEC2DAY)
+ if is_in_eclipse(
+ self, central_body=self._central_body, t=self.local_time
+ ) or is_in_eclipse(self, central_body=self._central_body, t=t1):
logger.debug("Actor is in eclipse, not charging.")
else:
- self = charge_model.charge(self, time_interval)
+ self = charge_model.charge(self, duration_in_s)
logger.debug(f"New battery level is {self.battery_level_in_Ws}")
diff --git a/paseos/paseos.py b/paseos/paseos.py
index 396a94e7..d6956d83 100644
--- a/paseos/paseos.py
+++ b/paseos/paseos.py
@@ -34,13 +34,13 @@ class PASEOS:
# Semaphore to track if an activity is currently running
_is_running_activity = False
+ # Semaphore to track if we are currently running "advance_time"
+ _is_advancing_time = False
+
# Used to monitor the local actor over execution and write performance stats
_operations_monitor = None
_time_since_previous_log = sys.float_info.max
- # Use automatic clock (default on for now)
- use_automatic_clock = True
-
def __init__(self, local_actor: BaseActor, cfg=None):
"""Initalize PASEOS
@@ -55,11 +55,11 @@ def __init__(self, local_actor: BaseActor, cfg=None):
self._known_actors = {}
self._local_actor = local_actor
# Update local actor time to simulation start time.
- self._local_actor.set_time(pk.epoch(self._cfg.sim.start_time * pk.SEC2DAY))
+ self.local_actor.set_time(pk.epoch(self._cfg.sim.start_time * pk.SEC2DAY))
self._activity_manager = ActivityManager(
self, self._cfg.sim.activity_timestep, self._cfg.sim.time_multiplier
)
- self._operations_monitor = OperationsMonitor(self._local_actor.name)
+ self._operations_monitor = OperationsMonitor(self.local_actor.name)
def save_status_log_csv(self, filename) -> None:
"""Saves the status log incl. all kinds of information such as battery charge,
@@ -72,21 +72,51 @@ def save_status_log_csv(self, filename) -> None:
def log_status(self):
"""Updates the status log."""
- self._operations_monitor.log(self._local_actor, self.known_actor_names)
+ self._operations_monitor.log(self.local_actor, self.known_actor_names)
- def advance_time(self, time_to_advance: float):
+ def advance_time(
+ self,
+ time_to_advance: float,
+ current_power_consumption_in_W: float,
+ constraint_function: types.FunctionType = None,
+ ):
"""Advances the simulation by a specified amount of time
Args:
time_to_advance (float): Time to advance in seconds.
+ current_power_consumption_in_W (float): Current power consumed per second in Watt.
+ constraint_function(FunctionType): Constraint function which will be evaluated
+ every cfg.sim.activity_timestep seconds. Aborts the advancement if False.
+
"""
+ assert (
+ not self._is_advancing_time
+ ), "advance_time is already running. This function is not thread-safe. Avoid mixing (async) activities and calling it."
+ self._is_advancing_time = True
+
+ assert time_to_advance > 0, "Time to advance has to be positive."
+ assert (
+ current_power_consumption_in_W >= 0
+ ), "Power consumption cannot be negative."
+
logger.debug("Advancing time by " + str(time_to_advance) + " s.")
target_time = self._state.time + time_to_advance
dt = self._cfg.sim.dt
+ time_since_constraint_check = float("inf")
+
# Perform timesteps until target_time - dt reached,
# then final smaller or equal timestep to reach target_time
while self._state.time < target_time:
+ if (
+ constraint_function is not None
+ and time_since_constraint_check > self._cfg.sim.activity_timestep
+ ):
+ time_since_constraint_check = 0
+ if not constraint_function():
+ logger.info("Time advancing interrupted. Constraint false.")
+ break
+
if self._state.time > target_time - dt:
# compute final timestep to catch up
dt = target_time - self._state.time
@@ -95,13 +125,22 @@ def advance_time(self, time_to_advance: float):
# Perform updates for local actor (e.g. charging)
# Each actor only updates itself
# charge from current moment to time after timestep
- self._local_actor.charge(
- self._local_actor.local_time,
- pk.epoch((self._state.time + dt) * pk.SEC2DAY),
- )
+ if self.local_actor.has_power_model:
+ self._local_actor.charge(dt)
+
+ # Update actor temperature
+ if self.local_actor.has_thermal_model:
+ self.local_actor._thermal_model.update_temperature(
+ dt, current_power_consumption_in_W
+ )
+
+ # Update state of charge
+ if self.local_actor.has_power_model:
+ self.local_actor.discharge(current_power_consumption_in_W, dt)
self._state.time += dt
- self._local_actor.set_time(pk.epoch(self._state.time * pk.SEC2DAY))
+ time_since_constraint_check += dt
+ self.local_actor.set_time(pk.epoch(self._state.time * pk.SEC2DAY))
# Check if we should update the status log
if self._time_since_previous_log > self._cfg.io.logging_interval:
@@ -111,6 +150,7 @@ def advance_time(self, time_to_advance: float):
self._time_since_previous_log += dt
logger.debug("New time is: " + str(self._state.time) + " s.")
+ self._is_advancing_time = False
def add_known_actor(self, actor: BaseActor):
"""Adds an actor to the simulation.
diff --git a/paseos/tests/event_based_test.py b/paseos/tests/event_based_test.py
new file mode 100644
index 00000000..7eb1a685
--- /dev/null
+++ b/paseos/tests/event_based_test.py
@@ -0,0 +1,57 @@
+"""Simple test for the operations monitor"""
+
+import numpy as np
+import pykep as pk
+
+import paseos
+from paseos import ActorBuilder, SpacecraftActor, load_default_cfg
+
+
+async def test_event_based_mode():
+ """Test for PASEOS event-based mode where we advance time up to a certain event
+ using the "advance_time" function instead of async activity stuff.
+ The test runs a some activities for a while to see if the temperature and battery level
+ change as expected.
+ """
+ # Define central body
+ earth = pk.planet.jpl_lp("earth")
+
+ # Define some satellite with a few physical models
+ sat1 = ActorBuilder.get_actor_scaffold("sat1", SpacecraftActor, pk.epoch(0))
+ ActorBuilder.set_orbit(sat1, [10000000, 0, 0], [0, 8000.0, 0], pk.epoch(0), earth)
+ ActorBuilder.set_power_devices(sat1, 500, 1000, 1)
+ ActorBuilder.set_thermal_model(
+ actor=sat1,
+ actor_mass=50.0,
+ actor_initial_temperature_in_K=273.15,
+ actor_sun_absorptance=1.0,
+ actor_infrared_absorptance=1.0,
+ actor_sun_facing_area=1.0,
+ actor_central_body_facing_area=1.0,
+ actor_emissive_area=1.0,
+ actor_thermal_capacity=1000,
+ )
+
+ # Check initial condition
+ assert sat1.temperature_in_K == 273.15
+ assert sat1.battery_level_in_Ws == 500
+ assert np.isclose(sat1.state_of_charge, 0.5)
+
+ def constraint_func():
+ # Abort when sat is at 10% battery
+ print(sat1.state_of_charge)
+ return sat1.state_of_charge > 0.1
+
+ # init simulation
+ cfg = load_default_cfg() # loading cfg to modify defaults
+ cfg.sim.dt = 0.1 # setting higher timestep to run things quickly
+ cfg.sim.activity_timestep = 1.0 # frequency of constraint func
+ sim = paseos.init_sim(sat1, cfg)
+
+ # Advance for long time, will interrupt much sooner due to power outage
+ sim.advance_time(3600, 10, constraint_function=constraint_func)
+
+ assert sat1.temperature_in_K > 274 # should be a little hotter
+
+ # Power should have gone to roughly 10%
+ assert sat1.battery_level_in_Ws > 90 and sat1.battery_level_in_Ws < 100
diff --git a/paseos/tests/multiple_instance_test.py b/paseos/tests/multiple_instance_test.py
index c048bb03..6e7596fa 100644
--- a/paseos/tests/multiple_instance_test.py
+++ b/paseos/tests/multiple_instance_test.py
@@ -11,10 +11,10 @@ def test_multiple_instances():
# Initial power is 500m check charging works
assert sat1.battery_level_in_Ws == 500
assert sat2.battery_level_in_Ws == 500
- sim.advance_time(42)
+ sim.advance_time(42, 0)
assert sat1.battery_level_in_Ws == 542
assert sat2.battery_level_in_Ws == 500
- sim2.advance_time(42)
+ sim2.advance_time(42, 0)
assert sat1.battery_level_in_Ws == 542
assert sat2.battery_level_in_Ws == 542
diff --git a/paseos/tests/power_test.py b/paseos/tests/power_test.py
index e269f906..2e46440a 100644
--- a/paseos/tests/power_test.py
+++ b/paseos/tests/power_test.py
@@ -9,7 +9,7 @@ def test_power_charging():
# Initial power is 500m check charging works
assert sat1.battery_level_in_Ws == 500
- sim.advance_time(42)
+ sim.advance_time(42, 0)
assert sat1.battery_level_in_Ws == 542
# TODO check charging doesn't work when in eclipse
diff --git a/paseos/visualization/space_animation.py b/paseos/visualization/space_animation.py
index 4488c219..fd9944b4 100644
--- a/paseos/visualization/space_animation.py
+++ b/paseos/visualization/space_animation.py
@@ -393,7 +393,7 @@ def _animate(self, sim: PASEOS, dt: float) -> List[Artist]:
Returns:
List[Artist]: list of Artist objects
"""
- sim.advance_time(dt)
+ sim.advance_time(dt, 0)
self.update(sim, creating_animation=True)
return self.ax_3d.get_children() + self.ax_los.get_children()