2022-02-23
This commit is contained in:
parent
35a0c40d14
commit
dc68cce9ed
|
@ -26,3 +26,4 @@ ed_lrr_gui/web/ed_lrr_web_ui.db
|
||||||
__version__.py
|
__version__.py
|
||||||
.nox/
|
.nox/
|
||||||
dist_vis.py
|
dist_vis.py
|
||||||
|
img/**
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"cells": [],
|
||||||
|
"metadata": {},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 4
|
||||||
|
}
|
|
@ -0,0 +1,904 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Populating the interactive namespace from numpy and matplotlib\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"%pylab notebook"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 131,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"<ipython-input-131-1f27bb9e71ac>:5: RuntimeWarning: invalid value encountered in double_scalars\n",
|
||||||
|
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"application/javascript": [
|
||||||
|
"/* Put everything inside the global mpl namespace */\n",
|
||||||
|
"window.mpl = {};\n",
|
||||||
|
"\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('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",
|
||||||
|
"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 = $('<div/>');\n",
|
||||||
|
" this._root_extra_style(this.root)\n",
|
||||||
|
" this.root.attr('style', 'display: inline-block');\n",
|
||||||
|
"\n",
|
||||||
|
" $(parent_element).append(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 (mpl.ratio != 1) {\n",
|
||||||
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\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 = $(\n",
|
||||||
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
||||||
|
" 'ui-helper-clearfix\"/>');\n",
|
||||||
|
" var titletext = $(\n",
|
||||||
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
||||||
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
||||||
|
" titlebar.append(titletext)\n",
|
||||||
|
" this.root.append(titlebar);\n",
|
||||||
|
" this.header = titletext[0];\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
||||||
|
"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
||||||
|
"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
||||||
|
" var fig = this;\n",
|
||||||
|
"\n",
|
||||||
|
" var canvas_div = $('<div/>');\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
||||||
|
"\n",
|
||||||
|
" function canvas_keyboard_event(event) {\n",
|
||||||
|
" return fig.key_event(event, event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
||||||
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
||||||
|
" this.canvas_div = canvas_div\n",
|
||||||
|
" this._canvas_extra_style(canvas_div)\n",
|
||||||
|
" this.root.append(canvas_div);\n",
|
||||||
|
"\n",
|
||||||
|
" var canvas = $('<canvas/>');\n",
|
||||||
|
" canvas.addClass('mpl-canvas');\n",
|
||||||
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
||||||
|
"\n",
|
||||||
|
" this.canvas = canvas[0];\n",
|
||||||
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
||||||
|
"\n",
|
||||||
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
||||||
|
"\n",
|
||||||
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
||||||
|
"\n",
|
||||||
|
" var rubberband = $('<canvas/>');\n",
|
||||||
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
||||||
|
"\n",
|
||||||
|
" var pass_mouse_events = true;\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.resizable({\n",
|
||||||
|
" start: function(event, ui) {\n",
|
||||||
|
" pass_mouse_events = false;\n",
|
||||||
|
" },\n",
|
||||||
|
" resize: function(event, ui) {\n",
|
||||||
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
||||||
|
" },\n",
|
||||||
|
" stop: function(event, ui) {\n",
|
||||||
|
" pass_mouse_events = true;\n",
|
||||||
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
||||||
|
" },\n",
|
||||||
|
" });\n",
|
||||||
|
"\n",
|
||||||
|
" function mouse_event_fn(event) {\n",
|
||||||
|
" if (pass_mouse_events)\n",
|
||||||
|
" return fig.mouse_event(event, event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
||||||
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
||||||
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
||||||
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
||||||
|
"\n",
|
||||||
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
||||||
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
||||||
|
" event = event.originalEvent;\n",
|
||||||
|
" event['data'] = 'scroll'\n",
|
||||||
|
" if (event.deltaY < 0) {\n",
|
||||||
|
" event.step = 1;\n",
|
||||||
|
" } else {\n",
|
||||||
|
" event.step = -1;\n",
|
||||||
|
" }\n",
|
||||||
|
" mouse_event_fn(event);\n",
|
||||||
|
" });\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.append(canvas);\n",
|
||||||
|
" canvas_div.append(rubberband);\n",
|
||||||
|
"\n",
|
||||||
|
" this.rubberband = rubberband;\n",
|
||||||
|
" this.rubberband_canvas = rubberband[0];\n",
|
||||||
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
||||||
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
||||||
|
"\n",
|
||||||
|
" this._resize_canvas = function(width, height) {\n",
|
||||||
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
||||||
|
" // canvas in synch.\n",
|
||||||
|
" canvas_div.css('width', width)\n",
|
||||||
|
" canvas_div.css('height', height)\n",
|
||||||
|
"\n",
|
||||||
|
" canvas.attr('width', width * mpl.ratio);\n",
|
||||||
|
" canvas.attr('height', height * mpl.ratio);\n",
|
||||||
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
||||||
|
"\n",
|
||||||
|
" rubberband.attr('width', width);\n",
|
||||||
|
" rubberband.attr('height', height);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
||||||
|
" // upon first draw.\n",
|
||||||
|
" this._resize_canvas(600, 600);\n",
|
||||||
|
"\n",
|
||||||
|
" // Disable right mouse context menu.\n",
|
||||||
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\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 nav_element = $('<div/>');\n",
|
||||||
|
" nav_element.attr('style', 'width: 100%');\n",
|
||||||
|
" this.root.append(nav_element);\n",
|
||||||
|
"\n",
|
||||||
|
" // Define a callback function for later on.\n",
|
||||||
|
" function toolbar_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
" function toolbar_mouse_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\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",
|
||||||
|
" // put a spacer in here.\n",
|
||||||
|
" continue;\n",
|
||||||
|
" }\n",
|
||||||
|
" var button = $('<button/>');\n",
|
||||||
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
||||||
|
" 'ui-button-icon-only');\n",
|
||||||
|
" button.attr('role', 'button');\n",
|
||||||
|
" button.attr('aria-disabled', 'false');\n",
|
||||||
|
" button.click(method_name, toolbar_event);\n",
|
||||||
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
||||||
|
"\n",
|
||||||
|
" var icon_img = $('<span/>');\n",
|
||||||
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
||||||
|
" icon_img.addClass(image);\n",
|
||||||
|
" icon_img.addClass('ui-corner-all');\n",
|
||||||
|
"\n",
|
||||||
|
" var tooltip_span = $('<span/>');\n",
|
||||||
|
" tooltip_span.addClass('ui-button-text');\n",
|
||||||
|
" tooltip_span.html(tooltip);\n",
|
||||||
|
"\n",
|
||||||
|
" button.append(icon_img);\n",
|
||||||
|
" button.append(tooltip_span);\n",
|
||||||
|
"\n",
|
||||||
|
" nav_element.append(button);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var fmt_picker_span = $('<span/>');\n",
|
||||||
|
"\n",
|
||||||
|
" var fmt_picker = $('<select/>');\n",
|
||||||
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
||||||
|
" fmt_picker_span.append(fmt_picker);\n",
|
||||||
|
" nav_element.append(fmt_picker_span);\n",
|
||||||
|
" this.format_dropdown = fmt_picker[0];\n",
|
||||||
|
"\n",
|
||||||
|
" for (var ind in mpl.extensions) {\n",
|
||||||
|
" var fmt = mpl.extensions[ind];\n",
|
||||||
|
" var option = $(\n",
|
||||||
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
||||||
|
" fmt_picker.append(option);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" // Add hover states to the ui-buttons\n",
|
||||||
|
" $( \".ui-button\" ).hover(\n",
|
||||||
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
||||||
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
||||||
|
" );\n",
|
||||||
|
"\n",
|
||||||
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
||||||
|
" nav_element.append(status_bar);\n",
|
||||||
|
" this.message = status_bar[0];\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",
|
||||||
|
"\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",
|
||||||
|
"\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]);\n",
|
||||||
|
" fig.send_message(\"refresh\", {});\n",
|
||||||
|
" };\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
||||||
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
||||||
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
||||||
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
||||||
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.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, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\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",
|
||||||
|
" var cursor = msg['cursor'];\n",
|
||||||
|
" switch(cursor)\n",
|
||||||
|
" {\n",
|
||||||
|
" case 0:\n",
|
||||||
|
" cursor = 'pointer';\n",
|
||||||
|
" break;\n",
|
||||||
|
" case 1:\n",
|
||||||
|
" cursor = 'default';\n",
|
||||||
|
" break;\n",
|
||||||
|
" case 2:\n",
|
||||||
|
" cursor = 'crosshair';\n",
|
||||||
|
" break;\n",
|
||||||
|
" case 3:\n",
|
||||||
|
" cursor = 'move';\n",
|
||||||
|
" break;\n",
|
||||||
|
" }\n",
|
||||||
|
" fig.rubberband_canvas.style.cursor = 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.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",
|
||||||
|
" /* 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",
|
||||||
|
" evt.data.type = \"image/png\";\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",
|
||||||
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
||||||
|
" evt.data);\n",
|
||||||
|
" fig.updated_canvas_event();\n",
|
||||||
|
" fig.waiting = false;\n",
|
||||||
|
" return;\n",
|
||||||
|
" }\n",
|
||||||
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\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(\"No handler for the '\" + msg_type + \"' message type: \", msg);\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(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
||||||
|
" }\n",
|
||||||
|
" }\n",
|
||||||
|
" };\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"// from http://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",
|
||||||
|
" if (e.target)\n",
|
||||||
|
" targ = e.target;\n",
|
||||||
|
" else if (e.srcElement)\n",
|
||||||
|
" targ = e.srcElement;\n",
|
||||||
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
||||||
|
" targ = targ.parentNode;\n",
|
||||||
|
"\n",
|
||||||
|
" // jQuery normalizes the pageX and pageY\n",
|
||||||
|
" // pageX,Y are the mouse positions relative to the document\n",
|
||||||
|
" // offset() returns the position of the element relative to the document\n",
|
||||||
|
" var x = e.pageX - $(targ).offset().left;\n",
|
||||||
|
" var y = e.pageY - $(targ).offset().top;\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",
|
||||||
|
" * http://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",
|
||||||
|
" 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",
|
||||||
|
" {\n",
|
||||||
|
" this.canvas.focus();\n",
|
||||||
|
" this.canvas_div.focus();\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
||||||
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
||||||
|
"\n",
|
||||||
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
||||||
|
" step: event.step,\n",
|
||||||
|
" guiEvent: simpleKeys(event)});\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",
|
||||||
|
"\n",
|
||||||
|
" // Prevent repeat events\n",
|
||||||
|
" if (name == 'key_press')\n",
|
||||||
|
" {\n",
|
||||||
|
" if (event.which === this._key)\n",
|
||||||
|
" return;\n",
|
||||||
|
" else\n",
|
||||||
|
" this._key = event.which;\n",
|
||||||
|
" }\n",
|
||||||
|
" if (name == 'key_release')\n",
|
||||||
|
" this._key = null;\n",
|
||||||
|
"\n",
|
||||||
|
" var value = '';\n",
|
||||||
|
" if (event.ctrlKey && event.which != 17)\n",
|
||||||
|
" value += \"ctrl+\";\n",
|
||||||
|
" if (event.altKey && event.which != 18)\n",
|
||||||
|
" value += \"alt+\";\n",
|
||||||
|
" if (event.shiftKey && event.which != 16)\n",
|
||||||
|
" value += \"shift+\";\n",
|
||||||
|
"\n",
|
||||||
|
" value += 'k';\n",
|
||||||
|
" value += event.which.toString();\n",
|
||||||
|
"\n",
|
||||||
|
" this._key_event_extra(event, name);\n",
|
||||||
|
"\n",
|
||||||
|
" this.send_message(name, {key: value,\n",
|
||||||
|
" 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",
|
||||||
|
"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\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.default_extension = \"png\";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.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",
|
||||||
|
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
|
||||||
|
" ws.onmessage(msg['content']['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 = $(\"#\" + id);\n",
|
||||||
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
||||||
|
"\n",
|
||||||
|
" function ondownload(figure, format) {\n",
|
||||||
|
" window.open(figure.imageObj.src);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
||||||
|
" ondownload,\n",
|
||||||
|
" element.get(0));\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.get(0);\n",
|
||||||
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
||||||
|
" if (!fig.cell_info) {\n",
|
||||||
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
||||||
|
" return;\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var output_index = fig.cell_info[2]\n",
|
||||||
|
" var cell = fig.cell_info[0];\n",
|
||||||
|
"\n",
|
||||||
|
"};\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
||||||
|
" var width = fig.canvas.width/mpl.ratio\n",
|
||||||
|
" fig.root.unbind('remove')\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).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\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/mpl.ratio\n",
|
||||||
|
" var dataURL = this.canvas.toDataURL();\n",
|
||||||
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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 () { fig.push_to_output() }, 1000);\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
||||||
|
" var fig = this;\n",
|
||||||
|
"\n",
|
||||||
|
" var nav_element = $('<div/>');\n",
|
||||||
|
" nav_element.attr('style', 'width: 100%');\n",
|
||||||
|
" this.root.append(nav_element);\n",
|
||||||
|
"\n",
|
||||||
|
" // Define a callback function for later on.\n",
|
||||||
|
" function toolbar_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
" function toolbar_mouse_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\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) { continue; };\n",
|
||||||
|
"\n",
|
||||||
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
||||||
|
" button.click(method_name, toolbar_event);\n",
|
||||||
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
||||||
|
" nav_element.append(button);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" // Add the status bar.\n",
|
||||||
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
||||||
|
" nav_element.append(status_bar);\n",
|
||||||
|
" this.message = status_bar[0];\n",
|
||||||
|
"\n",
|
||||||
|
" // Add the close button to the window.\n",
|
||||||
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
||||||
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
||||||
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
||||||
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
||||||
|
" buttongrp.append(button);\n",
|
||||||
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
||||||
|
" titlebar.prepend(buttongrp);\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
||||||
|
" var fig = this\n",
|
||||||
|
" el.on(\"remove\", function(){\n",
|
||||||
|
"\tfig.close_ws(fig, {});\n",
|
||||||
|
" });\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
||||||
|
" // this is important to make the div 'focusable\n",
|
||||||
|
" el.attr('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",
|
||||||
|
" }\n",
|
||||||
|
" else {\n",
|
||||||
|
" // location in version 2\n",
|
||||||
|
" IPython.keyboard_manager.register_events(el);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
||||||
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
||||||
|
" if (!manager)\n",
|
||||||
|
" manager = IPython.keyboard_manager;\n",
|
||||||
|
"\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",
|
||||||
|
"\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('matplotlib', mpl.mpl_figure_comm);\n",
|
||||||
|
"}\n"
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.Javascript object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/html": [
|
||||||
|
"<div id='215b26ff-80fe-45a3-bb52-9351ea1b2ebb'></div>"
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.HTML object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"(0, 100)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 131,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def h(p,src,dest):\n",
|
||||||
|
" c = (((src-dest)**2).sum())**0.5\n",
|
||||||
|
" a = (((dest-p)**2).sum())**0.5\n",
|
||||||
|
" b = (((src-p)**2).sum())**0.5\n",
|
||||||
|
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n",
|
||||||
|
" return a-b\n",
|
||||||
|
"S=np.array([10,10])\n",
|
||||||
|
"D=np.array([20,25])\n",
|
||||||
|
"\n",
|
||||||
|
"Rx=range(100)\n",
|
||||||
|
"Ry=range(100)\n",
|
||||||
|
"grid=np.zeros((len(Rx),len(Ry)))\n",
|
||||||
|
"for px,x in enumerate(Rx):\n",
|
||||||
|
" for py,y in enumerate(Ry):\n",
|
||||||
|
" grid[px,py]=h(np.array([x,y]),S,D)\n",
|
||||||
|
"imshow(grid,cmap='coolwarm_r',origin='lower')\n",
|
||||||
|
"colorbar()\n",
|
||||||
|
"\n",
|
||||||
|
"scatter(*S,color='green')\n",
|
||||||
|
"scatter(*D,color='red')\n",
|
||||||
|
"plt.xlim(0,100)\n",
|
||||||
|
"plt.ylim(0,100)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 38,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Object `np.magnitude` not found.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"np."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3.8.1 64-bit ('anaconda': conda)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python38164bitanacondaconda2a51168e890d45bd836f654eb2ae46f7"
|
||||||
|
},
|
||||||
|
"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.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 4
|
||||||
|
}
|
|
@ -0,0 +1,505 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Form generated from reading UI file 'ed_lrr.ui'
|
||||||
|
##
|
||||||
|
## Created by: Qt User Interface Compiler version 5.15.2
|
||||||
|
##
|
||||||
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
|
||||||
|
|
||||||
|
class Ui_ED_LRR(object):
|
||||||
|
def setupUi(self, ED_LRR):
|
||||||
|
if not ED_LRR.objectName():
|
||||||
|
ED_LRR.setObjectName(u"ED_LRR")
|
||||||
|
ED_LRR.setEnabled(True)
|
||||||
|
ED_LRR.resize(577, 500)
|
||||||
|
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||||
|
sizePolicy.setHorizontalStretch(0)
|
||||||
|
sizePolicy.setVerticalStretch(0)
|
||||||
|
sizePolicy.setHeightForWidth(ED_LRR.sizePolicy().hasHeightForWidth())
|
||||||
|
ED_LRR.setSizePolicy(sizePolicy)
|
||||||
|
ED_LRR.setMinimumSize(QSize(577, 500))
|
||||||
|
ED_LRR.setMaximumSize(QSize(577, 500))
|
||||||
|
ED_LRR.setStyleSheet(u"")
|
||||||
|
ED_LRR.setDocumentMode(False)
|
||||||
|
ED_LRR.setTabShape(QTabWidget.Rounded)
|
||||||
|
self.menu_act_quit = QAction(ED_LRR)
|
||||||
|
self.menu_act_quit.setObjectName(u"menu_act_quit")
|
||||||
|
self.actionA = QAction(ED_LRR)
|
||||||
|
self.actionA.setObjectName(u"actionA")
|
||||||
|
self.actionB = QAction(ED_LRR)
|
||||||
|
self.actionB.setObjectName(u"actionB")
|
||||||
|
self.centralwidget = QWidget(ED_LRR)
|
||||||
|
self.centralwidget.setObjectName(u"centralwidget")
|
||||||
|
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
||||||
|
sizePolicy1.setHorizontalStretch(0)
|
||||||
|
sizePolicy1.setVerticalStretch(0)
|
||||||
|
sizePolicy1.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
|
||||||
|
self.centralwidget.setSizePolicy(sizePolicy1)
|
||||||
|
self.verticalLayout = QVBoxLayout(self.centralwidget)
|
||||||
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||||
|
self.tabs = QTabWidget(self.centralwidget)
|
||||||
|
self.tabs.setObjectName(u"tabs")
|
||||||
|
self.tabs.setEnabled(True)
|
||||||
|
self.tabs.setAutoFillBackground(False)
|
||||||
|
self.tabs.setTabPosition(QTabWidget.North)
|
||||||
|
self.tabs.setTabShape(QTabWidget.Rounded)
|
||||||
|
self.tabs.setElideMode(Qt.ElideNone)
|
||||||
|
self.tabs.setTabsClosable(False)
|
||||||
|
self.tabs.setTabBarAutoHide(False)
|
||||||
|
self.tab_download = QWidget()
|
||||||
|
self.tab_download.setObjectName(u"tab_download")
|
||||||
|
self.formLayout = QFormLayout(self.tab_download)
|
||||||
|
self.formLayout.setObjectName(u"formLayout")
|
||||||
|
self.lbl_bodies_dl = QLabel(self.tab_download)
|
||||||
|
self.lbl_bodies_dl.setObjectName(u"lbl_bodies_dl")
|
||||||
|
|
||||||
|
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.lbl_bodies_dl)
|
||||||
|
|
||||||
|
self.lbl_systems_dl = QLabel(self.tab_download)
|
||||||
|
self.lbl_systems_dl.setObjectName(u"lbl_systems_dl")
|
||||||
|
|
||||||
|
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.lbl_systems_dl)
|
||||||
|
|
||||||
|
self.inp_bodies_dl = QComboBox(self.tab_download)
|
||||||
|
self.inp_bodies_dl.setObjectName(u"inp_bodies_dl")
|
||||||
|
self.inp_bodies_dl.setEditable(True)
|
||||||
|
self.inp_bodies_dl.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.inp_bodies_dl)
|
||||||
|
|
||||||
|
self.inp_systems_dl = QComboBox(self.tab_download)
|
||||||
|
self.inp_systems_dl.setObjectName(u"inp_systems_dl")
|
||||||
|
self.inp_systems_dl.setEditable(True)
|
||||||
|
self.inp_systems_dl.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.inp_systems_dl)
|
||||||
|
|
||||||
|
self.gridLayout = QGridLayout()
|
||||||
|
self.gridLayout.setObjectName(u"gridLayout")
|
||||||
|
self.inp_bodies_dest_dl = QComboBox(self.tab_download)
|
||||||
|
self.inp_bodies_dest_dl.setObjectName(u"inp_bodies_dest_dl")
|
||||||
|
sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
|
sizePolicy2.setHorizontalStretch(0)
|
||||||
|
sizePolicy2.setVerticalStretch(0)
|
||||||
|
sizePolicy2.setHeightForWidth(self.inp_bodies_dest_dl.sizePolicy().hasHeightForWidth())
|
||||||
|
self.inp_bodies_dest_dl.setSizePolicy(sizePolicy2)
|
||||||
|
self.inp_bodies_dest_dl.setEditable(False)
|
||||||
|
self.inp_bodies_dest_dl.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.inp_bodies_dest_dl, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
self.btn_bodies_dest_browse_dl = QPushButton(self.tab_download)
|
||||||
|
self.btn_bodies_dest_browse_dl.setObjectName(u"btn_bodies_dest_browse_dl")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.btn_bodies_dest_browse_dl, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout.setLayout(2, QFormLayout.FieldRole, self.gridLayout)
|
||||||
|
|
||||||
|
self.gridLayout_2 = QGridLayout()
|
||||||
|
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||||
|
self.btn_systems_dest_browse_dl = QPushButton(self.tab_download)
|
||||||
|
self.btn_systems_dest_browse_dl.setObjectName(u"btn_systems_dest_browse_dl")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.btn_systems_dest_browse_dl, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
self.inp_systems_dest_dl = QComboBox(self.tab_download)
|
||||||
|
self.inp_systems_dest_dl.setObjectName(u"inp_systems_dest_dl")
|
||||||
|
sizePolicy2.setHeightForWidth(self.inp_systems_dest_dl.sizePolicy().hasHeightForWidth())
|
||||||
|
self.inp_systems_dest_dl.setSizePolicy(sizePolicy2)
|
||||||
|
self.inp_systems_dest_dl.setEditable(False)
|
||||||
|
self.inp_systems_dest_dl.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.inp_systems_dest_dl, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout.setLayout(4, QFormLayout.FieldRole, self.gridLayout_2)
|
||||||
|
|
||||||
|
self.btn_download = QPushButton(self.tab_download)
|
||||||
|
self.btn_download.setObjectName(u"btn_download")
|
||||||
|
|
||||||
|
self.formLayout.setWidget(5, QFormLayout.LabelRole, self.btn_download)
|
||||||
|
|
||||||
|
self.label = QLabel(self.tab_download)
|
||||||
|
self.label.setObjectName(u"label")
|
||||||
|
|
||||||
|
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label)
|
||||||
|
|
||||||
|
self.label_2 = QLabel(self.tab_download)
|
||||||
|
self.label_2.setObjectName(u"label_2")
|
||||||
|
|
||||||
|
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_2)
|
||||||
|
|
||||||
|
self.tabs.addTab(self.tab_download, "")
|
||||||
|
self.tab_preprocess = QWidget()
|
||||||
|
self.tab_preprocess.setObjectName(u"tab_preprocess")
|
||||||
|
self.formLayout_3 = QFormLayout(self.tab_preprocess)
|
||||||
|
self.formLayout_3.setObjectName(u"formLayout_3")
|
||||||
|
self.lbl_bodies_pp = QLabel(self.tab_preprocess)
|
||||||
|
self.lbl_bodies_pp.setObjectName(u"lbl_bodies_pp")
|
||||||
|
|
||||||
|
self.formLayout_3.setWidget(0, QFormLayout.LabelRole, self.lbl_bodies_pp)
|
||||||
|
|
||||||
|
self.gr_bodies_pp = QGridLayout()
|
||||||
|
self.gr_bodies_pp.setObjectName(u"gr_bodies_pp")
|
||||||
|
self.btn_bodies_browse_pp = QPushButton(self.tab_preprocess)
|
||||||
|
self.btn_bodies_browse_pp.setObjectName(u"btn_bodies_browse_pp")
|
||||||
|
|
||||||
|
self.gr_bodies_pp.addWidget(self.btn_bodies_browse_pp, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
self.inp_bodies_pp = QComboBox(self.tab_preprocess)
|
||||||
|
self.inp_bodies_pp.setObjectName(u"inp_bodies_pp")
|
||||||
|
sizePolicy2.setHeightForWidth(self.inp_bodies_pp.sizePolicy().hasHeightForWidth())
|
||||||
|
self.inp_bodies_pp.setSizePolicy(sizePolicy2)
|
||||||
|
self.inp_bodies_pp.setEditable(False)
|
||||||
|
self.inp_bodies_pp.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.gr_bodies_pp.addWidget(self.inp_bodies_pp, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_3.setLayout(0, QFormLayout.FieldRole, self.gr_bodies_pp)
|
||||||
|
|
||||||
|
self.lbl_systems_pp = QLabel(self.tab_preprocess)
|
||||||
|
self.lbl_systems_pp.setObjectName(u"lbl_systems_pp")
|
||||||
|
|
||||||
|
self.formLayout_3.setWidget(1, QFormLayout.LabelRole, self.lbl_systems_pp)
|
||||||
|
|
||||||
|
self.gr_systems_pp = QGridLayout()
|
||||||
|
self.gr_systems_pp.setObjectName(u"gr_systems_pp")
|
||||||
|
self.btn_systems_browse_pp = QPushButton(self.tab_preprocess)
|
||||||
|
self.btn_systems_browse_pp.setObjectName(u"btn_systems_browse_pp")
|
||||||
|
|
||||||
|
self.gr_systems_pp.addWidget(self.btn_systems_browse_pp, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
self.inp_systems_pp = QComboBox(self.tab_preprocess)
|
||||||
|
self.inp_systems_pp.setObjectName(u"inp_systems_pp")
|
||||||
|
sizePolicy2.setHeightForWidth(self.inp_systems_pp.sizePolicy().hasHeightForWidth())
|
||||||
|
self.inp_systems_pp.setSizePolicy(sizePolicy2)
|
||||||
|
self.inp_systems_pp.setEditable(False)
|
||||||
|
self.inp_systems_pp.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.gr_systems_pp.addWidget(self.inp_systems_pp, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_3.setLayout(1, QFormLayout.FieldRole, self.gr_systems_pp)
|
||||||
|
|
||||||
|
self.lbl_out_pp = QLabel(self.tab_preprocess)
|
||||||
|
self.lbl_out_pp.setObjectName(u"lbl_out_pp")
|
||||||
|
|
||||||
|
self.formLayout_3.setWidget(2, QFormLayout.LabelRole, self.lbl_out_pp)
|
||||||
|
|
||||||
|
self.gr_out_grid_pp = QGridLayout()
|
||||||
|
self.gr_out_grid_pp.setObjectName(u"gr_out_grid_pp")
|
||||||
|
self.btn_out_browse_pp = QPushButton(self.tab_preprocess)
|
||||||
|
self.btn_out_browse_pp.setObjectName(u"btn_out_browse_pp")
|
||||||
|
sizePolicy3 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||||
|
sizePolicy3.setHorizontalStretch(0)
|
||||||
|
sizePolicy3.setVerticalStretch(0)
|
||||||
|
sizePolicy3.setHeightForWidth(self.btn_out_browse_pp.sizePolicy().hasHeightForWidth())
|
||||||
|
self.btn_out_browse_pp.setSizePolicy(sizePolicy3)
|
||||||
|
|
||||||
|
self.gr_out_grid_pp.addWidget(self.btn_out_browse_pp, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
self.inp_out_pp = QComboBox(self.tab_preprocess)
|
||||||
|
self.inp_out_pp.setObjectName(u"inp_out_pp")
|
||||||
|
sizePolicy2.setHeightForWidth(self.inp_out_pp.sizePolicy().hasHeightForWidth())
|
||||||
|
self.inp_out_pp.setSizePolicy(sizePolicy2)
|
||||||
|
self.inp_out_pp.setEditable(False)
|
||||||
|
self.inp_out_pp.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
|
||||||
|
self.gr_out_grid_pp.addWidget(self.inp_out_pp, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_3.setLayout(2, QFormLayout.FieldRole, self.gr_out_grid_pp)
|
||||||
|
|
||||||
|
self.btn_preprocess = QPushButton(self.tab_preprocess)
|
||||||
|
self.btn_preprocess.setObjectName(u"btn_preprocess")
|
||||||
|
|
||||||
|
self.formLayout_3.setWidget(3, QFormLayout.LabelRole, self.btn_preprocess)
|
||||||
|
|
||||||
|
self.tabs.addTab(self.tab_preprocess, "")
|
||||||
|
self.tab_route = QWidget()
|
||||||
|
self.tab_route.setObjectName(u"tab_route")
|
||||||
|
self.formLayout_2 = QFormLayout(self.tab_route)
|
||||||
|
self.formLayout_2.setObjectName(u"formLayout_2")
|
||||||
|
self.lbl_sys_lst = QLabel(self.tab_route)
|
||||||
|
self.lbl_sys_lst.setObjectName(u"lbl_sys_lst")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.lbl_sys_lst)
|
||||||
|
|
||||||
|
self.gr_sys = QGridLayout()
|
||||||
|
self.gr_sys.setObjectName(u"gr_sys")
|
||||||
|
self.btn_sys_lst_browse = QPushButton(self.tab_route)
|
||||||
|
self.btn_sys_lst_browse.setObjectName(u"btn_sys_lst_browse")
|
||||||
|
|
||||||
|
self.gr_sys.addWidget(self.btn_sys_lst_browse, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
self.inp_sys_lst = QComboBox(self.tab_route)
|
||||||
|
self.inp_sys_lst.setObjectName(u"inp_sys_lst")
|
||||||
|
sizePolicy2.setHeightForWidth(self.inp_sys_lst.sizePolicy().hasHeightForWidth())
|
||||||
|
self.inp_sys_lst.setSizePolicy(sizePolicy2)
|
||||||
|
self.inp_sys_lst.setEditable(False)
|
||||||
|
self.inp_sys_lst.setInsertPolicy(QComboBox.InsertAtTop)
|
||||||
|
self.inp_sys_lst.setFrame(True)
|
||||||
|
self.inp_sys_lst.setModelColumn(0)
|
||||||
|
|
||||||
|
self.gr_sys.addWidget(self.inp_sys_lst, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_2.setLayout(0, QFormLayout.FieldRole, self.gr_sys)
|
||||||
|
|
||||||
|
self.btn_add = QPushButton(self.tab_route)
|
||||||
|
self.btn_add.setObjectName(u"btn_add")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.btn_add)
|
||||||
|
|
||||||
|
self.inp_sys = QLineEdit(self.tab_route)
|
||||||
|
self.inp_sys.setObjectName(u"inp_sys")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(1, QFormLayout.FieldRole, self.inp_sys)
|
||||||
|
|
||||||
|
self.btn_rm = QPushButton(self.tab_route)
|
||||||
|
self.btn_rm.setObjectName(u"btn_rm")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(3, QFormLayout.LabelRole, self.btn_rm)
|
||||||
|
|
||||||
|
self.gr_mode = QGridLayout()
|
||||||
|
self.gr_mode.setObjectName(u"gr_mode")
|
||||||
|
self.rd_comp = QRadioButton(self.tab_route)
|
||||||
|
self.rd_comp.setObjectName(u"rd_comp")
|
||||||
|
self.rd_comp.setChecked(True)
|
||||||
|
|
||||||
|
self.gr_mode.addWidget(self.rd_comp, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
self.rd_precomp = QRadioButton(self.tab_route)
|
||||||
|
self.rd_precomp.setObjectName(u"rd_precomp")
|
||||||
|
|
||||||
|
self.gr_mode.addWidget(self.rd_precomp, 0, 2, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_2.setLayout(3, QFormLayout.FieldRole, self.gr_mode)
|
||||||
|
|
||||||
|
self.chk_permute = QCheckBox(self.tab_route)
|
||||||
|
self.chk_permute.setObjectName(u"chk_permute")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(4, QFormLayout.LabelRole, self.chk_permute)
|
||||||
|
|
||||||
|
self.gridLayout_4 = QGridLayout()
|
||||||
|
self.gridLayout_4.setObjectName(u"gridLayout_4")
|
||||||
|
self.chk_permute_keep_last = QCheckBox(self.tab_route)
|
||||||
|
self.chk_permute_keep_last.setObjectName(u"chk_permute_keep_last")
|
||||||
|
|
||||||
|
self.gridLayout_4.addWidget(self.chk_permute_keep_last, 0, 3, 1, 1)
|
||||||
|
|
||||||
|
self.chk_permute_keep_first = QCheckBox(self.tab_route)
|
||||||
|
self.chk_permute_keep_first.setObjectName(u"chk_permute_keep_first")
|
||||||
|
sizePolicy3.setHeightForWidth(self.chk_permute_keep_first.sizePolicy().hasHeightForWidth())
|
||||||
|
self.chk_permute_keep_first.setSizePolicy(sizePolicy3)
|
||||||
|
self.chk_permute_keep_first.setTristate(False)
|
||||||
|
|
||||||
|
self.gridLayout_4.addWidget(self.chk_permute_keep_first, 0, 2, 1, 1)
|
||||||
|
|
||||||
|
self.lbl_keep = QLabel(self.tab_route)
|
||||||
|
self.lbl_keep.setObjectName(u"lbl_keep")
|
||||||
|
sizePolicy1.setHeightForWidth(self.lbl_keep.sizePolicy().hasHeightForWidth())
|
||||||
|
self.lbl_keep.setSizePolicy(sizePolicy1)
|
||||||
|
|
||||||
|
self.gridLayout_4.addWidget(self.lbl_keep, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_2.setLayout(4, QFormLayout.FieldRole, self.gridLayout_4)
|
||||||
|
|
||||||
|
self.lst_sys = QTreeWidget(self.tab_route)
|
||||||
|
__qtreewidgetitem = QTreeWidgetItem()
|
||||||
|
__qtreewidgetitem.setText(0, u"Name");
|
||||||
|
self.lst_sys.setHeaderItem(__qtreewidgetitem)
|
||||||
|
self.lst_sys.setObjectName(u"lst_sys")
|
||||||
|
sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
sizePolicy4.setHorizontalStretch(0)
|
||||||
|
sizePolicy4.setVerticalStretch(0)
|
||||||
|
sizePolicy4.setHeightForWidth(self.lst_sys.sizePolicy().hasHeightForWidth())
|
||||||
|
self.lst_sys.setSizePolicy(sizePolicy4)
|
||||||
|
self.lst_sys.setMinimumSize(QSize(0, 0))
|
||||||
|
self.lst_sys.setDragEnabled(True)
|
||||||
|
self.lst_sys.setDragDropOverwriteMode(False)
|
||||||
|
self.lst_sys.setDragDropMode(QAbstractItemView.InternalMove)
|
||||||
|
self.lst_sys.setDefaultDropAction(Qt.MoveAction)
|
||||||
|
self.lst_sys.setAlternatingRowColors(True)
|
||||||
|
self.lst_sys.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||||
|
self.lst_sys.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
self.lst_sys.setHeaderHidden(True)
|
||||||
|
self.lst_sys.header().setVisible(False)
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(7, QFormLayout.SpanningRole, self.lst_sys)
|
||||||
|
|
||||||
|
self.lbl_range = QLabel(self.tab_route)
|
||||||
|
self.lbl_range.setObjectName(u"lbl_range")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(8, QFormLayout.LabelRole, self.lbl_range)
|
||||||
|
|
||||||
|
self.sb_range = QDoubleSpinBox(self.tab_route)
|
||||||
|
self.sb_range.setObjectName(u"sb_range")
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(8, QFormLayout.FieldRole, self.sb_range)
|
||||||
|
|
||||||
|
self.gr_opts = QGridLayout()
|
||||||
|
self.gr_opts.setObjectName(u"gr_opts")
|
||||||
|
self.cmb_mode = QComboBox(self.tab_route)
|
||||||
|
self.cmb_mode.addItem("")
|
||||||
|
self.cmb_mode.addItem("")
|
||||||
|
self.cmb_mode.addItem("")
|
||||||
|
self.cmb_mode.setObjectName(u"cmb_mode")
|
||||||
|
|
||||||
|
self.gr_opts.addWidget(self.cmb_mode, 0, 2, 1, 1)
|
||||||
|
|
||||||
|
self.lbl_greedyness = QLabel(self.tab_route)
|
||||||
|
self.lbl_greedyness.setObjectName(u"lbl_greedyness")
|
||||||
|
self.lbl_greedyness.setEnabled(True)
|
||||||
|
|
||||||
|
self.gr_opts.addWidget(self.lbl_greedyness, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
self.chk_primary = QCheckBox(self.tab_route)
|
||||||
|
self.chk_primary.setObjectName(u"chk_primary")
|
||||||
|
|
||||||
|
self.gr_opts.addWidget(self.chk_primary, 0, 3, 1, 1)
|
||||||
|
|
||||||
|
self.sld_greedyness = QSlider(self.tab_route)
|
||||||
|
self.sld_greedyness.setObjectName(u"sld_greedyness")
|
||||||
|
self.sld_greedyness.setMaximum(100)
|
||||||
|
self.sld_greedyness.setPageStep(10)
|
||||||
|
self.sld_greedyness.setValue(50)
|
||||||
|
self.sld_greedyness.setOrientation(Qt.Horizontal)
|
||||||
|
self.sld_greedyness.setTickPosition(QSlider.TicksBelow)
|
||||||
|
self.sld_greedyness.setTickInterval(10)
|
||||||
|
|
||||||
|
self.gr_opts.addWidget(self.sld_greedyness, 1, 2, 1, 2)
|
||||||
|
|
||||||
|
self.lbl_mode = QLabel(self.tab_route)
|
||||||
|
self.lbl_mode.setObjectName(u"lbl_mode")
|
||||||
|
|
||||||
|
self.gr_opts.addWidget(self.lbl_mode, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.formLayout_2.setLayout(9, QFormLayout.SpanningRole, self.gr_opts)
|
||||||
|
|
||||||
|
self.btn_go = QPushButton(self.tab_route)
|
||||||
|
self.btn_go.setObjectName(u"btn_go")
|
||||||
|
self.btn_go.setFlat(False)
|
||||||
|
|
||||||
|
self.formLayout_2.setWidget(10, QFormLayout.LabelRole, self.btn_go)
|
||||||
|
|
||||||
|
self.tabs.addTab(self.tab_route, "")
|
||||||
|
self.tab_log = QWidget()
|
||||||
|
self.tab_log.setObjectName(u"tab_log")
|
||||||
|
self.gridLayout_3 = QGridLayout(self.tab_log)
|
||||||
|
self.gridLayout_3.setObjectName(u"gridLayout_3")
|
||||||
|
self.txt_log = QTextEdit(self.tab_log)
|
||||||
|
self.txt_log.setObjectName(u"txt_log")
|
||||||
|
self.txt_log.setEnabled(True)
|
||||||
|
self.txt_log.setFrameShadow(QFrame.Sunken)
|
||||||
|
self.txt_log.setLineWidth(1)
|
||||||
|
self.txt_log.setReadOnly(True)
|
||||||
|
self.txt_log.setAcceptRichText(False)
|
||||||
|
|
||||||
|
self.gridLayout_3.addWidget(self.txt_log, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
self.tabs.addTab(self.tab_log, "")
|
||||||
|
|
||||||
|
self.verticalLayout.addWidget(self.tabs)
|
||||||
|
|
||||||
|
ED_LRR.setCentralWidget(self.centralwidget)
|
||||||
|
self.menu = QMenuBar(ED_LRR)
|
||||||
|
self.menu.setObjectName(u"menu")
|
||||||
|
self.menu.setGeometry(QRect(0, 0, 577, 21))
|
||||||
|
self.menu_file = QMenu(self.menu)
|
||||||
|
self.menu_file.setObjectName(u"menu_file")
|
||||||
|
self.menuWindow = QMenu(self.menu)
|
||||||
|
self.menuWindow.setObjectName(u"menuWindow")
|
||||||
|
self.menuStyle = QMenu(self.menuWindow)
|
||||||
|
self.menuStyle.setObjectName(u"menuStyle")
|
||||||
|
ED_LRR.setMenuBar(self.menu)
|
||||||
|
self.bar_status = QStatusBar(ED_LRR)
|
||||||
|
self.bar_status.setObjectName(u"bar_status")
|
||||||
|
ED_LRR.setStatusBar(self.bar_status)
|
||||||
|
QWidget.setTabOrder(self.rd_comp, self.cmb_mode)
|
||||||
|
QWidget.setTabOrder(self.cmb_mode, self.chk_primary)
|
||||||
|
QWidget.setTabOrder(self.chk_primary, self.sld_greedyness)
|
||||||
|
QWidget.setTabOrder(self.sld_greedyness, self.rd_precomp)
|
||||||
|
|
||||||
|
self.menu.addAction(self.menu_file.menuAction())
|
||||||
|
self.menu.addAction(self.menuWindow.menuAction())
|
||||||
|
self.menu_file.addAction(self.menu_act_quit)
|
||||||
|
self.menuWindow.addAction(self.menuStyle.menuAction())
|
||||||
|
|
||||||
|
self.retranslateUi(ED_LRR)
|
||||||
|
self.menu_act_quit.triggered.connect(ED_LRR.close)
|
||||||
|
|
||||||
|
self.tabs.setCurrentIndex(2)
|
||||||
|
|
||||||
|
|
||||||
|
QMetaObject.connectSlotsByName(ED_LRR)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
|
def retranslateUi(self, ED_LRR):
|
||||||
|
ED_LRR.setWindowTitle(QCoreApplication.translate("ED_LRR", u"Elite: Dangerous Long Range Route Plotter", None))
|
||||||
|
self.menu_act_quit.setText(QCoreApplication.translate("ED_LRR", u"Quit", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.menu_act_quit.setShortcut(QCoreApplication.translate("ED_LRR", u"Ctrl+Q", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.actionA.setText(QCoreApplication.translate("ED_LRR", u"A", None))
|
||||||
|
self.actionB.setText(QCoreApplication.translate("ED_LRR", u"B", None))
|
||||||
|
self.lbl_bodies_dl.setText(QCoreApplication.translate("ED_LRR", u"bodies.json", None))
|
||||||
|
self.lbl_systems_dl.setText(QCoreApplication.translate("ED_LRR", u"systemsWithCoordinates.json", None))
|
||||||
|
self.inp_bodies_dl.setCurrentText(QCoreApplication.translate("ED_LRR", u"https://www.edsm.net/dump/bodies.json", None))
|
||||||
|
self.inp_systems_dl.setCurrentText(QCoreApplication.translate("ED_LRR", u"https://www.edsm.net/dump/systemsWithCoordinates.json", None))
|
||||||
|
self.btn_bodies_dest_browse_dl.setText(QCoreApplication.translate("ED_LRR", u"...", None))
|
||||||
|
self.btn_systems_dest_browse_dl.setText(QCoreApplication.translate("ED_LRR", u"...", None))
|
||||||
|
self.btn_download.setText(QCoreApplication.translate("ED_LRR", u"Download", None))
|
||||||
|
self.label.setText(QCoreApplication.translate("ED_LRR", u"Download path", None))
|
||||||
|
self.label_2.setText(QCoreApplication.translate("ED_LRR", u"Download path", None))
|
||||||
|
self.tabs.setTabText(self.tabs.indexOf(self.tab_download), QCoreApplication.translate("ED_LRR", u"Download", None))
|
||||||
|
self.lbl_bodies_pp.setText(QCoreApplication.translate("ED_LRR", u"bodies.json", None))
|
||||||
|
self.btn_bodies_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None))
|
||||||
|
self.lbl_systems_pp.setText(QCoreApplication.translate("ED_LRR", u"systemsWithCoordinates.json", None))
|
||||||
|
self.btn_systems_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None))
|
||||||
|
self.lbl_out_pp.setText(QCoreApplication.translate("ED_LRR", u"Output", None))
|
||||||
|
self.btn_out_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None))
|
||||||
|
self.btn_preprocess.setText(QCoreApplication.translate("ED_LRR", u"Preprocess", None))
|
||||||
|
self.tabs.setTabText(self.tabs.indexOf(self.tab_preprocess), QCoreApplication.translate("ED_LRR", u"Preprocess", None))
|
||||||
|
self.lbl_sys_lst.setText(QCoreApplication.translate("ED_LRR", u"System List", None))
|
||||||
|
self.btn_sys_lst_browse.setText(QCoreApplication.translate("ED_LRR", u"...", None))
|
||||||
|
self.btn_add.setText(QCoreApplication.translate("ED_LRR", u"Add", None))
|
||||||
|
self.inp_sys.setPlaceholderText(QCoreApplication.translate("ED_LRR", u"System Name", None))
|
||||||
|
self.btn_rm.setText(QCoreApplication.translate("ED_LRR", u"Remove", None))
|
||||||
|
self.rd_comp.setText(QCoreApplication.translate("ED_LRR", u"Compute Route", None))
|
||||||
|
self.rd_precomp.setText(QCoreApplication.translate("ED_LRR", u"Precompute Graph", None))
|
||||||
|
self.chk_permute.setText(QCoreApplication.translate("ED_LRR", u"Permute", None))
|
||||||
|
self.chk_permute_keep_last.setText(QCoreApplication.translate("ED_LRR", u"Last", None))
|
||||||
|
self.chk_permute_keep_first.setText(QCoreApplication.translate("ED_LRR", u"First", None))
|
||||||
|
self.lbl_keep.setText(QCoreApplication.translate("ED_LRR", u"Keep Endpoints:", None))
|
||||||
|
___qtreewidgetitem = self.lst_sys.headerItem()
|
||||||
|
___qtreewidgetitem.setText(1, QCoreApplication.translate("ED_LRR", u"Type", None));
|
||||||
|
self.lbl_range.setText(QCoreApplication.translate("ED_LRR", u"Jump Range (Ly)", None))
|
||||||
|
self.cmb_mode.setItemText(0, QCoreApplication.translate("ED_LRR", u"Breadth-First Search", None))
|
||||||
|
self.cmb_mode.setItemText(1, QCoreApplication.translate("ED_LRR", u"Greedy-Search", None))
|
||||||
|
self.cmb_mode.setItemText(2, QCoreApplication.translate("ED_LRR", u"A*-Search", None))
|
||||||
|
|
||||||
|
self.cmb_mode.setCurrentText(QCoreApplication.translate("ED_LRR", u"Breadth-First Search", None))
|
||||||
|
self.lbl_greedyness.setText(QCoreApplication.translate("ED_LRR", u"Greedyness Factor", None))
|
||||||
|
self.chk_primary.setText(QCoreApplication.translate("ED_LRR", u"Primary Stars Only", None))
|
||||||
|
self.lbl_mode.setText(QCoreApplication.translate("ED_LRR", u"Mode", None))
|
||||||
|
self.btn_go.setText(QCoreApplication.translate("ED_LRR", u"GO!", None))
|
||||||
|
self.tabs.setTabText(self.tabs.indexOf(self.tab_route), QCoreApplication.translate("ED_LRR", u"Route", None))
|
||||||
|
self.tabs.setTabText(self.tabs.indexOf(self.tab_log), QCoreApplication.translate("ED_LRR", u"Log", None))
|
||||||
|
self.menu_file.setTitle(QCoreApplication.translate("ED_LRR", u"File", None))
|
||||||
|
self.menuWindow.setTitle(QCoreApplication.translate("ED_LRR", u"Window", None))
|
||||||
|
self.menuStyle.setTitle(QCoreApplication.translate("ED_LRR", u"Style", None))
|
||||||
|
# retranslateUi
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Form generated from reading UI file 'widget_route.ui'
|
||||||
|
##
|
||||||
|
## Created by: Qt User Interface Compiler version 5.15.2
|
||||||
|
##
|
||||||
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
|
||||||
|
|
||||||
|
class Ui_diag_route(object):
|
||||||
|
def setupUi(self, diag_route):
|
||||||
|
if not diag_route.objectName():
|
||||||
|
diag_route.setObjectName(u"diag_route")
|
||||||
|
diag_route.setWindowModality(Qt.WindowModal)
|
||||||
|
diag_route.resize(430, 300)
|
||||||
|
self.layout_main = QGridLayout(diag_route)
|
||||||
|
self.layout_main.setObjectName(u"layout_main")
|
||||||
|
self.layout_top = QGridLayout()
|
||||||
|
self.layout_top.setObjectName(u"layout_top")
|
||||||
|
self.lst_route = QTreeWidget(diag_route)
|
||||||
|
self.lst_route.setObjectName(u"lst_route")
|
||||||
|
self.lst_route.setProperty("showDropIndicator", False)
|
||||||
|
self.lst_route.setDefaultDropAction(Qt.IgnoreAction)
|
||||||
|
self.lst_route.setAlternatingRowColors(True)
|
||||||
|
self.lst_route.setSelectionMode(QAbstractItemView.NoSelection)
|
||||||
|
self.lst_route.setVerticalScrollMode(QAbstractItemView.ScrollPerItem)
|
||||||
|
self.lst_route.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.lst_route.setItemsExpandable(False)
|
||||||
|
self.lst_route.setAllColumnsShowFocus(False)
|
||||||
|
|
||||||
|
self.layout_top.addWidget(self.lst_route, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.layout_main.addLayout(self.layout_top, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
self.layout_bottom = QGridLayout()
|
||||||
|
self.layout_bottom.setObjectName(u"layout_bottom")
|
||||||
|
self.chk_copy = QCheckBox(diag_route)
|
||||||
|
self.chk_copy.setObjectName(u"chk_copy")
|
||||||
|
|
||||||
|
self.layout_bottom.addWidget(self.chk_copy, 1, 0, 1, 1)
|
||||||
|
|
||||||
|
self.btn_close = QPushButton(diag_route)
|
||||||
|
self.btn_close.setObjectName(u"btn_close")
|
||||||
|
|
||||||
|
self.layout_bottom.addWidget(self.btn_close, 1, 2, 1, 1)
|
||||||
|
|
||||||
|
self.btn_export = QPushButton(diag_route)
|
||||||
|
self.btn_export.setObjectName(u"btn_export")
|
||||||
|
|
||||||
|
self.layout_bottom.addWidget(self.btn_export, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.layout_main.addLayout(self.layout_bottom, 1, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.retranslateUi(diag_route)
|
||||||
|
|
||||||
|
QMetaObject.connectSlotsByName(diag_route)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
|
def retranslateUi(self, diag_route):
|
||||||
|
diag_route.setWindowTitle(QCoreApplication.translate("diag_route", u"Route", None))
|
||||||
|
___qtreewidgetitem = self.lst_route.headerItem()
|
||||||
|
___qtreewidgetitem.setText(3, QCoreApplication.translate("diag_route", u"Distance (Ls)", None));
|
||||||
|
___qtreewidgetitem.setText(2, QCoreApplication.translate("diag_route", u"Body", None));
|
||||||
|
___qtreewidgetitem.setText(1, QCoreApplication.translate("diag_route", u"System", None));
|
||||||
|
___qtreewidgetitem.setText(0, QCoreApplication.translate("diag_route", u"Num", None));
|
||||||
|
self.chk_copy.setText(QCoreApplication.translate("diag_route", u"Auto-copy next hop to clipboard", None))
|
||||||
|
self.btn_close.setText(QCoreApplication.translate("diag_route", u"Close", None))
|
||||||
|
self.btn_export.setText(QCoreApplication.translate("diag_route", u"Export", None))
|
||||||
|
# retranslateUi
|
||||||
|
|
43
README.md
43
README.md
|
@ -108,46 +108,3 @@ $$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \
|
||||||
$$dist = \frac{- B_{g} \cdot m_{fuel} - B_{g} \cdot m_{ship} + boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
|
$$dist = \frac{- B_{g} \cdot m_{fuel} - B_{g} \cdot m_{ship} + boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
|
||||||
|
|
||||||
$$e_{fuel} = \frac{l \cdot \left(\frac{10.0^{- \frac{3.0}{p}} \cdot \left(B_{g} + dist\right) \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}}{\min\left(f_{max}, m_{fuel}\right)}$$
|
$$e_{fuel} = \frac{l \cdot \left(\frac{10.0^{- \frac{3.0}{p}} \cdot \left(B_{g} + dist\right) \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}}{\min\left(f_{max}, m_{fuel}\right)}$$
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
|
|
||||||
- Implement Neutron Mode
|
|
||||||
- Filter for neutron stars, plot route, then plot "fine" router between waypoints
|
|
||||||
- What Jump-Range to use for neutron route? `max_range*4`?
|
|
||||||
- Implement Bidir BFS
|
|
||||||
- Optimized All-Pairs BFS for graph precomputation
|
|
||||||
- Take fuel consumption into account (WIP)
|
|
||||||
- Guardian Booster support (Done?)
|
|
||||||
- Economic routing
|
|
||||||
- Custom weights and filtering for routing
|
|
||||||
|
|
||||||
## GUI
|
|
||||||
|
|
||||||
- Implement estimate time to completion display for route computation and preprocessing (Done?)
|
|
||||||
- Export route as:
|
|
||||||
- JSON
|
|
||||||
- HTML (WIP)
|
|
||||||
- CSV
|
|
||||||
- SVG
|
|
||||||
|
|
||||||
## Installer
|
|
||||||
|
|
||||||
- Update PATH from installer
|
|
||||||
|
|
||||||
## Preprocessing
|
|
||||||
|
|
||||||
- Build index over `systemsWithCoordinates.json` instead of loading it into RAM (reuse modified `LineCache` from `router.rs`)
|
|
||||||
- Finish `galaxy.jsonl` preprocessor
|
|
||||||
- Implement Python interface to preprocessor
|
|
||||||
|
|
||||||
## Misc
|
|
||||||
|
|
||||||
- Luigi based Task queue for distributed routing
|
|
||||||
- Full route tree computation
|
|
||||||
- overlap elimination
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- expose multiroute TSP optimizer
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
- beam stack search, queue for each depth level, ordered by distance to goal node (min first)
|
||||||
|
- GraphSearch trait
|
||||||
|
- Implement Neutron Mode
|
||||||
|
- Filter for neutron stars, plot coarse route, then plot exact router between waypoints
|
||||||
|
- What Jump-Range to use for neutron route? `max_range*4`?
|
||||||
|
- furthest inside jump range, otherwise closest?
|
||||||
|
- Implement Bidirectional BFS
|
||||||
|
- Optimized All-Pairs BFS for graph precomputation
|
||||||
|
- Take fuel consumption into account (WIP) partially implemented
|
||||||
|
- Guardian Booster support (Done?)
|
||||||
|
- Economic routing (minimal fuel, dijkstra)
|
||||||
|
- implemented, needs to be properly exposed
|
||||||
|
- Custom weights and filtering for routing [evalexpr](https://docs.rs/evalexpr/)
|
||||||
|
- pathfinding weighted by $(goal-pos)\cdot(goal-start)$
|
||||||
|
- use vecmat crate for vector distance etc
|
||||||
|
|
||||||
|
|
||||||
|
## GUI
|
||||||
|
|
||||||
|
- Imgui?
|
||||||
|
- Implement estimate time to completion display for route computation and preprocessing (Done?)
|
||||||
|
- Export route as:
|
||||||
|
- JSON
|
||||||
|
- HTML (WIP)
|
||||||
|
- CSV
|
||||||
|
- SVG
|
||||||
|
|
||||||
|
## Installer
|
||||||
|
|
||||||
|
- Update PATH from installer
|
||||||
|
|
||||||
|
## Preprocessing
|
||||||
|
|
||||||
|
- Build index over `systemsWithCoordinates.json` instead of loading it into RAM (reuse modified `LineCache` from `router.rs`)
|
||||||
|
- Finish `galaxy.jsonl` preprocessor (Done?)
|
||||||
|
- Implement Python interface to preprocessor
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
- Luigi/Celery/Dask based Task queue for distributed routing
|
|
@ -0,0 +1,6 @@
|
||||||
|
def pruntLayer(l,w):
|
||||||
|
Keep=sorted(Open[l])[:w] # best `w` node from `Open[l]`
|
||||||
|
Prune = [n for n in Open[l] if n not in Keep]
|
||||||
|
beam_stack.top().f_max=min([f(n) for n in Prune])
|
||||||
|
for n in Prune:
|
||||||
|
Open[l].remove(n)
|
|
@ -0,0 +1,84 @@
|
||||||
|
from tqdm import tqdm
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import statistics
|
||||||
|
|
||||||
|
def stats(values):
|
||||||
|
ret={
|
||||||
|
"max":max(values),
|
||||||
|
"min":min(values),
|
||||||
|
"mean":statistics.mean(values),
|
||||||
|
"range":abs(max(values)-min(values)),
|
||||||
|
"sum":sum(values)
|
||||||
|
}
|
||||||
|
if len(values)>1:
|
||||||
|
ret["stdev"]=statistics.stdev(values)
|
||||||
|
else:
|
||||||
|
ret["stdev"]=0
|
||||||
|
return ret
|
||||||
|
|
||||||
|
last_seen=float('inf')
|
||||||
|
pbar=None
|
||||||
|
def callback(state):
|
||||||
|
try:
|
||||||
|
global pbar,last_seen
|
||||||
|
if state['n_seen']<last_seen:
|
||||||
|
if pbar:
|
||||||
|
pbar.close()
|
||||||
|
pbar=tqdm(total=state['d_total'],unit="Ly",unit_scale=True)
|
||||||
|
last_seen=state['n_seen']
|
||||||
|
pbar.n=round(state['d_total']-state['d_rem'],2)
|
||||||
|
pbar.set_description("[J:{depth} | Q:{queue_size} | D:{d_rem:.2f} Ly | S:{n_seen} ({prc_seen:.2f}%)] {system}".format(**state))
|
||||||
|
pbar.update(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
if not 'callback' in vars():
|
||||||
|
callback=None
|
||||||
|
import _ed_lrr
|
||||||
|
callback=None
|
||||||
|
r=_ed_lrr.PyRouter(callback)
|
||||||
|
r.load("stars.csv")
|
||||||
|
res=[]
|
||||||
|
done=set()
|
||||||
|
if os.path.isfile("res.json"):
|
||||||
|
with open("res.json","r") as fh:
|
||||||
|
try:
|
||||||
|
res=json.load(fh)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
for v in res:
|
||||||
|
done.add((v[0],v[1]))
|
||||||
|
systems=r.resolve_systems("Sol","Colonia")
|
||||||
|
min_dt_total=60*10
|
||||||
|
print("Warming up...")
|
||||||
|
r.route([systems['Sol'],systems['Sol']],48.0,0.0,0.0,0) # warmup
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
for g in [0.0, 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]:
|
||||||
|
for w in [0.0, 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]:
|
||||||
|
if (g,w) in done:
|
||||||
|
continue
|
||||||
|
dt=[]
|
||||||
|
lr=[]
|
||||||
|
l=0
|
||||||
|
while sum(dt)<min_dt_total:
|
||||||
|
l+=1
|
||||||
|
print({'g':g,'w':w,'l':l})
|
||||||
|
t=time.time()
|
||||||
|
route=r.route([systems['Sol'],systems['Colonia']],48.0,g,w,0)
|
||||||
|
dt.append(time.time()-t)
|
||||||
|
lr.append(len(route))
|
||||||
|
print(stats(dt))
|
||||||
|
dt=sum(dt)/len(dt)
|
||||||
|
res.append((g,w,lr,dt))
|
||||||
|
with open("res.json","w") as of:
|
||||||
|
json.dump(res,of)
|
||||||
|
|
||||||
|
# for n in range(21):
|
||||||
|
# grid=r.get_grid(1<<n)
|
||||||
|
# max_v=max(grid.items(),key=lambda v:v[1])
|
||||||
|
# mean_v=sum(grid.values())/len(grid)
|
||||||
|
# print(1<<n,len(grid),mean_v,max_v)
|
||||||
|
|
|
@ -3,9 +3,8 @@ from celery import Celery
|
||||||
import _ed_lrr
|
import _ed_lrr
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ.setdefault("CELERY_CONFIG_MODULE", "celeryconfig")
|
|
||||||
app = Celery("ed_lrr")
|
app = Celery("ed_lrr")
|
||||||
app.config_from_envvar("CELERY_CONFIG_MODULE")
|
app.config_from_object(__import__("celeryconfig"))
|
||||||
|
|
||||||
|
|
||||||
@app.task(bind=True)
|
@app.task(bind=True)
|
|
@ -1,48 +0,0 @@
|
||||||
MD = $(wildcard src/*.md)
|
|
||||||
DOTS = $(wildcard src/*.dot)
|
|
||||||
ASYS = $(wildcard src/*.asy)
|
|
||||||
PYS = $(wildcard src/img_*.py)
|
|
||||||
PDFS = $(MD:src/%.md=out/%.pdf)
|
|
||||||
|
|
||||||
IMG_PDFS = $(ASYS:src/%.asy=img/%.pdf) $(PYS:src/img_%.py=img/%.pdf) $(DOTS:src/%.dot=img/%.pdf)
|
|
||||||
|
|
||||||
IMGS = $(IMG_PDFS)
|
|
||||||
|
|
||||||
TEMPLATE = eisvogel
|
|
||||||
PDF_ENGINE = xelatex
|
|
||||||
PANDOC = pandoc
|
|
||||||
PANDOC_OPTIONS = -F panflute -F pandoc-citeproc --pdf-engine=$(PDF_ENGINE) --template $(TEMPLATE) -N --standalone --listings
|
|
||||||
|
|
||||||
GRAPHVIZ = dot
|
|
||||||
GRAPHVIZ_OPTIONS = -Tpdf
|
|
||||||
|
|
||||||
ASY = asy
|
|
||||||
ASY_OPTIONS = -noV -f pdf
|
|
||||||
|
|
||||||
PYTHON = python
|
|
||||||
PYTHON_OPTIONS =
|
|
||||||
|
|
||||||
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
|
||||||
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))
|
|
||||||
|
|
||||||
.PHONY: clean all default
|
|
||||||
all: $(PDFS)
|
|
||||||
default: all
|
|
||||||
|
|
||||||
out/%.pdf: src/%.md $(IMGS) Makefile
|
|
||||||
$(PANDOC) $(PANDOC_OPTIONS) -o $@ $<
|
|
||||||
|
|
||||||
img/%.pdf: src/%.dot
|
|
||||||
$(GRAPHVIZ) $(GRAPHVIZ_OPTIONS) -o $@ $<
|
|
||||||
|
|
||||||
img/%.pdf: src/img_%.py
|
|
||||||
$(PYTHON) $(PYTHON_OPTIONS) $< $@
|
|
||||||
|
|
||||||
img/%.pdf: src/%.asy
|
|
||||||
$(ASY) $(ASY_OPTIONS) -o $@ $<
|
|
||||||
|
|
||||||
watch:
|
|
||||||
watchexec -w src -w data -w filters -w Makefile make all
|
|
||||||
|
|
||||||
clean:
|
|
||||||
-rm $(PDFS) $(IMGS)
|
|
|
@ -1,158 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import contextlib
|
|
||||||
import csv
|
|
||||||
import datetime
|
|
||||||
import hashlib
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess as SP
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import panflute as pf
|
|
||||||
from dateutil.parser import parse as dateparse
|
|
||||||
from jinja2 import Environment, PackageLoader, Template, select_autoescape
|
|
||||||
from panflute import *
|
|
||||||
|
|
||||||
|
|
||||||
def remove_pound(elem, doc):
|
|
||||||
if type(elem) == Str:
|
|
||||||
return Str(elem.text.lstrip("#"))
|
|
||||||
|
|
||||||
|
|
||||||
def fix_color(elem, doc):
|
|
||||||
if type(elem) == MetaMap:
|
|
||||||
for k in elem.content:
|
|
||||||
if k.endswith("-color"):
|
|
||||||
elem[k] = elem[k].walk(remove_pound)
|
|
||||||
|
|
||||||
|
|
||||||
def update_date(elem, doc):
|
|
||||||
if type(elem) == MetaMap:
|
|
||||||
datefmt = doc.get_metadata("datefmt", "%Y-%m-%d")
|
|
||||||
today = datetime.date.today().strftime(datefmt)
|
|
||||||
date = dateparse(doc.get_metadata("date", today)).date()
|
|
||||||
elem["date"] = MetaInlines(Str(date.strftime(datefmt)))
|
|
||||||
return elem
|
|
||||||
|
|
||||||
|
|
||||||
def csv_table(elem, doc):
|
|
||||||
if type(elem) == Para and len(elem.content) == 1 and type(elem.content[0]) == Image:
|
|
||||||
elem = elem.content[0]
|
|
||||||
ext = os.path.splitext(elem.url)[1][1:]
|
|
||||||
if ext == "csv":
|
|
||||||
caption = elem.content
|
|
||||||
has_header = elem.attributes.get("has-header", "false").lower() == "true"
|
|
||||||
with open(elem.url) as f:
|
|
||||||
reader = csv.reader(f)
|
|
||||||
body = []
|
|
||||||
for row in reader:
|
|
||||||
cells = [TableCell(Plain(Str(x))) for x in row]
|
|
||||||
body.append(TableRow(*cells))
|
|
||||||
header = body.pop(0) if has_header else None
|
|
||||||
ret = Table(*body, header=header, caption=caption)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def code_refs(elem, doc):
|
|
||||||
if type(elem) == Cite:
|
|
||||||
label = elem.content[0]
|
|
||||||
if type(label) == Str:
|
|
||||||
label = label.text
|
|
||||||
filename = re.findall(r"^\[@lst:(.*)\]$", label) or [None]
|
|
||||||
if filename[0] in doc.inc_files:
|
|
||||||
return [
|
|
||||||
RawInline(
|
|
||||||
"\\hyperref[{}]{{{}}}".format(filename[0], filename[0]),
|
|
||||||
format="tex",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def include_code(elem, doc):
|
|
||||||
if type(elem) == CodeBlock:
|
|
||||||
if "include" in elem.attributes:
|
|
||||||
filepath = elem.attributes.pop("include")
|
|
||||||
filename = os.path.split(filepath)[-1]
|
|
||||||
try:
|
|
||||||
elem.text += elem.text + open(filepath, encoding="utf-8").read()
|
|
||||||
elem.attributes["caption"] = filename
|
|
||||||
doc.inc_files.append(filename)
|
|
||||||
except Exception as e:
|
|
||||||
elem.text += "Error: {}".format(e)
|
|
||||||
return [RawBlock("\\label{{{}}}".format(filename), format="tex"), elem]
|
|
||||||
|
|
||||||
|
|
||||||
def py_eval(options, data, element, doc):
|
|
||||||
out_buffer = io.StringIO()
|
|
||||||
with contextlib.redirect_stdout(out_buffer):
|
|
||||||
exec(data, doc.pyenv)
|
|
||||||
out_buffer.seek(0)
|
|
||||||
return convert_text(out_buffer.read())
|
|
||||||
|
|
||||||
|
|
||||||
def jinja_py_filt(doc, file):
|
|
||||||
env = {}
|
|
||||||
code = open(file, encoding="utf-8").read()
|
|
||||||
exec(code, env)
|
|
||||||
return env["main"](doc)
|
|
||||||
|
|
||||||
|
|
||||||
def prepare(doc):
|
|
||||||
doc.inc_files = []
|
|
||||||
doc.env = Environment()
|
|
||||||
doc.pyenv = {}
|
|
||||||
filters = {"py": partial(jinja_py_filt, doc)}
|
|
||||||
doc.env.filters.update(filters)
|
|
||||||
|
|
||||||
|
|
||||||
def process_templates(elem, doc):
|
|
||||||
if type(elem) == CodeBlock:
|
|
||||||
if elem.classes == ["@"]:
|
|
||||||
args = {"meta": doc.get_metadata()}
|
|
||||||
return convert_text(doc.env.from_string(elem.text).render(args))
|
|
||||||
|
|
||||||
|
|
||||||
def yaml_filt(elem, doc):
|
|
||||||
tags = {"eval": py_eval}
|
|
||||||
return yaml_filter(elem, doc, tags=tags, strict_yaml=True)
|
|
||||||
|
|
||||||
|
|
||||||
def checkboxes(elem, doc):
|
|
||||||
if type(elem) in [Para, Plain]:
|
|
||||||
val = re.findall(r"^\[([xX]|\ )\] (.*)$", stringify(elem))
|
|
||||||
if val:
|
|
||||||
val = val[0][0].lower() == "x"
|
|
||||||
else:
|
|
||||||
return elem
|
|
||||||
marker = {
|
|
||||||
True: RawInline("$\\boxtimes$", format="latex"),
|
|
||||||
False: RawInline("$\\square$", format="latex"),
|
|
||||||
}[val]
|
|
||||||
cont = []
|
|
||||||
if val:
|
|
||||||
cont += elem.content[2:]
|
|
||||||
else:
|
|
||||||
cont += elem.content[4:]
|
|
||||||
return Plain(marker, Space, *cont)
|
|
||||||
return elem
|
|
||||||
|
|
||||||
|
|
||||||
def main(doc=None):
|
|
||||||
f = [
|
|
||||||
process_templates,
|
|
||||||
update_date,
|
|
||||||
csv_table,
|
|
||||||
include_code,
|
|
||||||
fix_color,
|
|
||||||
code_refs,
|
|
||||||
yaml_filt,
|
|
||||||
checkboxes,
|
|
||||||
]
|
|
||||||
return run_filters(f, prepare=prepare, doc=doc)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,92 +0,0 @@
|
||||||
---
|
|
||||||
# Metadata
|
|
||||||
title: ED_LRR
|
|
||||||
author:
|
|
||||||
- Daniel Seiller <earthnuker@gmail.com>
|
|
||||||
subtitle: 'Elite Dangerous: Long-Range Router'
|
|
||||||
|
|
||||||
# Formating
|
|
||||||
toc: true
|
|
||||||
lang: en
|
|
||||||
colorlinks: true
|
|
||||||
papersize: a4
|
|
||||||
numbersections: true
|
|
||||||
|
|
||||||
#Panflute options
|
|
||||||
panflute-filters: [multifilter]
|
|
||||||
panflute-path: 'filters'
|
|
||||||
|
|
||||||
#Template options
|
|
||||||
titlepage: true
|
|
||||||
toc-own-page: false
|
|
||||||
---
|
|
||||||
|
|
||||||
\pagebreak
|
|
||||||
|
|
||||||
# Implementation
|
|
||||||
|
|
||||||
## `stars.csv` format
|
|
||||||
|
|
||||||
### Columns
|
|
||||||
|
|
||||||
| Name | Content |
|
|
||||||
| --------- | ------------------------------------------------------------ |
|
|
||||||
| id | unique ID-Number (not equal to id or id64, just a sequential number) |
|
|
||||||
| star_type | Type of Star |
|
|
||||||
| system | Name of System |
|
|
||||||
| body | Name of Star |
|
|
||||||
| mult | Jump Range Multiplier (1.5 for White Dwarfs, 4.0 for Neutron Stars) |
|
|
||||||
| distance | Distance from arrival in Ls |
|
|
||||||
| x,y,z | Position in Galactic Coordinates with Sol at (0,0,0) |
|
|
||||||
|
|
||||||
## `stars.idx` format
|
|
||||||
|
|
||||||
`bincode` serialized data:
|
|
||||||
|
|
||||||
- **[u64]**: List of byte offset for records (entry 0=first recod, entry 1= second record, etc)
|
|
||||||
|
|
||||||
## Routing Algorithms
|
|
||||||
|
|
||||||
### Breadth-First Search (BFS)
|
|
||||||
|
|
||||||
Standard Breadth-First Search, always finds the shortest route
|
|
||||||
|
|
||||||
### A*-Search
|
|
||||||
|
|
||||||
Modified A*-Search with adjustable "greediness". Priority Queue weighted by $\text{number of jumps from start system} + (\text{estimated number of jumps to target system} * \text{greediness})$
|
|
||||||
|
|
||||||
A greediness of 0 is equivalent to BFS and a greediness of $\infty$ is equivalent to Greedy-Search
|
|
||||||
|
|
||||||
### Greedy-Search
|
|
||||||
|
|
||||||
Priority Queue weighted by minimum distance to target, prefers systems with high multiplier (Neutron Stars and White Dwarfs)
|
|
||||||
|
|
||||||
## Optimizations
|
|
||||||
|
|
||||||
### Ellipse elimination
|
|
||||||
|
|
||||||
Only consider systems within an ellipsoid with source and destination as the foci, the width of the ellipsoid is adjustable
|
|
||||||
|
|
||||||
## Routing Graphs
|
|
||||||
|
|
||||||
### File format
|
|
||||||
|
|
||||||
`bincode` serialized data:
|
|
||||||
|
|
||||||
- *bool* **primary**: flag to indicate that graph only includes primary stars
|
|
||||||
- *f32* **range**: jump range for routing graph
|
|
||||||
- *[u8]* **file_hash**: sha3 hash of `stars.csv` from which graph was generated
|
|
||||||
- *String* **path**: path to `stars.csv` from which graph was generated
|
|
||||||
- *FnvHashMap* **graph**: Hashmap mapping systems to the systems from which they can be rached, traversed from destination system backwards to source to reconstruct route
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
<!--
|
|
||||||
TODO: Add screenshots
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Preprocessing Data
|
|
||||||
|
|
||||||
## Plotting a Route
|
|
||||||
|
|
||||||
# [Changelog](https://gitlab.com/Earthnuker/ed_lrr/blob/pyqt_gui/CHANGELOG.md)
|
|
|
@ -1,83 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import heapq
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pylab as PL
|
|
||||||
from scipy.spatial.ckdtree import cKDTree
|
|
||||||
|
|
||||||
exit()
|
|
||||||
|
|
||||||
|
|
||||||
def vec(a, b):
|
|
||||||
return b - a
|
|
||||||
|
|
||||||
|
|
||||||
def bfs(points):
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def in_ellipse(p, f1, f2, r, offset=0):
|
|
||||||
df = ((f1 - f2) ** 2).sum(0) ** 0.5
|
|
||||||
d_f1 = ((p - f1) ** 2).sum(1) ** 0.5
|
|
||||||
d_f2 = ((p - f2) ** 2).sum(1) ** 0.5
|
|
||||||
return (d_f1 + d_f2) < (df * (1 + r))
|
|
||||||
|
|
||||||
|
|
||||||
num_points = 100000
|
|
||||||
|
|
||||||
p_orig = np.random.normal(0, 10, size=(num_points, 2))
|
|
||||||
tree = cKDTree(p_orig)
|
|
||||||
f1 = np.array([0, -30])
|
|
||||||
f2 = -f1 # np.random.normal(0, 20, (3,))
|
|
||||||
# r = 2 ** ((n / cnt) - cnt)
|
|
||||||
|
|
||||||
mask = in_ellipse(p_orig, f1, f2, 0.1)
|
|
||||||
|
|
||||||
p = p_orig[mask]
|
|
||||||
p_orig = p_orig[~mask]
|
|
||||||
|
|
||||||
colors = np.random.random(p.shape[0])
|
|
||||||
fig = PL.gcf()
|
|
||||||
PL.scatter(
|
|
||||||
p_orig[:, 0],
|
|
||||||
p_orig[:, 1],
|
|
||||||
marker=".",
|
|
||||||
s=0.2,
|
|
||||||
edgecolor="None",
|
|
||||||
c=[(0.0, 0.0, 0.0)],
|
|
||||||
alpha=0.75,
|
|
||||||
rasterized=True,
|
|
||||||
)
|
|
||||||
PL.scatter(
|
|
||||||
p[:, 0],
|
|
||||||
p[:, 1],
|
|
||||||
marker="s",
|
|
||||||
s=0.2,
|
|
||||||
edgecolor="None",
|
|
||||||
c=colors,
|
|
||||||
rasterized=True
|
|
||||||
)
|
|
||||||
PL.plot(f1[0], f1[1], "r.", label="Source")
|
|
||||||
PL.plot(f2[0], f2[1], "g.", label="Destination")
|
|
||||||
|
|
||||||
max_v = max(
|
|
||||||
p_orig[:, 0].max(),
|
|
||||||
p_orig[:, 1].max(),
|
|
||||||
f1[0], f1[1],
|
|
||||||
f2[0], f2[1]
|
|
||||||
) + 2
|
|
||||||
|
|
||||||
min_v = min(
|
|
||||||
p_orig[:, 0].min(),
|
|
||||||
p_orig[:, 1].min(),
|
|
||||||
f1[0], f1[1],
|
|
||||||
f2[0], f2[1]
|
|
||||||
) - 2
|
|
||||||
|
|
||||||
|
|
||||||
PL.xlim(min_v, max_v)
|
|
||||||
PL.ylim(min_v, max_v)
|
|
||||||
|
|
||||||
PL.legend()
|
|
||||||
PL.savefig(sys.argv[1], dpi=1200)
|
|
|
@ -0,0 +1 @@
|
||||||
|
book
|
|
@ -4,8 +4,8 @@
|
||||||
"en"
|
"en"
|
||||||
],
|
],
|
||||||
"spellright.documentTypes": [
|
"spellright.documentTypes": [
|
||||||
"markdown",
|
|
||||||
"latex",
|
"latex",
|
||||||
"plaintext"
|
"plaintext",
|
||||||
|
"git-commit"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"todo": [],
|
||||||
|
"in-progress": [],
|
||||||
|
"testing": [],
|
||||||
|
"done": []
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
[book]
|
||||||
|
authors = ["Daniel Seiller"]
|
||||||
|
language = "en"
|
||||||
|
multilingual = false
|
||||||
|
src = "src"
|
||||||
|
title = "Elite: Dangerous Long Range Router"
|
||||||
|
|
||||||
|
[output.html]
|
||||||
|
preferred-dark-theme = "ayu"
|
||||||
|
mathjax-support = true
|
||||||
|
|
||||||
|
[preprocessor.svgbob]
|
||||||
|
text_width = 8.0
|
||||||
|
text_height = 16.0
|
||||||
|
class = "bob"
|
||||||
|
font_family = "arial"
|
||||||
|
font_size = 14.0
|
||||||
|
stroke_width = 2.0
|
||||||
|
# there's using css-variables from theme:
|
||||||
|
stroke_color = "var(--fg)" # see default theme / variables.css
|
||||||
|
background_color = "transparent" # also useful `var(--bg)`
|
||||||
|
# all properties are optional.
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
- [Intro](./intro/_index.md)
|
||||||
|
- [Galactic Travel in Elite: Dangerous](./intro/ed_travel.md)
|
||||||
|
- [FSD Fuel consumption and jump range](./intro/fsd_fuel.md)
|
||||||
|
- [Graph Search algorithms](./intro/graph_algos.md)
|
||||||
|
|
||||||
|
- [Elite Dangerous Long Range Router](./ed_lrr/_index.md)
|
||||||
|
- [Graph representation](./ed_lrr/graph.md)
|
||||||
|
- [Search modes](./ed_lrr/modes.md)
|
||||||
|
- [Graph precomputation](./ed_lrr/precomp.md)
|
||||||
|
- [Python API](./ed_lrr/py_api.md)
|
|
@ -0,0 +1 @@
|
||||||
|
# Elite Dangerous Long Range Router
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Graph representation
|
||||||
|
|
||||||
|
ED_LRR uses an implicit graph built on top of an R*-Tree for its route computation.
|
||||||
|
Every node (star system) has edges towards all systems within jump range, edge weights (the distance between star systems) can be computed on the fly when necessary
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Search modes
|
||||||
|
|
||||||
|
## Heuristic
|
||||||
|
|
||||||
|
A*, Greedy and Beam search all use the following heuristic to select candidates to expand on the next layer of the graph
|
||||||
|
|
||||||
|
$$mult(n) =
|
||||||
|
\begin{cases}
|
||||||
|
4 &\text{if $n$ is a neutron star} \\\\
|
||||||
|
1.5 &\text{if $n$ is a white dwarf star} \\\\
|
||||||
|
1 &\text{otherwise}
|
||||||
|
\end{cases}
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$d(a,b) = \sqrt{(a_x-b_x)^2+(a_y-b_y)^2+(a_z-b_z)^2}$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
h(\text{node},\text{goal})=
|
||||||
|
\max(
|
||||||
|
d(\text{node},\text{goal})-
|
||||||
|
(
|
||||||
|
\text{range}*mult(\text{node})
|
||||||
|
),0)
|
||||||
|
$$
|
||||||
|
|
||||||
|
potential new heuristic:
|
||||||
|
|
||||||
|
$$
|
||||||
|
h(\text{node},\text{next},\text{goal})=
|
||||||
|
1 - {\cos^{-1}(|(\text{next}-\text{node})| \cdot |(\text{goal}-\text{node})|)\over\pi}
|
||||||
|
$$
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Graph precomputation
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Python API
|
||||||
|
|
||||||
|
First the module needs to be imported
|
||||||
|
|
||||||
|
```python
|
||||||
|
from _ed_lrr import PyRouter, PyShip
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we need to instantiate a route plotter
|
||||||
|
|
||||||
|
```python
|
||||||
|
# callback is passed a dict describing the current search state and progress
|
||||||
|
def callback(state):
|
||||||
|
print(state)
|
||||||
|
|
||||||
|
r=PyRouter(callback)
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally ship loadouts can be loaded from the Elite: Dangerous journal files
|
||||||
|
|
||||||
|
```python
|
||||||
|
ships = PyShip.from_journal()
|
||||||
|
```
|
||||||
|
|
||||||
|
To plot a route we need to load a list of star systems with coordinates
|
||||||
|
|
||||||
|
```python
|
||||||
|
r.load("./stars.csv")
|
||||||
|
```
|
||||||
|
|
||||||
|
After a list has been loaded we can resolve star systems to their IDs
|
||||||
|
|
||||||
|
```python
|
||||||
|
systems = [
|
||||||
|
# resolve by coordinates, needs to build an R*-Tree so uses a few GB of RAM
|
||||||
|
(0,0,0),
|
||||||
|
# resolve by name, does fuzzy search, has to scan the whole list of system names
|
||||||
|
"Colonia",
|
||||||
|
# resolve by ID, fairly fast, but the IDs are ed_lrr specific
|
||||||
|
3553323
|
||||||
|
]
|
||||||
|
systems = r.resolve_systems(*query) # this will return a dict mapping the input key to a dict
|
||||||
|
assert sorted(systems.keys())==sorted(query)
|
||||||
|
sys_ids = {k: v["id"] for k, v in systems.items()}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the system IDs are known we can compute a route
|
||||||
|
|
||||||
|
```python
|
||||||
|
route = r.route(
|
||||||
|
[sys_ids["Sol"], sys_ids["Colonia"]] # route hops, can be any number of systems (at least 2)
|
||||||
|
48.0, # jump range
|
||||||
|
beam_width=1<<12, # beam width to limit exploration (4096)
|
||||||
|
greedyness=0, # greedyness for A* (0=BFS,0.5 = A*, 1=Greedy)
|
||||||
|
max_dist=500, # maximum deviation from straight line (not yet implemented)
|
||||||
|
num_workers=0 # number of workers to distribute the search accross (0 -> serial mode, >=1 -> spawn worker pool)
|
||||||
|
)
|
||||||
|
```
|
|
@ -0,0 +1,17 @@
|
||||||
|
import graph;
|
||||||
|
size(500,0);
|
||||||
|
real fsd(real m_fuel) {
|
||||||
|
// 4A drive
|
||||||
|
real boost = 1.0;
|
||||||
|
real f_max = 3.0;
|
||||||
|
real m_opt = 525.0;
|
||||||
|
real m_ship = 347.0;
|
||||||
|
real l = 12.0;
|
||||||
|
real p = 2.30;
|
||||||
|
return ((boost*m_opt*(1000.0*min(f_max,m_fuel)/l)^(1/p)))/(m_ship+m_fuel);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(graph(fsd,-100,100,n=500,Hermite),red);
|
||||||
|
|
||||||
|
xaxis("$m_{fuel}$");
|
||||||
|
yaxis("$range (Ly)$",0);
|
|
@ -0,0 +1 @@
|
||||||
|
# Intro
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Galactic Travel in Elite: Dangerous
|
||||||
|
|
||||||
|
All ships in Elite: Dangerous (E:D) are equipped with a Frame Shift Drive (FSD) which allows them to jumpst vast distances (multiple light years) from one star system to another.
|
||||||
|
|
||||||
|
The maximum range you can traverse in a single jump is limited by the maximum fuel consuption per jump of the specific drive (depends on class and rating) and influenced by the following factors:
|
||||||
|
|
||||||
|
- Rating of the FSD
|
||||||
|
- Class of the FSD
|
||||||
|
- Mass of your ship (Base mass+Cargo mass+Fuel mass)
|
||||||
|
- Amount of fuel available in the tank
|
||||||
|
|
||||||
|
For details see [the chapter detailing FSD fuel consumption](./fsd_fuel.html)
|
||||||
|
|
||||||
|
If the ship is equipped with a Fuel Scoop it can:
|
||||||
|
|
||||||
|
- Scoop hydrogen from the corona of a star to refill its fuel tank (only applies to stars of class K, G, B, F, O, A or M)
|
||||||
|
- Supercharge its FSD to increase the maximum jump range by a factor of:
|
||||||
|
- 4 if supercharging in the jets of a neutron star
|
||||||
|
- 1.5 if supercharging in the jets of a white dwarf star
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Notes on FSD Fuel consumption and jump range
|
||||||
|
|
||||||
|
FSD Fuel consumption ([Elite: Dangerous Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)):
|
||||||
|
$$Fuel = 0.001 \cdot l \cdot \left(\frac{dist \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}$$
|
||||||
|
|
||||||
|
Solving for \\(dist\\) gives the jump range (in Ly) for a given amount of fuel (in tons) as:
|
||||||
|
$$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
|
||||||
|
|
||||||
|
Assuming \\(f_{max}\\) tons of available fuel gives us the maximum jump range for a single jump as:
|
||||||
|
$$dist_{max} = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot f_{max}}{l}\right)^{\frac{1}{p}}}{f_{max} + m_{ship}}$$
|
||||||
|
|
||||||
|
Since the guardian FSD booster increases the maximum jump range by \\(B_g\\) light years we can calculate a correction factor for the fuel consumption as:
|
||||||
|
$$ e_{fuel} = 0.001 \cdot l \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p}$$
|
||||||
|
|
||||||
|
Incorporating \\(e_{fuel}\\) into the distance equation yields
|
||||||
|
$$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
|
||||||
|
|
||||||
|
Expanding \\(e_{fuel}\\) yields
|
||||||
|
$$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p} \cdot \min\left(f_{max}, m_{fuel}\right)\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
|
||||||
|
|
||||||
|
Finally, Expanding \\(dist_{max}\\) yields the full equation as
|
||||||
|
$$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000000.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{- p} \cdot \min\left(f_{max}, m_{fuel}\right)}{l^{2}}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- \\(Fuel\\) is the fuel needed to jump (in tons)
|
||||||
|
- \\(l\\) is the linear constant of your FSD (depends on the rating)
|
||||||
|
- \\(p\\) is the power constant of your FSD (depends on the class)
|
||||||
|
- \\(m_{ship}\\) is the mass of your ship (including cargo)
|
||||||
|
- \\(m_{fuel}\\) is the amount of fuel in tons currently stored in your tanks
|
||||||
|
- \\(m_{opt}\\) is the optimized mass of your FSD (in tons)
|
||||||
|
- \\(f_{max}\\) is the maximum amount of fuel your FSD can use per jump
|
||||||
|
- \\(boost\\) is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc)
|
||||||
|
- \\(dist\\) is the distance you can jump with a given fuel amount
|
||||||
|
- \\(dist_{max}\\) is the maximum distance you can jump (when \\(m_{fuel}=f_{max}\\))
|
||||||
|
- \\(B_{g}\\) is the amount of Ly added by your Guardian FSD Booster
|
||||||
|
- \\(e_{fuel}\\) is the efficiency increase added by the Guardian FSD Booster
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Graph Search algorithms
|
||||||
|
|
||||||
|
## Breadth-first search (BFS)
|
||||||
|
|
||||||
|
BFS expand node in breadth first order while keeping track of the parent node of each expanded node
|
||||||
|
|
||||||
|
## Beam search
|
||||||
|
|
||||||
|
Beam search is similar to BFS but limits the number of expanded nodes based on a heuristic
|
||||||
|
|
||||||
|
## Greedy search
|
||||||
|
|
||||||
|
Greedy search is essentially Beam search with a beam width of 1
|
||||||
|
|
||||||
|
## Dijkstra
|
||||||
|
|
||||||
|
Dijkstra's algorithm finds the shortest path across a graph based on some edge weight
|
||||||
|
|
||||||
|
## A*
|
||||||
|
|
||||||
|
A* is similar to Dijkstra but uses a heuristic to speed up the search
|
||||||
|
|
||||||
|
## Beam-Stack search (BSS)
|
||||||
|
|
||||||
|
Beam-Stack search is a variation of beam search which keeps a separate priority queue for each layer of the graph to allow backtracking and expand previously unexpanded nodes
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
import graph;
|
||||||
|
import stats;
|
||||||
|
size(500);
|
||||||
|
srand(10);
|
||||||
|
|
||||||
|
struct Star {
|
||||||
|
pair pos;
|
||||||
|
real mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
real range = 48.0;
|
||||||
|
|
||||||
|
int n_stars=1000;
|
||||||
|
Star[] stars=new Star[n_stars];
|
||||||
|
for(int i=0; i < n_stars; ++i) {
|
||||||
|
Star s=new Star;
|
||||||
|
s.pos=(Gaussrand()*range*2,Gaussrand()*range*2);
|
||||||
|
s.mult=1.0;
|
||||||
|
if (unitrand()<0.2) {
|
||||||
|
s.mult=1.5;
|
||||||
|
} else {
|
||||||
|
if (unitrand()<0.1) {
|
||||||
|
s.mult=4.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stars[i]=s;
|
||||||
|
}
|
||||||
|
|
||||||
|
Star origin=new Star;
|
||||||
|
origin.pos=(0,0);
|
||||||
|
origin.mult=1.0;
|
||||||
|
|
||||||
|
draw(circle(origin.pos,range*origin.mult),white);
|
||||||
|
draw(circle(origin.pos,range),white+dashed);
|
||||||
|
draw(circle(origin.pos,range*2),white+dashed);
|
||||||
|
draw(circle(origin.pos,range*4),white+dashed);
|
||||||
|
fill(circle(origin.pos,2),red);
|
||||||
|
|
||||||
|
for (Star s: stars) {
|
||||||
|
if (length(s.pos-origin.pos)<(range*origin.mult)) {
|
||||||
|
draw(s.pos--origin.pos,white+dashed);
|
||||||
|
fill(circle(s.pos,s.mult),green);
|
||||||
|
} else {
|
||||||
|
fill(circle(s.pos,s.mult),white);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "rust"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"discord.enabled": true,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/CVS": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/Thumbs.db": true,
|
||||||
|
"**/.history": true,
|
||||||
|
".history": true
|
||||||
|
},
|
||||||
|
"explorerExclude.backup": null
|
||||||
|
}
|
||||||
|
}
|
|
@ -304,15 +304,7 @@ def preprocess(systems, bodies, output):
|
||||||
"-g",
|
"-g",
|
||||||
metavar="<float>",
|
metavar="<float>",
|
||||||
default=cfg["route.greediness"],
|
default=cfg["route.greediness"],
|
||||||
help="Greedyness factor for A-Star",
|
help="Greedyness factor (0.0=BFS, 1.0=Greedy)",
|
||||||
show_default=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--mode",
|
|
||||||
"-m",
|
|
||||||
default=cfg["route.mode"],
|
|
||||||
help="Search mode",
|
|
||||||
type=click.Choice(["bfs", "bfs_old", "a-star", "greedy"]),
|
|
||||||
show_default=True,
|
show_default=True,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
from sympy import *
|
||||||
|
from sympy.utilities.codegen import RustCodeGen
|
||||||
|
|
||||||
|
|
||||||
|
def to_latex(eq, inline=False):
|
||||||
|
mode = "equation"
|
||||||
|
itex = True
|
||||||
|
if inline:
|
||||||
|
mode = "inline"
|
||||||
|
itex = False
|
||||||
|
return latex(eq, mul_symbol=" \\cdot ", mode=mode, itex=itex)
|
||||||
|
|
||||||
|
|
||||||
|
def solve_for(eq, sym):
|
||||||
|
return Eq(sym, solve(eq, sym)[0])
|
||||||
|
|
||||||
|
|
||||||
|
init_printing(use_latex=True, latex_mode="equation", mul_symbol="\\cdot")
|
||||||
|
|
||||||
|
var("m_ship m_fuel m_opt f_useable f_max l p boost dist dist_max Fuel B_g e_fuel")
|
||||||
|
|
||||||
|
mass = m_ship + m_fuel # total mass of ship+fuel
|
||||||
|
m_opt = m_opt * boost # supercharging increases optimized mass
|
||||||
|
|
||||||
|
available_fuel = Min(
|
||||||
|
f_max, m_fuel
|
||||||
|
) # limit maximum fuel consumption to FSD max fuel limit
|
||||||
|
|
||||||
|
eq_fuel = Eq(Fuel, l * 0.001 * (((dist * mass) / m_opt) ** p)) # FSD Fuel equation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
eq_fuel_boost = eq_fuel.subs({"dist":dist+B_g,"Fuel":available_fuel*e_fuel}) # FSD Booster boosts maximum distance by B_g
|
||||||
|
eq_d_boost = solve_for(eq_fuel_boost, dist) # solve for distance
|
||||||
|
# eq_d_boost = eq_d_boost.subs({"Fuel":f_max,"m_fuel":f_max}) # Assume maximum jump range
|
||||||
|
|
||||||
|
print(to_latex(eq_d_boost))
|
||||||
|
print(to_latex(solve_for(eq_d_boost,e_fuel)))
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
max_range = eq_d.subs(
|
||||||
|
{m_fuel: f_max, dist: dist_max}
|
||||||
|
) # Compute maximum jump range by assuming f_max tons of fuel in tank
|
||||||
|
|
||||||
|
full_eq = eq_d.subs(
|
||||||
|
Min(f_max, m_fuel), Min(f_max, m_fuel) * fuel_mult
|
||||||
|
).subs(
|
||||||
|
max_range.lhs, max_range.rhs
|
||||||
|
) # substitute everything in
|
||||||
|
|
||||||
|
docs = [
|
||||||
|
(
|
||||||
|
eq_fuel,
|
||||||
|
"FSD Fuel consumption ([E:D Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)):",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
eq_d,
|
||||||
|
"Solving for $dist$ gives the jump range (in Ly) for a given amount of fuel (in tons) as:",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
max_range,
|
||||||
|
"Assuming $f_{max}$ tons of available fuel gives us the maximum jump range for a single jump as:",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fuel_mult,
|
||||||
|
"Since the guardian FSD booster increases the maximum jump range by $B_g$ Ly we can calculate a correction factor for the fuel consumption as:",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
eq_d.subs(Min(f_max, m_fuel), Min(f_max, m_fuel) * e_fuel),
|
||||||
|
"Incorporating $e_{fuel}$ into the distance equation yields",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
eq_d.subs(Min(f_max, m_fuel), Min(f_max, m_fuel) * fuel_mult),
|
||||||
|
"Expanding $e_{fuel}$ yields",
|
||||||
|
),
|
||||||
|
(full_eq, "Finally, Expanding $dist_{max}$ yields the full equation as"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
var_defs = [
|
||||||
|
("Fuel", "is the fuel needed to jump (in tons)"),
|
||||||
|
("l", "is the linear constant of your FSD (depends on the rating)"),
|
||||||
|
("p", "is the power constant of your FSD (depends on the class)"),
|
||||||
|
("m_ship", "is the mass of your ship (including cargo)"),
|
||||||
|
("m_fuel", "is the amount of fuel in tons currently stored in your tanks"),
|
||||||
|
("m_opt", "is the optimized mass of your FSD (in tons)"),
|
||||||
|
("f_max", "is the maximum amount of fuel your FSD can use per jump"),
|
||||||
|
(
|
||||||
|
"boost",
|
||||||
|
'is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc)',
|
||||||
|
),
|
||||||
|
("dist", "is the distance you can jump with a given fuel amount"),
|
||||||
|
("dist_max", "is the maximum distance you can jump (when $m_{fuel}=f_{max}$)"),
|
||||||
|
("B_g", "is the amount of Ly added by your Guardian FSD Booster"),
|
||||||
|
("e_fuel", "is the efficiency increase added by the Guardian FSD Booster"),
|
||||||
|
]
|
||||||
|
for eq, doc in docs:
|
||||||
|
eq=simplify(eq)
|
||||||
|
if doc:
|
||||||
|
print(doc, to_latex(eq))
|
||||||
|
else:
|
||||||
|
print(to_latex(eq))
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("Where:")
|
||||||
|
for name, desc in var_defs:
|
||||||
|
print("- {} {}".format(to_latex(symbols(name), True), desc))
|
|
@ -0,0 +1,904 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Populating the interactive namespace from numpy and matplotlib\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"%pylab notebook"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 131,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"<ipython-input-131-1f27bb9e71ac>:5: RuntimeWarning: invalid value encountered in double_scalars\n",
|
||||||
|
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"application/javascript": [
|
||||||
|
"/* Put everything inside the global mpl namespace */\n",
|
||||||
|
"window.mpl = {};\n",
|
||||||
|
"\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('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",
|
||||||
|
"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 = $('<div/>');\n",
|
||||||
|
" this._root_extra_style(this.root)\n",
|
||||||
|
" this.root.attr('style', 'display: inline-block');\n",
|
||||||
|
"\n",
|
||||||
|
" $(parent_element).append(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 (mpl.ratio != 1) {\n",
|
||||||
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\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 = $(\n",
|
||||||
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
||||||
|
" 'ui-helper-clearfix\"/>');\n",
|
||||||
|
" var titletext = $(\n",
|
||||||
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
||||||
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
||||||
|
" titlebar.append(titletext)\n",
|
||||||
|
" this.root.append(titlebar);\n",
|
||||||
|
" this.header = titletext[0];\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
||||||
|
"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
||||||
|
"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
||||||
|
" var fig = this;\n",
|
||||||
|
"\n",
|
||||||
|
" var canvas_div = $('<div/>');\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
||||||
|
"\n",
|
||||||
|
" function canvas_keyboard_event(event) {\n",
|
||||||
|
" return fig.key_event(event, event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
||||||
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
||||||
|
" this.canvas_div = canvas_div\n",
|
||||||
|
" this._canvas_extra_style(canvas_div)\n",
|
||||||
|
" this.root.append(canvas_div);\n",
|
||||||
|
"\n",
|
||||||
|
" var canvas = $('<canvas/>');\n",
|
||||||
|
" canvas.addClass('mpl-canvas');\n",
|
||||||
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
||||||
|
"\n",
|
||||||
|
" this.canvas = canvas[0];\n",
|
||||||
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
||||||
|
"\n",
|
||||||
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
||||||
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
||||||
|
"\n",
|
||||||
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
||||||
|
"\n",
|
||||||
|
" var rubberband = $('<canvas/>');\n",
|
||||||
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
||||||
|
"\n",
|
||||||
|
" var pass_mouse_events = true;\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.resizable({\n",
|
||||||
|
" start: function(event, ui) {\n",
|
||||||
|
" pass_mouse_events = false;\n",
|
||||||
|
" },\n",
|
||||||
|
" resize: function(event, ui) {\n",
|
||||||
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
||||||
|
" },\n",
|
||||||
|
" stop: function(event, ui) {\n",
|
||||||
|
" pass_mouse_events = true;\n",
|
||||||
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
||||||
|
" },\n",
|
||||||
|
" });\n",
|
||||||
|
"\n",
|
||||||
|
" function mouse_event_fn(event) {\n",
|
||||||
|
" if (pass_mouse_events)\n",
|
||||||
|
" return fig.mouse_event(event, event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
||||||
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
||||||
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
||||||
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
||||||
|
"\n",
|
||||||
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
||||||
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
||||||
|
" event = event.originalEvent;\n",
|
||||||
|
" event['data'] = 'scroll'\n",
|
||||||
|
" if (event.deltaY < 0) {\n",
|
||||||
|
" event.step = 1;\n",
|
||||||
|
" } else {\n",
|
||||||
|
" event.step = -1;\n",
|
||||||
|
" }\n",
|
||||||
|
" mouse_event_fn(event);\n",
|
||||||
|
" });\n",
|
||||||
|
"\n",
|
||||||
|
" canvas_div.append(canvas);\n",
|
||||||
|
" canvas_div.append(rubberband);\n",
|
||||||
|
"\n",
|
||||||
|
" this.rubberband = rubberband;\n",
|
||||||
|
" this.rubberband_canvas = rubberband[0];\n",
|
||||||
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
||||||
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
||||||
|
"\n",
|
||||||
|
" this._resize_canvas = function(width, height) {\n",
|
||||||
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
||||||
|
" // canvas in synch.\n",
|
||||||
|
" canvas_div.css('width', width)\n",
|
||||||
|
" canvas_div.css('height', height)\n",
|
||||||
|
"\n",
|
||||||
|
" canvas.attr('width', width * mpl.ratio);\n",
|
||||||
|
" canvas.attr('height', height * mpl.ratio);\n",
|
||||||
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
||||||
|
"\n",
|
||||||
|
" rubberband.attr('width', width);\n",
|
||||||
|
" rubberband.attr('height', height);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
||||||
|
" // upon first draw.\n",
|
||||||
|
" this._resize_canvas(600, 600);\n",
|
||||||
|
"\n",
|
||||||
|
" // Disable right mouse context menu.\n",
|
||||||
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\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 nav_element = $('<div/>');\n",
|
||||||
|
" nav_element.attr('style', 'width: 100%');\n",
|
||||||
|
" this.root.append(nav_element);\n",
|
||||||
|
"\n",
|
||||||
|
" // Define a callback function for later on.\n",
|
||||||
|
" function toolbar_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
" function toolbar_mouse_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\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",
|
||||||
|
" // put a spacer in here.\n",
|
||||||
|
" continue;\n",
|
||||||
|
" }\n",
|
||||||
|
" var button = $('<button/>');\n",
|
||||||
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
||||||
|
" 'ui-button-icon-only');\n",
|
||||||
|
" button.attr('role', 'button');\n",
|
||||||
|
" button.attr('aria-disabled', 'false');\n",
|
||||||
|
" button.click(method_name, toolbar_event);\n",
|
||||||
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
||||||
|
"\n",
|
||||||
|
" var icon_img = $('<span/>');\n",
|
||||||
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
||||||
|
" icon_img.addClass(image);\n",
|
||||||
|
" icon_img.addClass('ui-corner-all');\n",
|
||||||
|
"\n",
|
||||||
|
" var tooltip_span = $('<span/>');\n",
|
||||||
|
" tooltip_span.addClass('ui-button-text');\n",
|
||||||
|
" tooltip_span.html(tooltip);\n",
|
||||||
|
"\n",
|
||||||
|
" button.append(icon_img);\n",
|
||||||
|
" button.append(tooltip_span);\n",
|
||||||
|
"\n",
|
||||||
|
" nav_element.append(button);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var fmt_picker_span = $('<span/>');\n",
|
||||||
|
"\n",
|
||||||
|
" var fmt_picker = $('<select/>');\n",
|
||||||
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
||||||
|
" fmt_picker_span.append(fmt_picker);\n",
|
||||||
|
" nav_element.append(fmt_picker_span);\n",
|
||||||
|
" this.format_dropdown = fmt_picker[0];\n",
|
||||||
|
"\n",
|
||||||
|
" for (var ind in mpl.extensions) {\n",
|
||||||
|
" var fmt = mpl.extensions[ind];\n",
|
||||||
|
" var option = $(\n",
|
||||||
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
||||||
|
" fmt_picker.append(option);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" // Add hover states to the ui-buttons\n",
|
||||||
|
" $( \".ui-button\" ).hover(\n",
|
||||||
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
||||||
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
||||||
|
" );\n",
|
||||||
|
"\n",
|
||||||
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
||||||
|
" nav_element.append(status_bar);\n",
|
||||||
|
" this.message = status_bar[0];\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",
|
||||||
|
"\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",
|
||||||
|
"\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]);\n",
|
||||||
|
" fig.send_message(\"refresh\", {});\n",
|
||||||
|
" };\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
||||||
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
||||||
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
||||||
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
||||||
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.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, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\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",
|
||||||
|
" var cursor = msg['cursor'];\n",
|
||||||
|
" switch(cursor)\n",
|
||||||
|
" {\n",
|
||||||
|
" case 0:\n",
|
||||||
|
" cursor = 'pointer';\n",
|
||||||
|
" break;\n",
|
||||||
|
" case 1:\n",
|
||||||
|
" cursor = 'default';\n",
|
||||||
|
" break;\n",
|
||||||
|
" case 2:\n",
|
||||||
|
" cursor = 'crosshair';\n",
|
||||||
|
" break;\n",
|
||||||
|
" case 3:\n",
|
||||||
|
" cursor = 'move';\n",
|
||||||
|
" break;\n",
|
||||||
|
" }\n",
|
||||||
|
" fig.rubberband_canvas.style.cursor = 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.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",
|
||||||
|
" /* 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",
|
||||||
|
" evt.data.type = \"image/png\";\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",
|
||||||
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
||||||
|
" evt.data);\n",
|
||||||
|
" fig.updated_canvas_event();\n",
|
||||||
|
" fig.waiting = false;\n",
|
||||||
|
" return;\n",
|
||||||
|
" }\n",
|
||||||
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\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(\"No handler for the '\" + msg_type + \"' message type: \", msg);\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(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
||||||
|
" }\n",
|
||||||
|
" }\n",
|
||||||
|
" };\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"// from http://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",
|
||||||
|
" if (e.target)\n",
|
||||||
|
" targ = e.target;\n",
|
||||||
|
" else if (e.srcElement)\n",
|
||||||
|
" targ = e.srcElement;\n",
|
||||||
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
||||||
|
" targ = targ.parentNode;\n",
|
||||||
|
"\n",
|
||||||
|
" // jQuery normalizes the pageX and pageY\n",
|
||||||
|
" // pageX,Y are the mouse positions relative to the document\n",
|
||||||
|
" // offset() returns the position of the element relative to the document\n",
|
||||||
|
" var x = e.pageX - $(targ).offset().left;\n",
|
||||||
|
" var y = e.pageY - $(targ).offset().top;\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",
|
||||||
|
" * http://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",
|
||||||
|
" 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",
|
||||||
|
" {\n",
|
||||||
|
" this.canvas.focus();\n",
|
||||||
|
" this.canvas_div.focus();\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
||||||
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
||||||
|
"\n",
|
||||||
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
||||||
|
" step: event.step,\n",
|
||||||
|
" guiEvent: simpleKeys(event)});\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",
|
||||||
|
"\n",
|
||||||
|
" // Prevent repeat events\n",
|
||||||
|
" if (name == 'key_press')\n",
|
||||||
|
" {\n",
|
||||||
|
" if (event.which === this._key)\n",
|
||||||
|
" return;\n",
|
||||||
|
" else\n",
|
||||||
|
" this._key = event.which;\n",
|
||||||
|
" }\n",
|
||||||
|
" if (name == 'key_release')\n",
|
||||||
|
" this._key = null;\n",
|
||||||
|
"\n",
|
||||||
|
" var value = '';\n",
|
||||||
|
" if (event.ctrlKey && event.which != 17)\n",
|
||||||
|
" value += \"ctrl+\";\n",
|
||||||
|
" if (event.altKey && event.which != 18)\n",
|
||||||
|
" value += \"alt+\";\n",
|
||||||
|
" if (event.shiftKey && event.which != 16)\n",
|
||||||
|
" value += \"shift+\";\n",
|
||||||
|
"\n",
|
||||||
|
" value += 'k';\n",
|
||||||
|
" value += event.which.toString();\n",
|
||||||
|
"\n",
|
||||||
|
" this._key_event_extra(event, name);\n",
|
||||||
|
"\n",
|
||||||
|
" this.send_message(name, {key: value,\n",
|
||||||
|
" 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",
|
||||||
|
"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\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.default_extension = \"png\";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.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",
|
||||||
|
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
|
||||||
|
" ws.onmessage(msg['content']['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 = $(\"#\" + id);\n",
|
||||||
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
||||||
|
"\n",
|
||||||
|
" function ondownload(figure, format) {\n",
|
||||||
|
" window.open(figure.imageObj.src);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
||||||
|
" ondownload,\n",
|
||||||
|
" element.get(0));\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.get(0);\n",
|
||||||
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
||||||
|
" if (!fig.cell_info) {\n",
|
||||||
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
||||||
|
" return;\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" var output_index = fig.cell_info[2]\n",
|
||||||
|
" var cell = fig.cell_info[0];\n",
|
||||||
|
"\n",
|
||||||
|
"};\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
||||||
|
" var width = fig.canvas.width/mpl.ratio\n",
|
||||||
|
" fig.root.unbind('remove')\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).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\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/mpl.ratio\n",
|
||||||
|
" var dataURL = this.canvas.toDataURL();\n",
|
||||||
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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 () { fig.push_to_output() }, 1000);\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
||||||
|
" var fig = this;\n",
|
||||||
|
"\n",
|
||||||
|
" var nav_element = $('<div/>');\n",
|
||||||
|
" nav_element.attr('style', 'width: 100%');\n",
|
||||||
|
" this.root.append(nav_element);\n",
|
||||||
|
"\n",
|
||||||
|
" // Define a callback function for later on.\n",
|
||||||
|
" function toolbar_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
" function toolbar_mouse_event(event) {\n",
|
||||||
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
||||||
|
" }\n",
|
||||||
|
"\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) { continue; };\n",
|
||||||
|
"\n",
|
||||||
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
||||||
|
" button.click(method_name, toolbar_event);\n",
|
||||||
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
||||||
|
" nav_element.append(button);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" // Add the status bar.\n",
|
||||||
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
||||||
|
" nav_element.append(status_bar);\n",
|
||||||
|
" this.message = status_bar[0];\n",
|
||||||
|
"\n",
|
||||||
|
" // Add the close button to the window.\n",
|
||||||
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
||||||
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
||||||
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
||||||
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
||||||
|
" buttongrp.append(button);\n",
|
||||||
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
||||||
|
" titlebar.prepend(buttongrp);\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
||||||
|
" var fig = this\n",
|
||||||
|
" el.on(\"remove\", function(){\n",
|
||||||
|
"\tfig.close_ws(fig, {});\n",
|
||||||
|
" });\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
||||||
|
" // this is important to make the div 'focusable\n",
|
||||||
|
" el.attr('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",
|
||||||
|
" }\n",
|
||||||
|
" else {\n",
|
||||||
|
" // location in version 2\n",
|
||||||
|
" IPython.keyboard_manager.register_events(el);\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
||||||
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
||||||
|
" if (!manager)\n",
|
||||||
|
" manager = IPython.keyboard_manager;\n",
|
||||||
|
"\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",
|
||||||
|
"\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('matplotlib', mpl.mpl_figure_comm);\n",
|
||||||
|
"}\n"
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.Javascript object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/html": [
|
||||||
|
"<div id='215b26ff-80fe-45a3-bb52-9351ea1b2ebb'></div>"
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.HTML object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"(0, 100)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 131,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def h(p,src,dest):\n",
|
||||||
|
" c = (((src-dest)**2).sum())**0.5\n",
|
||||||
|
" a = (((dest-p)**2).sum())**0.5\n",
|
||||||
|
" b = (((src-p)**2).sum())**0.5\n",
|
||||||
|
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n",
|
||||||
|
" return a-b\n",
|
||||||
|
"S=np.array([10,10])\n",
|
||||||
|
"D=np.array([20,25])\n",
|
||||||
|
"\n",
|
||||||
|
"Rx=range(100)\n",
|
||||||
|
"Ry=range(100)\n",
|
||||||
|
"grid=np.zeros((len(Rx),len(Ry)))\n",
|
||||||
|
"for px,x in enumerate(Rx):\n",
|
||||||
|
" for py,y in enumerate(Ry):\n",
|
||||||
|
" grid[px,py]=h(np.array([x,y]),S,D)\n",
|
||||||
|
"imshow(grid,cmap='coolwarm_r',origin='lower')\n",
|
||||||
|
"colorbar()\n",
|
||||||
|
"\n",
|
||||||
|
"scatter(*S,color='green')\n",
|
||||||
|
"scatter(*D,color='red')\n",
|
||||||
|
"plt.xlim(0,100)\n",
|
||||||
|
"plt.ylim(0,100)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 38,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Object `np.magnitude` not found.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"np."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3.8.1 64-bit ('anaconda': conda)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python38164bitanacondaconda2a51168e890d45bd836f654eb2ae46f7"
|
||||||
|
},
|
||||||
|
"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.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 4
|
||||||
|
}
|
49
icon/make.py
49
icon/make.py
|
@ -1,6 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
import svgpathtools
|
||||||
import random
|
import random
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
from math import sin, cos, pi
|
from math import sin, cos, pi
|
||||||
import tsp as m_tsp
|
import tsp as m_tsp
|
||||||
|
|
||||||
|
@ -30,8 +33,6 @@ def make_points(n, size, min_dist=0):
|
||||||
px, py = random.random(), random.random()
|
px, py = random.random(), random.random()
|
||||||
px *= size / 2
|
px *= size / 2
|
||||||
py *= size / 2
|
py *= size / 2
|
||||||
px += 70
|
|
||||||
py += 70
|
|
||||||
valid = True
|
valid = True
|
||||||
for p in points:
|
for p in points:
|
||||||
if dist2(p, (px, py)) < min_dist:
|
if dist2(p, (px, py)) < min_dist:
|
||||||
|
@ -60,9 +61,9 @@ def generate(seed, name=None, small=False):
|
||||||
size = 1000
|
size = 1000
|
||||||
if name is None:
|
if name is None:
|
||||||
name = seed
|
name = seed
|
||||||
dwg = svgwrite.Drawing(filename="out/{}.svg".format(name))
|
out_path = "out/{}.svg".format(name)
|
||||||
dwg.defs.add(dwg.style(".background { fill: #222; }"))
|
dwg = svgwrite.Drawing(filename=out_path)
|
||||||
dwg.add(dwg.rect(size=("100%", "100%"), class_="background"))
|
dwg.defs.add(dwg.style(".background { fill: #222 }"))
|
||||||
print("Generating points...")
|
print("Generating points...")
|
||||||
color = "#eee"
|
color = "#eee"
|
||||||
pos = make_points(num_points, size, min_dist=min_dist)
|
pos = make_points(num_points, size, min_dist=min_dist)
|
||||||
|
@ -74,12 +75,7 @@ def generate(seed, name=None, small=False):
|
||||||
x2 /= sd
|
x2 /= sd
|
||||||
y1 /= sd
|
y1 /= sd
|
||||||
y2 /= sd
|
y2 /= sd
|
||||||
dwg.add(dwg.line(
|
dwg.add(dwg.line((x1, y1), (x2, y2), stroke_width=w, stroke=color))
|
||||||
(x1, y1),
|
|
||||||
(x2, y2),
|
|
||||||
stroke_width=w,
|
|
||||||
stroke=color
|
|
||||||
))
|
|
||||||
|
|
||||||
for (px, py) in pos:
|
for (px, py) in pos:
|
||||||
base_r = 3
|
base_r = 3
|
||||||
|
@ -111,17 +107,13 @@ def generate(seed, name=None, small=False):
|
||||||
random.random()
|
random.random()
|
||||||
random.random()
|
random.random()
|
||||||
random.random()
|
random.random()
|
||||||
|
random.random()
|
||||||
continue
|
continue
|
||||||
r += ring_step(random.random())
|
r += ring_step(random.random())
|
||||||
ring_col = color
|
ring_col = color
|
||||||
if random.random() > 0.75:
|
if random.random() > 0.75:
|
||||||
ring_col = "#ea0"
|
ring_col = "#ea0"
|
||||||
circ = dwg.add(dwg.circle(
|
circ = dwg.add(dwg.circle((px, py), r=r, stroke_width=w, stroke=ring_col))
|
||||||
(px, py),
|
|
||||||
r=r,
|
|
||||||
stroke_width=w,
|
|
||||||
stroke=ring_col
|
|
||||||
))
|
|
||||||
circ.fill(color, opacity=0)
|
circ.fill(color, opacity=0)
|
||||||
d = random.random() * pi * 2
|
d = random.random() * pi * 2
|
||||||
dx = cos(d)
|
dx = cos(d)
|
||||||
|
@ -136,10 +128,27 @@ def generate(seed, name=None, small=False):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moon.fill(ring_col)
|
moon.fill(ring_col)
|
||||||
|
dwg.fit()
|
||||||
dwg.save()
|
path = tempfile.TemporaryDirectory()
|
||||||
|
filename = os.path.join(path.name, "out.svg")
|
||||||
|
dwg.saveas(filename)
|
||||||
|
paths, attrs = svgpathtools.svg2paths(filename)
|
||||||
|
bbox = [float("inf"), float("-inf"), float("inf"), float("-inf")]
|
||||||
|
for path in paths:
|
||||||
|
path_bbox = path.bbox()
|
||||||
|
bbox[0] = min(bbox[0], path_bbox[0]) # xmin
|
||||||
|
bbox[1] = max(bbox[1], path_bbox[1]) # xmax
|
||||||
|
bbox[2] = min(bbox[2], path_bbox[2]) # ymin
|
||||||
|
bbox[3] = max(bbox[3], path_bbox[3]) # ymax
|
||||||
|
px = bbox[0]
|
||||||
|
sx = (bbox[1] - bbox[0])
|
||||||
|
py = bbox[2]
|
||||||
|
sy = (bbox[3] - bbox[2])
|
||||||
|
dwg.add(dwg.rect(x=px, y=px, size=(sx, sy), class_="background"))
|
||||||
|
dwg.elements.insert(1, dwg.elements.pop(-1))
|
||||||
|
dwg.saveas(out_path)
|
||||||
|
|
||||||
|
|
||||||
seed = -4
|
seed = -5
|
||||||
generate(seed, "icon_1", small=False)
|
generate(seed, "icon_1", small=False)
|
||||||
generate(seed, "icon_1_small", small=True)
|
generate(seed, "icon_1_small", small=True)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
|
@ -1,2 +1,2 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<svg baseProfile="full" height="100%" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222; }]]></style></defs><rect class="background" height="100%" width="100%" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="188.02404486871725" x2="103.25754783979495" y1="121.5830171153579" y2="270.7955072425374" /><line stroke="#eee" stroke-width="2" x1="103.25754783979495" x2="528.9775215438594" y1="270.7955072425374" y2="470.2261757479043" /><line stroke="#eee" stroke-width="2" x1="528.9775215438594" x2="452.58130125271924" y1="470.2261757479043" y2="180.96408784515882" /><line stroke="#eee" stroke-width="2" x1="452.58130125271924" x2="338.3400040874068" y1="180.96408784515882" y2="208.34132172072512" /><circle cx="188.02404486871725" cy="121.5830171153579" fill="#eee" r="3.3185498772945903" stroke="#eee" stroke-width="2" /><circle cx="188.02404486871725" cy="121.5830171153579" fill="#eee" fill-opacity="0" r="22.429593602379832" stroke="#eee" stroke-width="2" /><circle cx="173.80179554276944" cy="104.23901834695114" fill="#eee" r="2.52050830126476" stroke="#eee" stroke-width="2" /><circle cx="103.25754783979495" cy="270.7955072425374" fill="#eee" r="3.4944547782059745" stroke="#eee" stroke-width="2" /><circle cx="103.25754783979495" cy="270.7955072425374" fill="#eee" fill-opacity="0" r="19.2697560241313" stroke="#eee" stroke-width="2" /><circle cx="115.03444741085107" cy="255.5433554690071" fill="#eee" r="3.7601015027223985" stroke="#eee" stroke-width="2" /><circle cx="103.25754783979495" cy="270.7955072425374" fill="#eee" fill-opacity="0" r="30.13693855048052" stroke="#eee" stroke-width="2" /><circle cx="89.02212054970202" cy="244.23260689141011" fill="#eee" r="3.0119075514208307" stroke="#eee" stroke-width="2" /><circle cx="528.9775215438594" cy="470.2261757479043" fill="#eee" r="4.420763662755435" stroke="#eee" stroke-width="2" /><circle cx="528.9775215438594" cy="470.2261757479043" fill="#eee" fill-opacity="0" r="22.44577790309402" stroke="#ea0" stroke-width="2" /><circle cx="549.9596596985477" cy="462.25354606049257" fill="#ea0" r="3.680925358835544" stroke="#ea0" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" r="3.8758250081323116" stroke="#eee" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" fill-opacity="0" r="21.8231723987879" stroke="#ea0" stroke-width="2" /><circle cx="430.78831758434035" cy="179.8166052191519" fill="#ea0" r="2.827892086263464" stroke="#ea0" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" fill-opacity="0" r="37.812297120687795" stroke="#eee" stroke-width="2" /><circle cx="472.57653937753463" cy="213.0570818761791" fill="#eee" r="2.6102231928654778" stroke="#eee" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" fill-opacity="0" r="55.938220307034" stroke="#eee" stroke-width="2" /><circle cx="506.1669380410402" cy="197.01600427617765" fill="#eee" r="3.252701491079807" stroke="#eee" stroke-width="2" /><circle cx="338.3400040874068" cy="208.34132172072512" fill="#eee" r="4.603865384638267" stroke="#eee" stroke-width="2" /><circle cx="338.3400040874068" cy="208.34132172072512" fill="#eee" fill-opacity="0" r="20.00878719559634" stroke="#eee" stroke-width="2" /><circle cx="329.11968037233845" cy="190.58358550160054" fill="#eee" r="2.132876938772122" stroke="#eee" stroke-width="2" /><circle cx="338.3400040874068" cy="208.34132172072512" fill="#eee" fill-opacity="0" r="39.144105385654704" stroke="#eee" stroke-width="2" /><circle cx="301.84139133159863" cy="222.48742554279568" fill="#eee" r="2.3674072974299003" stroke="#eee" stroke-width="2" /></svg>
|
<svg baseProfile="full" height="100%" preserveAspectRatio="xMidYMid meet" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222 }]]></style></defs><rect class="background" height="517.2983884727208" width="530.2849722660202" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="311.450847444851" x2="14.502614141807369" y1="370.89349463036467" y2="232.81132718905266" /><line stroke="#eee" stroke-width="2" x1="14.502614141807369" x2="450.45024587531134" y1="232.81132718905266" y2="56.60298232657218" /><line stroke="#eee" stroke-width="2" x1="450.45024587531134" x2="471.67835849915684" y1="56.60298232657218" y2="324.4872765684621" /><line stroke="#eee" stroke-width="2" x1="471.67835849915684" x2="397.5967827828483" y1="324.4872765684621" y2="471.2251418885252" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" r="3.739718497859491" stroke="#eee" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="14.019744021739154" stroke="#eee" stroke-width="2" /><circle cx="309.7972050350797" cy="356.9716165524724" fill="#eee" r="2.816302109949028" stroke="#eee" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="25.84050065951694" stroke="#ea0" stroke-width="2" /><circle cx="291.5987578747967" cy="387.4351394711004" fill="#ea0" r="3.5238508551887717" stroke="#ea0" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="36.559818016667215" stroke="#eee" stroke-width="2" /><circle cx="277.06522171012307" cy="383.3131981792842" fill="#eee" r="2.2644817084338493" stroke="#eee" stroke-width="2" /><circle cx="14.502614141807369" cy="232.81132718905266" fill="#0ae" r="3.015939411732324" stroke="#0ae" stroke-width="2" /><circle cx="14.502614141807369" cy="232.81132718905266" fill="#eee" fill-opacity="0" r="22.601271002174997" stroke="#eee" stroke-width="2" /><circle cx="25.825648341003884" cy="252.3716530410824" fill="#eee" r="2.6273027354699074" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" r="5.628356799732829" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="15.047795145337929" stroke="#ea0" stroke-width="2" /><circle cx="444.96726006047606" cy="42.5896670410885" fill="#ea0" r="3.933128624634391" stroke="#ea0" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="33.98521192110272" stroke="#eee" stroke-width="2" /><circle cx="428.59208164282967" cy="82.62634271119818" fill="#eee" r="2.331912114259491" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="45.44223101650954" stroke="#eee" stroke-width="2" /><circle cx="436.0392802753398" cy="99.69962291762357" fill="#eee" r="3.2062199948153087" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" r="5.033802748643073" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" fill-opacity="0" r="13.660224505510772" stroke="#eee" stroke-width="2" /><circle cx="466.96528173978857" cy="337.3086899461385" fill="#eee" r="3.3928396152823135" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" fill-opacity="0" r="25.507956906495714" stroke="#eee" stroke-width="2" /><circle cx="483.09849262326327" cy="347.2959679410741" fill="#eee" r="2.5123636718854243" stroke="#eee" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#0ae" r="5.847661430011579" stroke="#0ae" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#eee" fill-opacity="0" r="21.448808893881296" stroke="#eee" stroke-width="2" /><circle cx="402.63513027636975" cy="450.3764858766675" fill="#eee" r="2.7323689516837213" stroke="#eee" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#eee" fill-opacity="0" r="37.23399718445004" stroke="#eee" stroke-width="2" /><circle cx="433.2375240152304" cy="482.0004892489591" fill="#eee" r="2.3618389759020957" stroke="#eee" stroke-width="2" /></svg>
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
|
@ -1,2 +1,2 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<svg baseProfile="full" height="100%" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222; }]]></style></defs><rect class="background" height="100%" width="100%" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="94.01202243435863" x2="51.628773919897476" y1="60.79150855767895" y2="135.3977536212687" /><line stroke="#eee" stroke-width="2" x1="51.628773919897476" x2="264.4887607719297" y1="135.3977536212687" y2="235.11308787395214" /><line stroke="#eee" stroke-width="2" x1="264.4887607719297" x2="226.29065062635962" y1="235.11308787395214" y2="90.48204392257941" /><line stroke="#eee" stroke-width="2" x1="226.29065062635962" x2="169.1700020437034" y1="90.48204392257941" y2="104.17066086036256" /><circle cx="94.01202243435863" cy="60.79150855767895" fill="#eee" r="5.53091646215765" stroke="#eee" stroke-width="2" /><circle cx="51.628773919897476" cy="135.3977536212687" fill="#eee" r="6.358738772326545" stroke="#eee" stroke-width="2" /><circle cx="264.4887607719297" cy="235.11308787395214" fill="#0ae" r="9.400253756805997" stroke="#0ae" stroke-width="2" /><circle cx="226.29065062635962" cy="90.48204392257941" fill="#eee" r="6.236611100792434" stroke="#eee" stroke-width="2" /><circle cx="169.1700020437034" cy="104.17066086036256" fill="#eee" r="9.41158619939395" stroke="#eee" stroke-width="2" /></svg>
|
<svg baseProfile="full" height="100%" preserveAspectRatio="xMidYMid meet" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222 }]]></style></defs><rect class="background" height="246.4377768305505" width="267.0041091126337" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="155.7254237224255" x2="7.251307070903684" y1="185.44674731518234" y2="116.40566359452633" /><line stroke="#eee" stroke-width="2" x1="7.251307070903684" x2="225.22512293765567" y1="116.40566359452633" y2="28.30149116328609" /><line stroke="#eee" stroke-width="2" x1="225.22512293765567" x2="235.83917924957842" y1="28.30149116328609" y2="162.24363828423105" /><line stroke="#eee" stroke-width="2" x1="235.83917924957842" x2="198.79839139142416" y1="162.24363828423105" y2="235.6125709442626" /><circle cx="155.7254237224255" cy="185.44674731518234" fill="#eee" r="6.232864163099151" stroke="#eee" stroke-width="2" /><circle cx="7.251307070903684" cy="116.40566359452633" fill="#0ae" r="5.026565686220541" stroke="#0ae" stroke-width="2" /><circle cx="225.22512293765567" cy="28.30149116328609" fill="#eee" r="9.380594666221384" stroke="#eee" stroke-width="2" /><circle cx="235.83917924957842" cy="162.24363828423105" fill="#eee" r="8.389671247738455" stroke="#eee" stroke-width="2" /><circle cx="198.79839139142416" cy="235.6125709442626" fill="#0ae" r="9.74610238335263" stroke="#0ae" stroke-width="2" /></svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,132 @@
|
||||||
|
import dearpygui.core as dpg
|
||||||
|
import dearpygui.simple as sdpg
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(loglevel="INFO"):
|
||||||
|
import coloredlogs
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
|
||||||
|
|
||||||
|
class DeltaTimeFormatter(coloredlogs.ColoredFormatter):
|
||||||
|
def format(self, record):
|
||||||
|
seconds = record.relativeCreated / 1000
|
||||||
|
duration = timedelta(seconds=seconds)
|
||||||
|
record.delta = str(duration)
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
coloredlogs.ColoredFormatter = DeltaTimeFormatter
|
||||||
|
logfmt = " | ".join(
|
||||||
|
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
|
||||||
|
)
|
||||||
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
|
if not isinstance(numeric_level, int):
|
||||||
|
raise ValueError("Invalid log level: %s" % loglevel)
|
||||||
|
coloredlogs.install(level=numeric_level, fmt=logfmt)
|
||||||
|
|
||||||
|
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
def resolve_systems(stars, *args):
|
||||||
|
import _ed_lrr
|
||||||
|
|
||||||
|
r = _ed_lrr.PyRouter(None)
|
||||||
|
r.load(stars)
|
||||||
|
return r.resolve_systems(*args)
|
||||||
|
|
||||||
|
|
||||||
|
class EdLrrGui:
|
||||||
|
def __init__(self):
|
||||||
|
self.pool = ProcessPoolExecutor(1)
|
||||||
|
self.systems = []
|
||||||
|
self.__resolve_job = None
|
||||||
|
self.logger = logging.getLogger("GUI")
|
||||||
|
|
||||||
|
def __set_state(self, state):
|
||||||
|
print(state)
|
||||||
|
dpg.set_value("ed_lrr_state", state)
|
||||||
|
|
||||||
|
def __resolve_done(self, fut):
|
||||||
|
data = fut.result()
|
||||||
|
self.logger.info(f"Gor resolver data back: {data}")
|
||||||
|
self.__resolve_job = None
|
||||||
|
for n, system in enumerate(self.systems):
|
||||||
|
if system["name"] in data:
|
||||||
|
self.systems[n] = data[system["name"]]
|
||||||
|
self.systems[n]["resolved"] = True
|
||||||
|
|
||||||
|
def __resolve_systems(self, sender, data):
|
||||||
|
names = []
|
||||||
|
for system in self.systems:
|
||||||
|
if not system.get("resolver", False):
|
||||||
|
names.append(system["name"])
|
||||||
|
if self.__resolve_job is None:
|
||||||
|
job = self.pool.submit(resolve_systems, "../stars.csv", *names)
|
||||||
|
self.logger.info(f"Resolving {len(names)} systems...")
|
||||||
|
self.__resolve_job = job
|
||||||
|
self.__resolve_job.add_done_callback(self.__resolve_done)
|
||||||
|
|
||||||
|
def __render(self, sender, data):
|
||||||
|
dpg.clear_table("Systems")
|
||||||
|
for system in self.systems:
|
||||||
|
row = [
|
||||||
|
system.get("id", ""),
|
||||||
|
system["name"],
|
||||||
|
", ".join(map(str, system.get("pos", []))),
|
||||||
|
]
|
||||||
|
dpg.add_row("Systems", row)
|
||||||
|
|
||||||
|
def __add_system(self, sender, data):
|
||||||
|
system_name = dpg.get_value("sys-name")
|
||||||
|
self.systems.append({"name": system_name})
|
||||||
|
dpg.set_value("sys-name", "")
|
||||||
|
|
||||||
|
def __select_system(self, sender, data):
|
||||||
|
system_row = dpg.get_table_selections("Systems")
|
||||||
|
idx = system_row[0][0]
|
||||||
|
dpg.add_data("selected-system-index", idx)
|
||||||
|
|
||||||
|
def __remove_system(self, sender, data):
|
||||||
|
if self.systems:
|
||||||
|
system_index = dpg.get_data("selected-system-index")
|
||||||
|
self.systems.pop(system_index)
|
||||||
|
|
||||||
|
def __clear_systems(self, sender, data):
|
||||||
|
self.systems = []
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
with sdpg.window("Main Window"):
|
||||||
|
dpg.set_main_window_size(550, 550)
|
||||||
|
dpg.set_main_window_resizable(False)
|
||||||
|
dpg.set_main_window_title("Elite: Dangerous Long Range Router")
|
||||||
|
|
||||||
|
dpg.add_text("ED_LRR")
|
||||||
|
dpg.add_separator()
|
||||||
|
|
||||||
|
dpg.add_input_text("System name", source="sys-name")
|
||||||
|
dpg.add_button("Add", callback=self.__add_system)
|
||||||
|
dpg.add_separator()
|
||||||
|
|
||||||
|
dpg.add_table(
|
||||||
|
"Systems",
|
||||||
|
["ID", "Name", "Position"],
|
||||||
|
height=200,
|
||||||
|
callback=self.__select_system,
|
||||||
|
)
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_button("Remove", callback=self.__remove_system)
|
||||||
|
dpg.add_button("Clear", callback=self.__clear_systems)
|
||||||
|
dpg.add_button("Resolve", callback=self.__resolve_systems)
|
||||||
|
|
||||||
|
# Render Callback and Start gui
|
||||||
|
dpg.set_render_callback(self.__render)
|
||||||
|
dpg.start_dearpygui(primary_window="Main Window")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
edlrr_gui = EdLrrGui()
|
||||||
|
edlrr_gui.show()
|
|
@ -0,0 +1 @@
|
||||||
|
{"route": [], "dt": 292.124997}
|
File diff suppressed because it is too large
Load Diff
|
@ -28,14 +28,14 @@ versions += ["3"]
|
||||||
nox.options.keywords = "test"
|
nox.options.keywords = "test"
|
||||||
|
|
||||||
|
|
||||||
@nox.session(venv_backend="conda")
|
@nox.session(python=versions,venv_backend="conda")
|
||||||
def devenv(session):
|
def devenv(session):
|
||||||
"""Set up development environment"""
|
"""Set up development environment"""
|
||||||
global path
|
global path
|
||||||
location = os.path.abspath(session._runner.venv.location_name)
|
location = os.path.abspath(session._runner.venv.location_name)
|
||||||
session.env["PATH"] = os.pathsep.join([location, path, location])
|
session.env["PATH"] = os.pathsep.join([location, path, location])
|
||||||
session.conda_install("pycrypto", "ujson")
|
session.conda_install("pycrypto", "ujson")
|
||||||
session.install("--no-cache-dir", "-e",".[all]")
|
session.install("--no-cache-dir", "-e",".")
|
||||||
logger.warning(f'Devenv set up, now run "conda activate {location}"')
|
logger.warning(f'Devenv set up, now run "conda activate {location}"')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
// cargo-deps: crossbeam-channel="0.5.1"
|
||||||
|
extern crate crossbeam_channel;
|
||||||
|
use std::io::{BufReader,BufRead, BufWriter,Write};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use crossbeam_channel::unbounded;
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use std::thread;
|
||||||
|
fn process(rx: Receiver<String>) -> (HashMap<usize,usize>,HashMap<usize,usize>) {
|
||||||
|
let mut hm_min: HashMap<usize,usize> = HashMap::new();
|
||||||
|
let mut hm_max: HashMap<usize,usize> = HashMap::new();
|
||||||
|
while let Ok(line) = rx.recv() {
|
||||||
|
let line: Vec<usize> = line.split(",").map(|s| s.parse::<usize>().unwrap()).collect();
|
||||||
|
let id=line[0];
|
||||||
|
let mut depth=line[1];
|
||||||
|
hm_min.entry(id).and_modify(|e| {
|
||||||
|
*e=*e.min(&mut depth);
|
||||||
|
}).or_insert(depth);
|
||||||
|
hm_max.entry(id).and_modify(|e| {
|
||||||
|
*e=*e.max(&mut depth);
|
||||||
|
}).or_insert(depth);
|
||||||
|
}
|
||||||
|
(hm_min,hm_max)
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let (tx,rx) = unbounded();
|
||||||
|
let mut threads: Vec<_> = (0..8).map(|_| {
|
||||||
|
let rx=rx.clone();
|
||||||
|
thread::spawn(|| {
|
||||||
|
process(rx)
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
let fh = BufReader::new(File::open(std::env::args().nth(1).unwrap()).unwrap());
|
||||||
|
fh.lines().flatten().for_each(|line| {
|
||||||
|
tx.send(line).unwrap();
|
||||||
|
});
|
||||||
|
drop(tx);
|
||||||
|
let mut hm_min: HashMap<usize,usize> = HashMap::new();
|
||||||
|
let mut hm_max: HashMap<usize,usize> = HashMap::new();
|
||||||
|
for thread in threads.drain(..) {
|
||||||
|
let (min,max)=thread.join().unwrap();
|
||||||
|
println!("Thread: {:?}",(min.len(),max.len()));
|
||||||
|
for (id,depth) in min {
|
||||||
|
hm_min.entry(id).and_modify(|e| {
|
||||||
|
*e=(*e).min(depth);
|
||||||
|
}).or_insert(depth);
|
||||||
|
}
|
||||||
|
for (id,depth) in max {
|
||||||
|
hm_max.entry(id).and_modify(|e| {
|
||||||
|
*e=(*e).max(depth);
|
||||||
|
}).or_insert(depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Final: {:?}",(hm_min.len(),hm_max.len()));
|
||||||
|
let mut fh_max = BufWriter::new(File::create("route_log_max.txt").unwrap());
|
||||||
|
for (id,depth) in hm_max {
|
||||||
|
write!(fh_max,"{},{}\n",id,depth).unwrap();
|
||||||
|
}
|
||||||
|
let mut fh_min = BufWriter::new(File::create("route_log_min.txt").unwrap());
|
||||||
|
for (id,depth) in hm_min {
|
||||||
|
write!(fh_min,"{},{}\n",id,depth).unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import sys
|
||||||
|
import datashader as ds
|
||||||
|
import pandas as pd
|
||||||
|
import datashader.transfer_functions as tf
|
||||||
|
from datashader.utils import export_image
|
||||||
|
from datashader.transfer_functions import set_background
|
||||||
|
import subprocess as SP
|
||||||
|
import os
|
||||||
|
import itertools as ITT
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
print("Loading stars...")
|
||||||
|
stars = pd.read_csv("stars.csv", usecols=["id", "x", "z", "mult"], index_col=0)
|
||||||
|
stars.loc[stars.mult == 1.0, "mult"] = float("nan")
|
||||||
|
|
||||||
|
steps = int(sys.argv[1])
|
||||||
|
size = 1080
|
||||||
|
mode = "eq_hist"
|
||||||
|
|
||||||
|
cvs = ds.Canvas(plot_width=size, plot_height=size)
|
||||||
|
|
||||||
|
print("Plotting density")
|
||||||
|
density_agg = cvs.points(stars, "x", "z")
|
||||||
|
density = tf.shade(density_agg, cmap=["black", "white"], how=mode)
|
||||||
|
|
||||||
|
print("Plotting neutrons")
|
||||||
|
neutrons_agg = cvs.points(stars, "x", "z", agg=ds.count("mult"))
|
||||||
|
neutrons = tf.shade(neutrons_agg, cmap=["darkblue", "lightblue"], how=mode)
|
||||||
|
|
||||||
|
base = tf.stack(density, neutrons)
|
||||||
|
|
||||||
|
# ffplay = SP.Popen([
|
||||||
|
# "ffplay","-f","image2pipe","-"
|
||||||
|
# ],stdin=SP.PIPE,bufsize=0)
|
||||||
|
|
||||||
|
|
||||||
|
for rh_fn in ITT.chain.from_iterable(map(glob, sys.argv[2:])):
|
||||||
|
basename = os.path.splitext(os.path.split(rh_fn)[-1])[0]
|
||||||
|
filename = "img/{}_{}_{}.mkv".format(basename, size, mode)
|
||||||
|
ffmpeg = SP.Popen(
|
||||||
|
[
|
||||||
|
"ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-f",
|
||||||
|
"image2pipe",
|
||||||
|
"-i",
|
||||||
|
"-",
|
||||||
|
"-crf",
|
||||||
|
"17",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
filename,
|
||||||
|
],
|
||||||
|
stdin=SP.PIPE,
|
||||||
|
bufsize=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Loading", rh_fn)
|
||||||
|
route_hist = pd.read_csv(
|
||||||
|
rh_fn, names=["id", "d"], index_col=0, dtype={"d": int}, low_memory=False,
|
||||||
|
)
|
||||||
|
exp_span = [route_hist.d.min(), route_hist.d.max()]
|
||||||
|
stars["d"] = float("nan")
|
||||||
|
rng = range(route_hist.d.min(), route_hist.d.max() + 1, steps)
|
||||||
|
if steps == 0:
|
||||||
|
rng = [route_hist.d.max() + 1]
|
||||||
|
for n in rng:
|
||||||
|
stars['d'] = route_hist[route_hist.d < n] # slow
|
||||||
|
explored_agg = cvs.points(stars, "x", "z", agg=ds.mean("d")) # slow
|
||||||
|
explored = tf.shade(
|
||||||
|
explored_agg, cmap=["darkred", "lightpink"], how="linear", span=exp_span
|
||||||
|
)
|
||||||
|
img = set_background(tf.stack(base, explored), "black").to_pil()
|
||||||
|
img.save(ffmpeg.stdin, "png")
|
||||||
|
img.save("img/current.png")
|
||||||
|
ffmpeg.stdin.close()
|
||||||
|
ffmpeg.wait()
|
|
@ -0,0 +1,79 @@
|
||||||
|
import pandas as pd
|
||||||
|
import vaex as vx
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
from skimage import exposure
|
||||||
|
from skimage.util import img_as_ubyte
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib import cm
|
||||||
|
import sys
|
||||||
|
|
||||||
|
base_size = 1080, 1920
|
||||||
|
|
||||||
|
|
||||||
|
def scale_to(width=None, height=None):
|
||||||
|
isnone = (width is None, height is None)
|
||||||
|
ret = {
|
||||||
|
(False, False): lambda w, h: (w, h),
|
||||||
|
(True, True): lambda w, h: (width, height),
|
||||||
|
(False, True): lambda w, h: (width, width * (h / w)),
|
||||||
|
(True, False): lambda w, h: (height * (w / h), height),
|
||||||
|
}
|
||||||
|
return lambda *args: tuple(map(int, ret[isnone](*args)))
|
||||||
|
|
||||||
|
|
||||||
|
# xz -1 1
|
||||||
|
bining = {
|
||||||
|
("zx", -1, 1): scale_to(width=base_size[0]), # main view, top down
|
||||||
|
# ('yx',1,1): lambda size,w,h: (size,int(size*(w/h))), #
|
||||||
|
# ('zy',-1,1): lambda size,w,h: (int(size*(h/w)),size), #
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Loading stars.csv")
|
||||||
|
stars = pd.read_csv(
|
||||||
|
"stars.csv",
|
||||||
|
names=["id", "name", "num_bodies", "has_scoopable", "mult", "x", "y", "z"],
|
||||||
|
usecols=["id", "num_bodies", "x", "y", "z", "mult"],
|
||||||
|
index_col=0,
|
||||||
|
)
|
||||||
|
stars = vx.from_pandas(stars, copy_index=False)
|
||||||
|
|
||||||
|
filename = "heuristic.png"
|
||||||
|
fnt = ImageFont.truetype(r"FiraCode-Regular", 40)
|
||||||
|
|
||||||
|
for (binby_key, m1, m2), calcshape in bining.items():
|
||||||
|
binby = [m1 * stars[binby_key[0]], m2 * stars[binby_key[1]]]
|
||||||
|
|
||||||
|
mm = [binby[0].minmax(), binby[1].minmax()]
|
||||||
|
|
||||||
|
w, h = [mm[0][1] - mm[0][0], mm[1][1] - mm[1][0]]
|
||||||
|
shape = calcshape(w, h)
|
||||||
|
hm_all = stars.sum("num_bodies", binby=binby, shape=shape, limits="minmax")
|
||||||
|
hm_all_mask = hm_all != 0
|
||||||
|
hm_all = exposure.equalize_hist(hm_all)
|
||||||
|
hm_all -= hm_all.min()
|
||||||
|
hm_all /= hm_all.max()
|
||||||
|
|
||||||
|
hm_boost = stars.sum(
|
||||||
|
"astype(mult>1.0,'int')", binby=binby, shape=shape, limits="minmax"
|
||||||
|
)
|
||||||
|
hm_boost_mask = hm_boost != 0
|
||||||
|
|
||||||
|
hm_boost = exposure.equalize_hist(hm_boost)
|
||||||
|
hm_boost -= hm_boost.min()
|
||||||
|
hm_boost /= hm_boost.max()
|
||||||
|
|
||||||
|
# R = cm.Reds_r()
|
||||||
|
G = cm.Greens_r(hm_all)
|
||||||
|
B = cm.Blues_r(hm_boost)
|
||||||
|
|
||||||
|
img = np.zeros((base_size[0], base_size[1], 4))
|
||||||
|
img[:, :, :] = 0.0
|
||||||
|
img[:, :, 3] = 1.0
|
||||||
|
canvas = img[: shape[0], : shape[1], :]
|
||||||
|
canvas[hm_all_mask] = G[hm_all_mask]
|
||||||
|
canvas[hm_boost_mask] = B[hm_boost_mask]
|
||||||
|
pil_img = Image.fromarray(img_as_ubyte(img))
|
||||||
|
draw = ImageDraw.Draw(pil_img)
|
||||||
|
messages = ["Hello World"]
|
||||||
|
draw.multiline_text((shape[0], 0), "\n".join(messages), font=fnt)
|
||||||
|
pil_img.save(filename)
|
|
@ -0,0 +1,208 @@
|
||||||
|
import pandas as pd
|
||||||
|
import vaex as vx
|
||||||
|
import json
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
from skimage import exposure
|
||||||
|
from skimage.io import imsave
|
||||||
|
from skimage.util import img_as_ubyte
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib import cm
|
||||||
|
import subprocess as SP
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
from datetime import timedelta
|
||||||
|
import itertools as ITT
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
base_size = 1080, 1920
|
||||||
|
steps = 1
|
||||||
|
framerate = 25
|
||||||
|
|
||||||
|
rh_fn = sys.argv[1]
|
||||||
|
|
||||||
|
|
||||||
|
def scale_to(width=None, height=None):
|
||||||
|
isnone = (width is None, height is None)
|
||||||
|
ret = {
|
||||||
|
(False, False): lambda w, h: (w, h),
|
||||||
|
(True, True): lambda w, h: (width, height),
|
||||||
|
(False, True): lambda w, h: (width, width * (h / w)),
|
||||||
|
(True, False): lambda w, h: (height * (w / h), height),
|
||||||
|
}
|
||||||
|
return lambda *args: tuple(map(int, ret[isnone](*args)))
|
||||||
|
|
||||||
|
|
||||||
|
# xz -1 1
|
||||||
|
bining = {
|
||||||
|
("zx", -1, 1): scale_to(width=base_size[0]), # main view, top down
|
||||||
|
# ('yx',1,1): lambda size,w,h: (size,int(size*(w/h))), #
|
||||||
|
# ('zy',-1,1): lambda size,w,h: (int(size*(h/w)),size), #
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_depth(stars, rh_fn):
|
||||||
|
print("Loading", rh_fn, flush=True, end=" ")
|
||||||
|
route_hist = pd.read_csv(
|
||||||
|
rh_fn,
|
||||||
|
names=["id", "depth"],
|
||||||
|
index_col=0,
|
||||||
|
dtype={"depth": int},
|
||||||
|
low_memory=False,
|
||||||
|
)
|
||||||
|
print("OK")
|
||||||
|
print("Converting to pandas dataframe", flush=True, end=" ")
|
||||||
|
stars = stars.to_pandas_df()
|
||||||
|
gc.collect()
|
||||||
|
print("OK")
|
||||||
|
print("Applying depth", flush=True, end=" ")
|
||||||
|
stars["depth"] = float("nan")
|
||||||
|
print("...",flush=True,end=" ")
|
||||||
|
stars["depth"] = route_hist.depth + 1.0
|
||||||
|
print("OK")
|
||||||
|
print("Converting to vaex dataframe", flush=True, end=" ")
|
||||||
|
stars = vx.from_pandas(stars, copy_index=False)
|
||||||
|
gc.collect()
|
||||||
|
print("OK")
|
||||||
|
return stars, route_hist.depth.max()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)]
|
||||||
|
pub struct System {
|
||||||
|
/// Unique System id
|
||||||
|
pub id: u32,
|
||||||
|
/// Star system
|
||||||
|
pub name: String,
|
||||||
|
/// Number of bodies
|
||||||
|
pub num_bodies: u8,
|
||||||
|
/// Does the system have a scoopable star?
|
||||||
|
pub has_scoopable: bool,
|
||||||
|
/// Jump range multiplier (1.5 for white dwarfs, 4.0 for neutron stars, 1.0 otherwise)
|
||||||
|
pub mult: f32,
|
||||||
|
/// Position
|
||||||
|
pub pos: [f32; 3],
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Loading stars.csv")
|
||||||
|
stars = pd.read_csv(
|
||||||
|
"stars.csv",
|
||||||
|
names=["id", "name", "num_bodies", "has_scoopable", "mult", "x", "y", "z"],
|
||||||
|
usecols=["id", "num_bodies", "x", "y", "z", "mult"],
|
||||||
|
index_col=0,
|
||||||
|
)
|
||||||
|
stars = vx.from_pandas(stars, copy_index=False)
|
||||||
|
|
||||||
|
|
||||||
|
def render(stars, rh_fn):
|
||||||
|
print("Rendering")
|
||||||
|
json_file = os.path.splitext(rh_fn)[0] + ".json"
|
||||||
|
if os.path.isfile(json_file):
|
||||||
|
with open(json_file) as fh:
|
||||||
|
route_info = json.load(fh)
|
||||||
|
route_len = len(route_info["route"])
|
||||||
|
time_taken = str(timedelta(seconds=route_info["dt"]))
|
||||||
|
route_rate = route_len / route_info["dt"]
|
||||||
|
else:
|
||||||
|
time_taken = "N/A"
|
||||||
|
route_len = 0
|
||||||
|
route_rate = 0
|
||||||
|
route_info = {"dt": -1.0}
|
||||||
|
stars, d_max = apply_depth(stars, rh_fn)
|
||||||
|
basename = os.path.splitext(os.path.split(rh_fn)[-1])[0]
|
||||||
|
filename = "img/{}.mkv".format(basename)
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return
|
||||||
|
ffmpeg = SP.Popen(
|
||||||
|
[
|
||||||
|
"ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-f",
|
||||||
|
"image2pipe",
|
||||||
|
"-probesize",
|
||||||
|
"128M",
|
||||||
|
"-i",
|
||||||
|
"-",
|
||||||
|
"-crf",
|
||||||
|
"17",
|
||||||
|
"-preset",
|
||||||
|
"veryslow",
|
||||||
|
"-r",
|
||||||
|
str(framerate),
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
filename,
|
||||||
|
],
|
||||||
|
stdin=SP.PIPE,
|
||||||
|
bufsize=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
total = stars.length()
|
||||||
|
fnt = ImageFont.truetype(r"FiraCode-Regular", 40)
|
||||||
|
|
||||||
|
for (binby_key, m1, m2), calcshape in bining.items():
|
||||||
|
binby = [m1 * stars[binby_key[0]], m2 * stars[binby_key[1]]]
|
||||||
|
|
||||||
|
mm = [binby[0].minmax(), binby[1].minmax()]
|
||||||
|
|
||||||
|
w, h = [mm[0][1] - mm[0][0], mm[1][1] - mm[1][0]]
|
||||||
|
shape = calcshape(w, h)
|
||||||
|
hm_all = stars.sum("num_bodies", binby=binby, shape=shape, limits="minmax")
|
||||||
|
hm_all_mask = hm_all != 0
|
||||||
|
hm_all = exposure.equalize_hist(hm_all)
|
||||||
|
hm_all -= hm_all.min()
|
||||||
|
hm_all /= hm_all.max()
|
||||||
|
|
||||||
|
hm_boost = stars.sum(
|
||||||
|
"astype(mult>1.0,'int')", binby=binby, shape=shape, limits="minmax"
|
||||||
|
)
|
||||||
|
hm_boost_mask = hm_boost != 0
|
||||||
|
|
||||||
|
hm_boost = exposure.equalize_hist(hm_boost)
|
||||||
|
hm_boost -= hm_boost.min()
|
||||||
|
hm_boost /= hm_boost.max()
|
||||||
|
|
||||||
|
G = cm.Greens_r(hm_all)
|
||||||
|
B = cm.Blues_r(hm_boost)
|
||||||
|
hm_exp = stars.mean("depth", binby=binby, shape=shape, limits="minmax")
|
||||||
|
hm_exp[np.isnan(hm_exp)] = 0.0
|
||||||
|
|
||||||
|
hm_exp -= hm_exp.min()
|
||||||
|
hm_exp /= d_max
|
||||||
|
R = cm.Reds_r(hm_exp)
|
||||||
|
|
||||||
|
hm_exp_mask_base = hm_exp != 0.0
|
||||||
|
img = np.zeros((base_size[0], base_size[1], 4))
|
||||||
|
d_array = stars[~stars["depth"].isna()]["depth"].values
|
||||||
|
exploration_rate = (d_array <= d_max).sum() / route_info["dt"]
|
||||||
|
print("Total frames:",d_max)
|
||||||
|
for d in range(0, d_max, steps):
|
||||||
|
hm_exp_mask = np.logical_and(hm_exp_mask_base, hm_exp <= (d / d_max))
|
||||||
|
num_explored = (d_array <= d).sum()
|
||||||
|
img[:, :, :] = 0.0
|
||||||
|
img[:, :, 3] = 1.0
|
||||||
|
canvas = img[: shape[0], : shape[1], :]
|
||||||
|
canvas[hm_all_mask] = G[hm_all_mask]
|
||||||
|
canvas[hm_boost_mask] = B[hm_boost_mask]
|
||||||
|
canvas[hm_exp_mask] = R[hm_exp_mask]
|
||||||
|
pil_img = Image.fromarray(img_as_ubyte(img))
|
||||||
|
draw = ImageDraw.Draw(pil_img)
|
||||||
|
messages = [
|
||||||
|
"Filename: {}".format(basename),
|
||||||
|
"Total Stars: {:,}".format(total),
|
||||||
|
"Explored: {:,} ({:.2%})".format(num_explored, num_explored / total),
|
||||||
|
"Search Depth: {:,}/{:,}".format(d, route_len),
|
||||||
|
"Time: {}".format(time_taken),
|
||||||
|
"Rate: {:.3f} waypoints/s".format(route_rate),
|
||||||
|
"Exploration Rate: {:.3f} stars/s".format(exploration_rate),
|
||||||
|
]
|
||||||
|
draw.multiline_text((shape[0], 0), "\n".join(messages), font=fnt)
|
||||||
|
pil_img.save(ffmpeg.stdin, "bmp")
|
||||||
|
ffmpeg.stdin.close()
|
||||||
|
ffmpeg.wait()
|
||||||
|
|
||||||
|
|
||||||
|
for rh_fn in ITT.chain.from_iterable(map(glob, sys.argv[1:])):
|
||||||
|
render(stars, rh_fn)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"spellright.language": [
|
||||||
|
"de",
|
||||||
|
"en"
|
||||||
|
],
|
||||||
|
"spellright.documentTypes": [
|
||||||
|
"latex",
|
||||||
|
"plaintext",
|
||||||
|
"git-commit"
|
||||||
|
],
|
||||||
|
"discord.enabled": true,
|
||||||
|
"python.pythonPath": "..\\.nox\\devenv-3-8\\python.exe",
|
||||||
|
"jupyter.jupyterServerType": "remote",
|
||||||
|
"files.associations": {
|
||||||
|
"*.ksy": "yaml",
|
||||||
|
"*.vpy": "python",
|
||||||
|
"stat.h": "c"
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
121
rust/Cargo.toml
121
rust/Cargo.toml
|
@ -1,42 +1,79 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ed_lrr"
|
name = "ed_lrr"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
authors = [ "Daniel Seiller <earthnuker@gmail.com>",]
|
authors = ["Daniel Seiller <earthnuker@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://gitlab.com/Earthnuker/ed_lrr.git"
|
repository = "https://gitlab.com/Earthnuker/ed_lrr.git"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = [ "cdylib",]
|
crate-type = ["cdylib"]
|
||||||
name = "_ed_lrr"
|
name = "_ed_lrr"
|
||||||
|
|
||||||
[dependencies]
|
[profile.release]
|
||||||
csv = "1.1.3"
|
codegen-units = 1
|
||||||
humantime = "2.0.1"
|
opt-level = 3
|
||||||
permutohedron = "0.2.4"
|
debug = true
|
||||||
serde_json = "1.0.55"
|
lto = "fat"
|
||||||
fnv = "1.0.7"
|
|
||||||
bincode = "1.2.1"
|
|
||||||
sha3 = "0.9.0"
|
[dependencies]
|
||||||
byteorder = "1.3.4"
|
pyo3 = { version = "0.15.1", features = ["extension-module","eyre"] }
|
||||||
strsim = "0.10.0"
|
csv = "1.1.6"
|
||||||
rstar = "0.8.0"
|
humantime = "2.1.0"
|
||||||
crossbeam-channel = "0.4.2"
|
permutohedron = "0.2.4"
|
||||||
better-panic = "0.2.0"
|
serde_json = "1.0.74"
|
||||||
derivative = "2.1.1"
|
fnv = "1.0.7"
|
||||||
dict_derive = "0.2.0"
|
bincode = "1.3.3"
|
||||||
num_cpus = "1.13.0"
|
sha3 = "0.10.0"
|
||||||
regex = "1.3.9"
|
byteorder = "1.4.3"
|
||||||
chrono = "0.4.11"
|
rstar = "0.9.2"
|
||||||
|
crossbeam-channel = "0.5.2"
|
||||||
[dependencies.pyo3]
|
better-panic = "0.3.0"
|
||||||
git = "https://github.com/PyO3/pyo3"
|
derivative = "2.2.0"
|
||||||
features = [ "extension-module",]
|
dict_derive = "0.4.0"
|
||||||
|
regex = "1.5.4"
|
||||||
[dependencies.serde]
|
num_cpus = "1.13.1"
|
||||||
version = "1.0.112"
|
eddie = "0.4.2"
|
||||||
features = [ "derive",]
|
thiserror = "1.0.30"
|
||||||
|
pyo3-log = "0.5.0"
|
||||||
[profile.release]
|
log = "0.4.14"
|
||||||
codegen-units = 1
|
flate2 = "1.0.22"
|
||||||
lto = true
|
eval = "0.4.3"
|
||||||
|
pythonize = "0.15.0"
|
||||||
|
itertools = "0.10.3"
|
||||||
|
intmap = "0.7.1"
|
||||||
|
diff-struct = "0.4.1"
|
||||||
|
rustc-hash = "1.1.0"
|
||||||
|
stats_alloc = "0.1.8"
|
||||||
|
|
||||||
|
tracing = { version = "0.1.29", optional = true }
|
||||||
|
tracing-subscriber = { version = "0.3.5", optional = true }
|
||||||
|
tracing-tracy = { version = "0.8.0", optional = true }
|
||||||
|
tracing-unwrap = { version = "0.9.2", optional = true }
|
||||||
|
tracy-client = { version = "0.12.6", optional = true }
|
||||||
|
tracing-chrome = "0.4.0"
|
||||||
|
rand = "0.8.4"
|
||||||
|
eyre = "0.6.6"
|
||||||
|
memmap = "0.7.0"
|
||||||
|
csv-core = "0.1.10"
|
||||||
|
postcard = { version = "0.7.3", features = ["alloc"] }
|
||||||
|
nohash-hasher = "0.2.0"
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
profiling = ["tracing","tracing-subscriber","tracing-tracy","tracing-unwrap","tracy-client"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = { version = "0.3.5", features = ["real_blackbox"] }
|
||||||
|
rand = "0.8.4"
|
||||||
|
rand_distr = "0.4.2"
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0.133"
|
||||||
|
features = ["derive"]
|
||||||
|
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "dot_bench"
|
||||||
|
harness = false
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import ujson
|
||||||
|
from glob import glob
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
route_info = {}
|
||||||
|
for log in glob("../logs/route_log*.json"):
|
||||||
|
name = log.split("route_log_")[1].rsplit(".", 1)[0]
|
||||||
|
data = ujson.load(open(log))
|
||||||
|
dt = data["dt"]
|
||||||
|
route_len = len(data["route"])
|
||||||
|
if route_len:
|
||||||
|
route_info[name] = (dt, route_len)
|
||||||
|
dt, route_len = route_info["beam_0"] # BFS as baseline
|
||||||
|
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for name, (dt_o, l_o) in sorted(route_info.items(), key=lambda v: v[1][0] / v[1][1]):
|
||||||
|
dt_s = str(timedelta(seconds=round(dt_o, 2))).rstrip("0")
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"time": "{} ({:.2f}x)".format(dt_s, dt / dt_o),
|
||||||
|
"length": "{} (+{:.2%})".format(l_o, (l_o / route_len) - 1),
|
||||||
|
"time/hop": "{:.2} s".format(dt_o / l_o),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
print(df.to_markdown(index=False))
|
|
@ -0,0 +1,142 @@
|
||||||
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
|
use rand::Rng;
|
||||||
|
use rand_distr::StandardNormal;
|
||||||
|
|
||||||
|
fn rand_v3() -> [f32; 3] {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
[
|
||||||
|
rng.sample(StandardNormal),
|
||||||
|
rng.sample(StandardNormal),
|
||||||
|
rng.sample(StandardNormal),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arand() -> f32 {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
rng.sample::<f32, _>(StandardNormal).abs()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn veclen(v: &[f32; 3]) -> f32 {
|
||||||
|
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
let dx = p1[0] - p2[0];
|
||||||
|
let dy = p1[1] - p2[1];
|
||||||
|
let dz = p1[2] - p2[2];
|
||||||
|
|
||||||
|
dx * dx + dy * dy + dz * dz
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
dist2(p1, p2).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dot product (cosine of angle) between two 3D vectors
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot_vec_dist(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let z: [f32; 3] = [0.0; 3];
|
||||||
|
let lm = dist(u, &z) * dist(v, &z);
|
||||||
|
((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dot product (cosine of angle) between two 3D vectors
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot_vec_len(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let lm = veclen(u) * veclen(v);
|
||||||
|
((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot_iter(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let mut l_u = 0.0;
|
||||||
|
let mut l_v = 0.0;
|
||||||
|
let mut l_s = 0.0;
|
||||||
|
for (u, v) in u.iter().zip(v.iter()) {
|
||||||
|
l_s += u * v;
|
||||||
|
l_u += u * u;
|
||||||
|
l_v += v * v;
|
||||||
|
}
|
||||||
|
l_s / (l_u * l_v).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_ndot(c: &mut Criterion) {
|
||||||
|
let mut g = c.benchmark_group("ndot");
|
||||||
|
g.bench_function("vec_dist", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), rand_v3()),
|
||||||
|
|(v1, v2)| ndot_vec_dist(&v1, &v2),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
g.bench_function("vec_len", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), rand_v3()),
|
||||||
|
|(v1, v2)| ndot_vec_len(&v1, &v2),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
g.bench_function("iter", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), rand_v3()),
|
||||||
|
|(v1, v2)| ndot_iter(&v1, &v2),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
g.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_dist(c: &mut Criterion) {
|
||||||
|
let mut g = c.benchmark_group("dist");
|
||||||
|
g.bench_function("dist", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), rand_v3()),
|
||||||
|
|(v1, v2)| dist(&v1, &v2),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
g.bench_function("dist2", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), rand_v3()),
|
||||||
|
|(v1, v2)| dist2(&v1, &v2),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
g.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vsub(a: &[f32; 3], b: &[f32; 3]) -> [f32; 3] {
|
||||||
|
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn h_old(node: &[f32; 3], m: f32, goal: &[f32; 3], r: f32) -> f32 {
|
||||||
|
(dist(node, goal) - (r * m)).max(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn h_new(node: &[f32; 3], next: &[f32; 3], goal: &[f32; 3]) -> f32 {
|
||||||
|
-ndot_iter(&vsub(node, goal), &vsub(node, next)).acos()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_new_heur(c: &mut Criterion) {
|
||||||
|
c.bench_function("old_heuristic", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), arand(), rand_v3(), arand()),
|
||||||
|
|(node, m, goal, range)| h_old(&node, m, &goal, range),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("new_heuristic", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| (rand_v3(), rand_v3(), rand_v3()),
|
||||||
|
|(v1, v2, v3)| h_new(&v1, &v2, &v3),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, bench_ndot, bench_dist, bench_new_heur);
|
||||||
|
criterion_main!(benches);
|
2203
rust/ch.txt
2203
rust/ch.txt
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,46 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(loglevel="INFO"):
|
||||||
|
import logging
|
||||||
|
import coloredlogs
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
|
||||||
|
|
||||||
|
class DeltaTimeFormatter(coloredlogs.ColoredFormatter):
|
||||||
|
def format(self, record):
|
||||||
|
seconds = record.relativeCreated / 1000
|
||||||
|
duration = datetime.timedelta(seconds=seconds)
|
||||||
|
record.delta = str(duration)
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
coloredlogs.ColoredFormatter = DeltaTimeFormatter
|
||||||
|
logfmt = " | ".join(
|
||||||
|
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
|
||||||
|
)
|
||||||
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
|
if not isinstance(numeric_level, int):
|
||||||
|
raise ValueError("Invalid log level: %s" % loglevel)
|
||||||
|
coloredlogs.install(level=numeric_level, fmt=logfmt)
|
||||||
|
|
||||||
|
|
||||||
|
setup_logging()
|
||||||
|
_ed_lrr = __import__("_ed_lrr")
|
||||||
|
|
||||||
|
r = _ed_lrr.PyRouter(None)
|
||||||
|
r.load("stars.csv")
|
||||||
|
# r.run_bfs(48)
|
||||||
|
r.test(48)
|
||||||
|
exit()
|
||||||
|
|
||||||
|
_ed_lrr.PyRouter.preprocess_galaxy("E:/EDSM/galaxy.json.gz", "E:/EDSM/stars.csv")
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
r = _ed_lrr.PyRouter(print)
|
||||||
|
r.load("../stars.csv")
|
||||||
|
systems = r.resolve_systems((0, 0, 0), "Colonia", 18627)
|
||||||
|
print(systems)
|
||||||
|
print(systems[0, 0, 0])
|
|
@ -0,0 +1,221 @@
|
||||||
|
import subprocess as SP
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(loglevel="INFO"):
|
||||||
|
import logging
|
||||||
|
import coloredlogs
|
||||||
|
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
|
||||||
|
|
||||||
|
class DeltaTimeFormatter(coloredlogs.ColoredFormatter):
|
||||||
|
def format(self, record):
|
||||||
|
seconds = record.relativeCreated / 1000
|
||||||
|
duration = timedelta(seconds=seconds)
|
||||||
|
record.delta = str(duration)
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
coloredlogs.ColoredFormatter = DeltaTimeFormatter
|
||||||
|
logfmt = " | ".join(
|
||||||
|
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
|
||||||
|
)
|
||||||
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
|
if not isinstance(numeric_level, int):
|
||||||
|
raise ValueError("Invalid log level: %s" % loglevel)
|
||||||
|
coloredlogs.install(level=numeric_level, fmt=logfmt)
|
||||||
|
|
||||||
|
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
JUMP_RANGE = 48
|
||||||
|
globals().setdefault("__file__", r"D:\devel\rust\ed_lrr_gui\rust\run_test.py")
|
||||||
|
dirname = os.path.dirname(__file__) or "."
|
||||||
|
os.chdir(dirname)
|
||||||
|
t_start = datetime.now()
|
||||||
|
os.environ["PYO3_PYTHON"] = sys.executable
|
||||||
|
if "--clean" in sys.argv[1:]:
|
||||||
|
SP.check_call(["cargo","clean"])
|
||||||
|
if "--build" in sys.argv[1:]:
|
||||||
|
SP.check_call(["cargo","lcheck"])
|
||||||
|
SP.check_call([sys.executable, "-m", "pip", "install", "-e", ".."])
|
||||||
|
print("Build+Install took:", datetime.now() - t_start)
|
||||||
|
|
||||||
|
sys.path.append("..")
|
||||||
|
_ed_lrr = __import__("_ed_lrr")
|
||||||
|
|
||||||
|
def callback(state):
|
||||||
|
print(state)
|
||||||
|
print(_ed_lrr)
|
||||||
|
r = _ed_lrr.PyRouter(callback)
|
||||||
|
r.load("../stars_2.csv", immediate=False)
|
||||||
|
print(r)
|
||||||
|
r.str_tree_test()
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
r = _ed_lrr.PyRouter(callback)
|
||||||
|
r.load("../stars.csv", immediate=False)
|
||||||
|
print(r.resolve("Sol","Saggitarius A","Colonia","Merope"))
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
ships = _ed_lrr.PyShip.from_journal()
|
||||||
|
r = _ed_lrr.PyRouter(callback)
|
||||||
|
r.load("../stars.csv", immediate=False)
|
||||||
|
|
||||||
|
def func(*args,**kwargs):
|
||||||
|
print(kwargs)
|
||||||
|
return 12
|
||||||
|
|
||||||
|
r.precompute_neighbors(JUMP_RANGE)
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# start, end = "Sol", "Colonia" # # 135 in 22m 36s 664ms 268us 800ns
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
{'mode': 'BFS_serial', 'system': 'Nuwo OP-N c23-1', 'from': 'Sol', 'to': 'Beagle Point', 'depth': 492, 'queue_size': 1602, 'd_rem': 2456.31298828125, 'd_total': 65279.3515625, 'prc_done': 96.23722839355469, 'n_seen': 17366296, 'prc_seen': 26.25494384765625}
|
||||||
|
[0:43:19.715858] INFO | _ed_lrr.route:src\route.rs:2402 | Took: 34m 38s 40ms 256us 500ns
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
{'mode': 'BFS_serial', 'system': 'Syriae Thaa DN-B d13-2', 'from': 'Sol', 'to': 'Beagle Point', 'depth': 521, 'queue_size': 2311, 'd_rem': 492.8757019042969, 'd_total': 65279.3515625, 'prc_done': 99.2449722290039, 'n_seen': 19566797, 'prc_seen': 29.58173179626465}
|
||||||
|
[0:53:28.431326] INFO | _ed_lrr.route:src\route.rs:2402 | Took: 48m 34s 958ms 326us 300ns
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
[0:36:02.738233] INFO | _ed_lrr.route:src\route.rs:2404 | Took: 27m 6s 216ms 161us 100ns
|
||||||
|
Optimal route: 534
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
Sol, Colonia
|
||||||
|
Took: 30m 22s 63ms 818us
|
||||||
|
Allocs: 26622742
|
||||||
|
Reallocs: 45809664
|
||||||
|
Deallocs: 26622600
|
||||||
|
Optimal route: 135
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
Sol, Ix
|
||||||
|
Took: 1s 995ms 115us 100ns
|
||||||
|
Allocs: 17058
|
||||||
|
Reallocs: 32042
|
||||||
|
Deallocs: 17047
|
||||||
|
Optimal route: 4
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Stats { allocations: 23257531, deallocations: 23257389, reallocations: 42747420, bytes_allocated: 179667997387, bytes_deallocated: 179667853217, bytes_reallocated: 151573742821 }
|
||||||
|
|
||||||
|
start, end = "Sol", "Colonia"
|
||||||
|
|
||||||
|
systems = r.resolve(start, end)
|
||||||
|
sys_ids = {k: v["id"] for k, v in systems.items()}
|
||||||
|
|
||||||
|
cfg = {}
|
||||||
|
cfg["mode"] = "incremental_broadening"
|
||||||
|
# input("{}>".format(os.getpid()))
|
||||||
|
route = r.route([sys_ids[start], sys_ids[end]], JUMP_RANGE, cfg, 0)
|
||||||
|
print("Optimal route:", len(route))
|
||||||
|
|
||||||
|
# cfg["mode"] = "beam_stack"
|
||||||
|
# route = r.route([sys_ids[start], sys_ids[end]], JUMP_RANGE, cfg, 0)
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# bw_l = [
|
||||||
|
# 1,
|
||||||
|
# 2,
|
||||||
|
# 4,
|
||||||
|
# 8,
|
||||||
|
# 16,
|
||||||
|
# 32,
|
||||||
|
# 64,
|
||||||
|
# 128,
|
||||||
|
# 256,
|
||||||
|
# 512,
|
||||||
|
# 1024,
|
||||||
|
# 2048,
|
||||||
|
# 4096,
|
||||||
|
# 8192,
|
||||||
|
# 16384,
|
||||||
|
# 0.1,
|
||||||
|
# 0.25,
|
||||||
|
# 0.5,
|
||||||
|
# 0.75,
|
||||||
|
# 0.9,
|
||||||
|
# 0.99,
|
||||||
|
# 0,
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# cfg = {
|
||||||
|
# "mode": "bfs",
|
||||||
|
# "greedyness": 0,
|
||||||
|
# }
|
||||||
|
|
||||||
|
# bw_l = [0]
|
||||||
|
|
||||||
|
# for bw in bw_l:
|
||||||
|
# ofn = "../logs/route_log_beam_{}.txt".format(bw)
|
||||||
|
# # if os.path.isfile(ofn):
|
||||||
|
# # continue
|
||||||
|
# print(ofn)
|
||||||
|
# t_start = datetime.today()
|
||||||
|
# try:
|
||||||
|
# if isinstance(bw, int):
|
||||||
|
# cfg["beam_width"] = {"absolute": bw}
|
||||||
|
# else:
|
||||||
|
# cfg["beam_width"] = {"fraction": bw}
|
||||||
|
# route = r.route([sys_ids["Sol"], sys_ids["Beagle Point"]], JUMP_RANGE, cfg, 8)
|
||||||
|
# print(route)
|
||||||
|
# except Exception as e:
|
||||||
|
# print("Error:", e)
|
||||||
|
# route = []
|
||||||
|
# dt = (datetime.today() - t_start).total_seconds()
|
||||||
|
# shutil.copy("route_log.txt", ofn)
|
||||||
|
# with open(ofn.replace(".txt", ".json"), "w") as of:
|
||||||
|
# json.dump({"route": route, "dt": dt}, of)
|
||||||
|
|
||||||
|
# g_l = [1.0, 0.99, 0.9, 0.75, 0.5, 0.25]
|
||||||
|
|
||||||
|
# g_l.clear()
|
||||||
|
# cfg["beam_width"] = 0
|
||||||
|
# for g in g_l:
|
||||||
|
# ofn = "../logs/route_log_g_{}.txt".format(g)
|
||||||
|
# if os.path.isfile(ofn):
|
||||||
|
# continue
|
||||||
|
# print(ofn)
|
||||||
|
# t_start = datetime.today()
|
||||||
|
# try:
|
||||||
|
# cfg["greedyness"] = g
|
||||||
|
# route = r.route([sys_ids["Sol"], sys_ids["Beagle Point"]], JUMP_RANGE, cfg)
|
||||||
|
# except Exception as e:
|
||||||
|
# print("Error:", e)
|
||||||
|
# route = []
|
||||||
|
# dt = (datetime.today() - t_start).total_seconds()
|
||||||
|
# shutil.copy("route_log.txt", ofn)
|
||||||
|
# with open(ofn.replace(".txt", ".json"), "w") as of:
|
||||||
|
# json.dump({"route": route, "dt": dt}, of)
|
||||||
|
# r.unload()
|
||||||
|
# exit()
|
||||||
|
# os.chdir("..")
|
||||||
|
# SP.check_call(
|
||||||
|
# [
|
||||||
|
# "conda",
|
||||||
|
# "run",
|
||||||
|
# "-n",
|
||||||
|
# "base",
|
||||||
|
# "--no-capture-output",
|
||||||
|
# "python",
|
||||||
|
# "plot_heatmap_vaex.py",
|
||||||
|
# "logs/route_log_*.txt",
|
||||||
|
# ],
|
||||||
|
# shell=True,
|
||||||
|
# )
|
|
@ -1,13 +1,257 @@
|
||||||
use crate::route::Router;
|
//! # Common utlility functions
|
||||||
|
use crate::route::{LineCache, Router};
|
||||||
|
use bincode::Options;
|
||||||
|
use crossbeam_channel::{bounded, Receiver};
|
||||||
|
use csv::ByteRecord;
|
||||||
use dict_derive::IntoPyObject;
|
use dict_derive::IntoPyObject;
|
||||||
use pyo3::conversion::ToPyObject;
|
use eyre::Result;
|
||||||
|
use log::*;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyDict, PyTuple};
|
use pyo3::types::PyDict;
|
||||||
|
use pyo3::{conversion::ToPyObject, create_exception};
|
||||||
|
use pythonize::depythonize;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use sha3::{Digest, Sha3_256};
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||||
use std::path::PathBuf;
|
use std::hash::{Hash, Hasher, BuildHasherDefault};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::thread;
|
||||||
|
use std::{cmp::Ordering, cmp::Reverse, collections::BinaryHeap};
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufReader, BufWriter},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use nohash_hasher::NoHashHasher;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn heuristic(range: f32, node: &TreeNode, goal: &TreeNode) -> f32 {
|
||||||
|
// distance remaining after jumping from node towards goal
|
||||||
|
let a2 = dist2(&node.pos, &goal.pos);
|
||||||
|
let mult=node.get_mult();
|
||||||
|
let b2 = range * range * mult*mult;
|
||||||
|
return (a2 - b2).max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Min-heap priority queue using f32 as priority
|
||||||
|
pub struct MinFHeap<T: Ord>(pub BinaryHeap<(Reverse<F32>, T)>);
|
||||||
|
/// Max-heap priority queue using f32 as priority
|
||||||
|
pub struct MaxFHeap<T: Ord>(pub BinaryHeap<(F32, T)>);
|
||||||
|
|
||||||
|
impl<T: Ord> MaxFHeap<T> {
|
||||||
|
/// Create new, empty priority queue
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MaxFHeap(BinaryHeap::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// push value `item` with priority `w` into queue
|
||||||
|
pub fn push(&mut self, w: f32, item: T) {
|
||||||
|
self.0.push((F32(w), item))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return largest item and priority
|
||||||
|
pub fn pop(&mut self) -> Option<(f32, T)> {
|
||||||
|
self.0.pop().map(|(F32(w), item)| (w, item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> Default for MaxFHeap<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
return MaxFHeap(BinaryHeap::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> Deref for MaxFHeap<T> {
|
||||||
|
type Target = BinaryHeap<(F32, T)>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> DerefMut for MaxFHeap<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> MinFHeap<T> {
|
||||||
|
/// Create new, empty priority queue
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MinFHeap(BinaryHeap::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// push value `item` with priority `w` into queue
|
||||||
|
pub fn push(&mut self, w: f32, item: T) {
|
||||||
|
self.0.push((Reverse(F32(w)), item))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return smallest item and priority
|
||||||
|
pub fn pop(&mut self) -> Option<(f32, T)> {
|
||||||
|
self.0.pop().map(|(Reverse(F32(w)), item)| (w, item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> Default for MinFHeap<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
return MinFHeap(BinaryHeap::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> Deref for MinFHeap<T> {
|
||||||
|
type Target = BinaryHeap<(Reverse<F32>, T)>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> DerefMut for MinFHeap<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// ED LRR error type
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum EdLrrError {
|
||||||
|
#[error("failed to compute route from {from:?} to {to:?}: {reason}")]
|
||||||
|
RouteError {
|
||||||
|
from: Option<System>,
|
||||||
|
to: Option<System>,
|
||||||
|
reason: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to find system matching {0:?}")]
|
||||||
|
ResolveError(String),
|
||||||
|
|
||||||
|
#[error("runtime error: {0:?}")]
|
||||||
|
RuntimeError(String),
|
||||||
|
|
||||||
|
#[error("Failed to process {0}")]
|
||||||
|
ProcessingError(PathBuf),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
EvalError(#[from] eval::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
CSVError(#[from] csv::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
IOError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
BincodeError(#[from] Box<bincode::ErrorKind>),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
PyError(#[from] pyo3::PyErr),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Error(#[from] eyre::Error),
|
||||||
|
|
||||||
|
#[error("unknown error")]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod py_exceptions {
|
||||||
|
use super::*;
|
||||||
|
pub use pyo3::exceptions::*;
|
||||||
|
create_exception!(_ed_lrr, RouteError, PyException);
|
||||||
|
create_exception!(_ed_lrr, ResolveError, PyException);
|
||||||
|
create_exception!(_ed_lrr, EdLrrException, PyException);
|
||||||
|
create_exception!(_ed_lrr, ProcessingError, PyException);
|
||||||
|
create_exception!(_ed_lrr, FileFormatError, PyException);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for EdLrrError {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self::RuntimeError(s.to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<String> for EdLrrError {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::RuntimeError(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<EdLrrError> for PyErr {
|
||||||
|
fn from(err: EdLrrError) -> PyErr {
|
||||||
|
match err {
|
||||||
|
EdLrrError::PyError(e) => e,
|
||||||
|
EdLrrError::BincodeError(..) => {
|
||||||
|
py_exceptions::FileFormatError::new_err(err.to_string())
|
||||||
|
}
|
||||||
|
EdLrrError::RouteError { .. } => py_exceptions::RouteError::new_err(err.to_string()),
|
||||||
|
EdLrrError::RuntimeError(msg) => py_exceptions::PyRuntimeError::new_err(msg),
|
||||||
|
EdLrrError::ResolveError(..) => py_exceptions::PyRuntimeError::new_err(err.to_string()),
|
||||||
|
EdLrrError::EvalError(err) => py_exceptions::PyRuntimeError::new_err(err.to_string()),
|
||||||
|
EdLrrError::CSVError(err) => py_exceptions::PyRuntimeError::new_err(err.to_string()),
|
||||||
|
EdLrrError::IOError(err) => py_exceptions::PyIOError::new_err(err.to_string()),
|
||||||
|
EdLrrError::Error(err) => py_exceptions::EdLrrException::new_err(err.to_string()),
|
||||||
|
EdLrrError::ProcessingError(buf) => {
|
||||||
|
py_exceptions::ProcessingError::new_err(format!("{}", buf.display()))
|
||||||
|
}
|
||||||
|
EdLrrError::Unknown => {
|
||||||
|
py_exceptions::EdLrrException::new_err("Unknown error!".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type EdLrrResult<T> = Result<T, EdLrrError>;
|
||||||
|
|
||||||
|
/// f32 compare wrapper
|
||||||
|
pub fn fcmp(a: f32, b: f32) -> Ordering {
|
||||||
|
match (a, b) {
|
||||||
|
(x, y) if x.is_nan() && y.is_nan() => Ordering::Equal,
|
||||||
|
(x, _) if x.is_nan() => Ordering::Greater,
|
||||||
|
(_, y) if y.is_nan() => Ordering::Less,
|
||||||
|
(..) => a.partial_cmp(&b).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// f32 warpper type implementing `Eq` and `Ord`
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct F32(pub f32);
|
||||||
|
|
||||||
|
impl PartialEq for F32 {
|
||||||
|
fn eq(&self, other: &F32) -> bool {
|
||||||
|
fcmp(self.0, other.0) == std::cmp::Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for F32 {}
|
||||||
|
|
||||||
|
impl PartialOrd for F32 {
|
||||||
|
fn partial_cmp(&self, other: &F32) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(fcmp(self.0, other.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for F32 {
|
||||||
|
fn cmp(&self, other: &F32) -> std::cmp::Ordering {
|
||||||
|
fcmp(self.0, other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for F32 {
|
||||||
|
type Target = f32;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for F32 {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns additional jump range (in Ly) granted by specified class of Guardian FSD Booster
|
||||||
pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> {
|
pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> {
|
||||||
// Data from https://elite-dangerous.fandom.com/wiki/Guardian_Frame_Shift_Drive_Booster
|
// Data from https://elite-dangerous.fandom.com/wiki/Guardian_Frame_Shift_Drive_Booster
|
||||||
let ret = match class {
|
let ret = match class {
|
||||||
|
@ -22,6 +266,7 @@ pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> {
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns optimal mass and maximum fuel per jump for the given FSD rating and class as a hash map
|
||||||
pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, String> {
|
pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, String> {
|
||||||
let mut ret = HashMap::new();
|
let mut ret = HashMap::new();
|
||||||
// Data from https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Specifications
|
// Data from https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Specifications
|
||||||
|
@ -68,6 +313,7 @@ pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>,
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns jump range multiplier for the specified star type (4 for neutron stars, 1.5 for white dwarfs and 1.0 otherwise)
|
||||||
pub fn get_mult(star_type: &str) -> f32 {
|
pub fn get_mult(star_type: &str) -> f32 {
|
||||||
if star_type.contains("White Dwarf") {
|
if star_type.contains("White Dwarf") {
|
||||||
return 1.5;
|
return 1.5;
|
||||||
|
@ -78,133 +324,403 @@ pub fn get_mult(star_type: &str) -> f32 {
|
||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum BeamWidth {
|
||||||
|
Absolute(usize),
|
||||||
|
Fraction(f32),
|
||||||
|
Radius(f32),
|
||||||
|
Infinite,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for BeamWidth {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
BeamWidth::Absolute(n) => write!(f, "{}", n),
|
||||||
|
BeamWidth::Fraction(v) => write!(f, "{}%", (*v) * 100.0),
|
||||||
|
BeamWidth::Radius(r) => write!(f, "{} Ly", r),
|
||||||
|
BeamWidth::Infinite => write!(f, "Infinite"),
|
||||||
|
}?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BeamWidth {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Infinite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromPyObject<'_> for BeamWidth {
|
||||||
|
fn extract(ob: &PyAny) -> PyResult<Self> {
|
||||||
|
depythonize(ob).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BeamWidth {
|
||||||
|
pub fn is_set(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Fraction(f) => *f > 0.0,
|
||||||
|
Self::Absolute(n) => *n != 0,
|
||||||
|
Self::Radius(r) => *r > 0.0,
|
||||||
|
Self::Infinite => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_infinite(&self) -> bool {
|
||||||
|
matches!(self, Self::Infinite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute(&self, nodes: usize) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Fraction(f) => {
|
||||||
|
let w = (nodes as f32) * f.max(0.0).min(1.0);
|
||||||
|
return (w.ceil() as usize).max(1);
|
||||||
|
}
|
||||||
|
Self::Absolute(n) => *n,
|
||||||
|
Self::Radius(_) | Self::Infinite => nodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Represents an uresolved system to be searched for by name, id or position
|
||||||
|
#[derive(Debug, FromPyObject)]
|
||||||
pub enum SysEntry {
|
pub enum SysEntry {
|
||||||
ID(u32),
|
ID(u32),
|
||||||
Name(String),
|
Name(String),
|
||||||
|
Pos((f32, f32, f32)),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SysEntry {
|
impl ToPyObject for SysEntry {
|
||||||
pub fn parse(s: &str) -> Self {
|
fn to_object(&self, py: Python) -> PyObject {
|
||||||
if let Ok(n) = s.parse() {
|
match self {
|
||||||
SysEntry::ID(n)
|
Self::ID(id) => id.to_object(py),
|
||||||
} else {
|
Self::Name(name) => name.to_object(py),
|
||||||
SysEntry::Name(s.to_owned())
|
Self::Pos(pos) => pos.to_object(py),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_matches(
|
pub fn grid_stats(
|
||||||
path: &PathBuf,
|
path: &Path,
|
||||||
|
grid_size: f32,
|
||||||
|
) -> Result<BTreeMap<(i64, i64, i64), Vec<u32>>, String> {
|
||||||
|
let mut reader = match csv::ReaderBuilder::new().has_headers(false).from_path(path) {
|
||||||
|
Ok(rdr) => rdr,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let systems = reader.deserialize::<System>().map(Result::unwrap);
|
||||||
|
let mut ret: BTreeMap<(i64, i64, i64), Vec<u32>> = BTreeMap::new();
|
||||||
|
for sys in systems {
|
||||||
|
let k = (
|
||||||
|
((sys.pos[0] / grid_size).round() * grid_size) as i64,
|
||||||
|
((sys.pos[1] / grid_size).round() * grid_size) as i64,
|
||||||
|
((sys.pos[2] / grid_size).round() * grid_size) as i64,
|
||||||
|
);
|
||||||
|
ret.entry(k).or_default().push(sys.id);
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Node {
|
||||||
|
Start,
|
||||||
|
Goal,
|
||||||
|
ID(u32),
|
||||||
|
}
|
||||||
|
pub enum Weight {
|
||||||
|
Dist(Node),
|
||||||
|
Depth,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Weight {
|
||||||
|
fn eval(&self) -> f32 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Weights(Vec<(f32, Weight)>);
|
||||||
|
|
||||||
|
impl Weights {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
fn add(&mut self, w: f32, v: Weight) {
|
||||||
|
self.0.push((w, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval(&mut self) -> f32 {
|
||||||
|
self.0.iter().map(|(w, v)| w * v.eval()).sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
let dx = p1[0] - p2[0];
|
||||||
|
let dy = p1[1] - p2[1];
|
||||||
|
let dz = p1[2] - p2[2];
|
||||||
|
|
||||||
|
dx * dx + dy * dy + dz * dz
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
dist2(p1, p2).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn distm(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
let dx = (p1[0] - p2[0]).abs();
|
||||||
|
let dy = (p1[1] - p2[1]).abs();
|
||||||
|
let dz = (p1[2] - p2[2]).abs();
|
||||||
|
dx + dy + dz
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dot product (cosine of angle) between two 3D vectors
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let z: [f32; 3] = [0.0; 3];
|
||||||
|
let lm = dist(u, &z) * dist(v, &z);
|
||||||
|
(u[0] * v[0]) / lm + (u[1] * v[1]) / lm + (u[2] * v[2]) / lm
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fuzzy string matcher, use to resolve star system names
|
||||||
|
#[cfg_attr(feature = "profiling", tracing::instrument(skip(rx)))]
|
||||||
|
fn matcher(
|
||||||
|
rx: Receiver<ByteRecord>,
|
||||||
names: Vec<String>,
|
names: Vec<String>,
|
||||||
exact: bool,
|
exact: bool,
|
||||||
) -> Result<HashMap<String, (f64, Option<System>)>, String> {
|
) -> HashMap<String, (f64, Option<u32>)> {
|
||||||
let mut best: HashMap<String, (f64, Option<System>)> = HashMap::new();
|
let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new();
|
||||||
|
for name in &names {
|
||||||
|
best.insert(name.to_string(), (0.0, None));
|
||||||
|
}
|
||||||
|
let names_u8: Vec<(String, _)> = names.iter().map(|n| (n.clone(), n.as_bytes())).collect();
|
||||||
|
let sdist = eddie::slice::Levenshtein::new();
|
||||||
|
for sys in rx.into_iter() {
|
||||||
|
for (name, name_b) in &names_u8 {
|
||||||
|
if let Some(ent) = best.get_mut(name) {
|
||||||
|
if (ent.0 - 1.0).abs() < std::f64::EPSILON {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if exact && (&sys[1] == *name_b) {
|
||||||
|
let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap();
|
||||||
|
*ent = (1.0, Some(id));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let d = sdist.similarity(&sys[1], name_b);
|
||||||
|
if d > ent.0 {
|
||||||
|
let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap();
|
||||||
|
*ent = (d, Some(id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan through the csv file at `path` and return a hash map
|
||||||
|
/// mapping the strings from `names` to a tuple `(score, Option<system_id>)`.
|
||||||
|
/// Scoring matching uses the normalized Levenshtein distance where 1.0 is an exact match.
|
||||||
|
pub fn find_matches(
|
||||||
|
path: &Path,
|
||||||
|
names: Vec<String>,
|
||||||
|
exact: bool,
|
||||||
|
) -> Result<HashMap<String, (f64, Option<u32>)>, String> {
|
||||||
|
let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new();
|
||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
return Ok(best);
|
return Ok(best);
|
||||||
}
|
}
|
||||||
for name in &names {
|
for name in &names {
|
||||||
best.insert(name.to_string(), (0.0, None));
|
best.insert(name.to_string(), (0.0, None));
|
||||||
}
|
}
|
||||||
let mut reader = match csv::ReaderBuilder::new().from_path(path) {
|
|
||||||
|
let mut workers = Vec::new();
|
||||||
|
let ncpus = num_cpus::get();
|
||||||
|
let (tx, rx) = bounded(4096 * ncpus);
|
||||||
|
for _ in 0..ncpus {
|
||||||
|
let names = names.clone();
|
||||||
|
let rx = rx.clone();
|
||||||
|
let th = thread::spawn(move || matcher(rx, names, exact));
|
||||||
|
workers.push(th);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rdr = match csv::ReaderBuilder::new().has_headers(false).from_path(path) {
|
||||||
Ok(rdr) => rdr,
|
Ok(rdr) => rdr,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e));
|
return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let systems = reader.deserialize::<SystemSerde>();
|
let t_start = std::time::Instant::now();
|
||||||
for sys in systems {
|
let mut processed: usize = 0;
|
||||||
let sys = sys.unwrap();
|
for record in rdr.byte_records().flat_map(|v| v.ok()) {
|
||||||
for name in &names {
|
tx.send(record).unwrap();
|
||||||
|
processed += 1;
|
||||||
|
}
|
||||||
|
drop(tx);
|
||||||
|
while let Some(th) = workers.pop() {
|
||||||
|
for (name, (score, sys)) in th.join().unwrap().iter() {
|
||||||
best.entry(name.clone()).and_modify(|ent| {
|
best.entry(name.clone()).and_modify(|ent| {
|
||||||
if (exact) && (&sys.system == name) {
|
if score > &ent.0 {
|
||||||
*ent = (1.0, Some(sys.clone().build()))
|
*ent = (*score, *sys);
|
||||||
} else {
|
|
||||||
let d1 = strsim::normalized_levenshtein(&sys.system, &name);
|
|
||||||
let d2 = strsim::normalized_levenshtein(&sys.body, &name);
|
|
||||||
if d1 > ent.0 {
|
|
||||||
*ent = (d1, Some(sys.clone().build()))
|
|
||||||
} else if d2 > ent.0 {
|
|
||||||
*ent = (d2, Some(sys.clone().build()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let dt = std::time::Instant::now() - t_start;
|
||||||
|
info!(
|
||||||
|
"Searched {} records in {:?}: {} records/second",
|
||||||
|
processed,
|
||||||
|
dt,
|
||||||
|
(processed as f64) / dt.as_secs_f64()
|
||||||
|
);
|
||||||
Ok(best)
|
Ok(best)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hash the contents of `path` with sha3 and return the hash as a vector of bytes
|
||||||
|
fn hash_file(path: &Path) -> Vec<u8> {
|
||||||
|
let mut hash_reader = BufReader::new(File::open(path).unwrap());
|
||||||
|
let mut hasher = Sha3_256::new();
|
||||||
|
std::io::copy(&mut hash_reader, &mut hasher).unwrap();
|
||||||
|
hasher.finalize().iter().copied().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct and `O(1)` lookup index for the csv file at `path`.
|
||||||
|
/// The structure of the index is `(sha3, Vec<usize>)`
|
||||||
|
/// where the first element is the sha3 hash of the file the index belongs to
|
||||||
|
/// followed by a deltified vector where the entry at index `i` is the file offset for line `i` of the csv file.
|
||||||
|
pub fn build_index(path: &Path) -> std::io::Result<()> {
|
||||||
|
let file_hash = hash_file(path);
|
||||||
|
let mut wtr = BufWriter::new(File::create(path.with_extension("idx"))?);
|
||||||
|
let mut idx: Vec<u8> = Vec::new();
|
||||||
|
let mut records = (csv::ReaderBuilder::new()
|
||||||
|
.has_headers(false)
|
||||||
|
.from_path(path)?)
|
||||||
|
.into_deserialize::<System>();
|
||||||
|
let mut n: usize = 0;
|
||||||
|
let mut size;
|
||||||
|
idx.push(0);
|
||||||
|
loop {
|
||||||
|
n += 1;
|
||||||
|
if n % 100000 == 0 {
|
||||||
|
info!("{} Bodies processed", n);
|
||||||
|
}
|
||||||
|
let new_pos = records.reader().position().byte();
|
||||||
|
if records.next().is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size = records.reader().position().byte() - new_pos;
|
||||||
|
idx.push(size as u8);
|
||||||
|
}
|
||||||
|
assert_eq!(idx.len(), n);
|
||||||
|
bincode::serialize_into(&mut wtr, &(file_hash, idx)).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node for R*-Tree
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct TreeNode {
|
pub struct TreeNode {
|
||||||
|
/// System ID
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
|
/// Position in space
|
||||||
pub pos: [f32; 3],
|
pub pos: [f32; 3],
|
||||||
pub mult: f32,
|
/// flags
|
||||||
|
/// 00 unscoopable
|
||||||
|
/// 01 scoopable
|
||||||
|
/// 10 white dward
|
||||||
|
/// 11 neutron star
|
||||||
|
pub flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for TreeNode {
|
||||||
|
fn to_object(&self, py: Python) -> PyObject {
|
||||||
|
pythonize::pythonize(py, self).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TreeNode {
|
impl TreeNode {
|
||||||
pub fn get(&self, router: &Router) -> Option<System> {
|
/// Retrieve matching [System] for this tree node
|
||||||
let mut cache = router.cache.as_ref().unwrap().lock().unwrap();
|
pub fn get(&self, router: &Router) -> Result<Option<System>, String> {
|
||||||
cache.get(self.id)
|
router.get(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mult(&self) -> f32 {
|
||||||
|
match self.flags {
|
||||||
|
0b11 => 4.0,
|
||||||
|
0b10 => 1.5,
|
||||||
|
_ => 1.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TreeNode {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for TreeNode {}
|
||||||
|
|
||||||
|
impl PartialOrd for TreeNode {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.id.cmp(&other.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for TreeNode {
|
||||||
|
fn cmp(&self, other: &TreeNode) -> Ordering {
|
||||||
|
self.id.cmp(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for TreeNode {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Star system info read from CSV
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)]
|
#[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)]
|
||||||
pub struct SystemSerde {
|
|
||||||
pub id: u32,
|
|
||||||
pub star_type: String,
|
|
||||||
pub system: String,
|
|
||||||
pub body: String,
|
|
||||||
pub mult: f32,
|
|
||||||
pub distance: f32,
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemSerde {
|
|
||||||
pub fn build(self) -> System {
|
|
||||||
System {
|
|
||||||
id: self.id,
|
|
||||||
star_type: self.star_type,
|
|
||||||
system: self.system,
|
|
||||||
body: self.body,
|
|
||||||
mult: self.mult,
|
|
||||||
distance: self.distance,
|
|
||||||
pos: [self.x, self.y, self.z],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_node(&self) -> TreeNode {
|
|
||||||
TreeNode {
|
|
||||||
id: self.id,
|
|
||||||
pos: [self.x, self.y, self.z],
|
|
||||||
mult: self.mult,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct System {
|
pub struct System {
|
||||||
|
/// Unique System id
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub star_type: String,
|
/// Star system
|
||||||
pub system: String,
|
pub name: String,
|
||||||
pub body: String,
|
/// Number of bodies
|
||||||
|
pub num_bodies: u8,
|
||||||
|
/// Does the system have a scoopable star?
|
||||||
|
pub has_scoopable: bool,
|
||||||
|
/// Jump range multiplier (1.5 for white dwarfs, 4.0 for neutron stars, 1.0 otherwise)
|
||||||
pub mult: f32,
|
pub mult: f32,
|
||||||
pub distance: f32,
|
/// Position
|
||||||
pub pos: [f32; 3],
|
pub pos: [f32; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl System {
|
||||||
|
fn get_flags(&self) -> u8 {
|
||||||
|
let mut flags=0;
|
||||||
|
if self.mult==4.0 {
|
||||||
|
return 0b11
|
||||||
|
}
|
||||||
|
if self.mult==1.5 {
|
||||||
|
return 0b10
|
||||||
|
}
|
||||||
|
if self.has_scoopable {
|
||||||
|
return 0b01
|
||||||
|
}
|
||||||
|
return 0b00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToPyObject for System {
|
impl ToPyObject for System {
|
||||||
fn to_object(&self, py: Python) -> PyObject {
|
fn to_object(&self, py: Python) -> PyObject {
|
||||||
let pos = PyTuple::new(py, self.pos.iter());
|
let d = PyDict::new(py);
|
||||||
let elem = PyDict::new(py);
|
d.set_item("id", self.id).unwrap();
|
||||||
elem.set_item("star_type", self.star_type.clone()).unwrap();
|
d.set_item("name", self.name.clone()).unwrap();
|
||||||
elem.set_item("system", self.system.clone()).unwrap();
|
d.set_item("num_bodies", self.num_bodies).unwrap();
|
||||||
elem.set_item("body", self.body.clone()).unwrap();
|
d.set_item("has_scoopable", self.has_scoopable).unwrap();
|
||||||
elem.set_item("distance", self.distance).unwrap();
|
d.set_item("mult", self.mult).unwrap();
|
||||||
elem.set_item("mult", self.mult).unwrap();
|
d.set_item("pos", (self.pos[0], self.pos[1], self.pos[2]))
|
||||||
elem.set_item("id", self.id).unwrap();
|
.unwrap();
|
||||||
elem.set_item("pos", pos).unwrap();
|
return d.to_object(py);
|
||||||
elem.to_object(py)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +729,7 @@ impl System {
|
||||||
TreeNode {
|
TreeNode {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
pos: self.pos,
|
pos: self.pos,
|
||||||
mult: self.mult,
|
flags: self.get_flags(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,3 +745,118 @@ impl PartialOrd for System {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DQueue<T>(Vec<VecDeque<T>>);
|
||||||
|
|
||||||
|
impl<T> DQueue<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue(&mut self, depth: usize, item: T) {
|
||||||
|
self.0.resize_with(depth, VecDeque::new);
|
||||||
|
self.0[depth].push_back(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dequeue(&mut self, depth: usize) -> Option<T> {
|
||||||
|
self.0.resize_with(depth, VecDeque::new);
|
||||||
|
self.0[depth].pop_back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for DQueue<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
struct BKTreeNode {
|
||||||
|
ids: HashSet<u32,BuildHasherDefault<NoHashHasher<u32>>>,
|
||||||
|
children: HashMap<u8,Self,BuildHasherDefault<NoHashHasher<u8>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BKTreeNode {
|
||||||
|
fn new(data: &[String], dist: &eddie::str::Levenshtein) -> Self {
|
||||||
|
let mut tree= Self::default();
|
||||||
|
let mut max_depth=0;
|
||||||
|
(0..data.len()).map(|id| {
|
||||||
|
max_depth=max_depth.max(tree.insert(data,id as u32, dist,0));
|
||||||
|
if (id>0) && (id%100_000 == 0) {
|
||||||
|
println!("Inserting ID {}, Max Depth: {}",id,max_depth);
|
||||||
|
}
|
||||||
|
}).max();
|
||||||
|
println!("Max Depth: {}",max_depth);
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_id(id: u32) -> Self {
|
||||||
|
let mut ret=Self::default();
|
||||||
|
ret.ids.insert(id);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, data: &[String],id: u32, dist: &eddie::str::Levenshtein, depth: usize) -> usize {
|
||||||
|
if self.is_empty() {
|
||||||
|
self.ids.insert(id);
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
let idx = self.get_id().unwrap() as usize;
|
||||||
|
let self_key = data.get(idx).unwrap();
|
||||||
|
let ins_key = data.get(id as usize).unwrap();
|
||||||
|
let dist_key = dist.distance(self_key,ins_key) as u8;
|
||||||
|
if dist_key==0 {
|
||||||
|
self.ids.insert(id);
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
if let Some(child) = self.children.get_mut(&dist_key) {
|
||||||
|
return child.insert(data,id,dist,depth+1);
|
||||||
|
} else {
|
||||||
|
self.children.insert(dist_key,Self::from_id(id));
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_id(&self) -> Option<u32> {
|
||||||
|
self.ids.iter().copied().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
return self.ids.is_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BKTree {
|
||||||
|
base_id: u32,
|
||||||
|
root: BKTreeNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl BKTree {
|
||||||
|
pub fn new(data: &[String], base_id: u32) -> Self {
|
||||||
|
let dist = eddie::str::Levenshtein::new();
|
||||||
|
let root = BKTreeNode::new(data, &dist);
|
||||||
|
Self {base_id,root}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.base_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump(&self, fh: &mut BufWriter<File>) -> EdLrrResult<()> {
|
||||||
|
let options = bincode::DefaultOptions::new();
|
||||||
|
let amt = options.serialized_size(self)?;
|
||||||
|
println!("Writing {}",amt);
|
||||||
|
options.serialize_into(fh,self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, name: &str) -> u32 {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
196
rust/src/edsm.rs
196
rust/src/edsm.rs
|
@ -1,196 +0,0 @@
|
||||||
use crate::common::get_mult;
|
|
||||||
use crate::common::SystemSerde;
|
|
||||||
use fnv::FnvHashMap;
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_json::Result;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Seek;
|
|
||||||
use std::io::{BufRead, BufReader, BufWriter, SeekFrom};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
struct Body {
|
|
||||||
name: String,
|
|
||||||
subType: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
body_type: String,
|
|
||||||
systemId: i32,
|
|
||||||
systemId64: i64,
|
|
||||||
#[serde(rename = "distanceToArrival")]
|
|
||||||
distance: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Coords {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct System {
|
|
||||||
id: i32,
|
|
||||||
id64: i64,
|
|
||||||
name: String,
|
|
||||||
coords: Coords,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PreprocessState {
|
|
||||||
pub file: String,
|
|
||||||
pub message: String,
|
|
||||||
pub total: u64,
|
|
||||||
pub done: u64,
|
|
||||||
pub count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process(
|
|
||||||
path: &PathBuf,
|
|
||||||
func: &mut dyn for<'r> FnMut(&'r str) -> (),
|
|
||||||
callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
let fh = File::open(path)?;
|
|
||||||
let total_size = fh.metadata()?.len();
|
|
||||||
let mut t_last = Instant::now();
|
|
||||||
let mut reader = BufReader::new(fh);
|
|
||||||
let mut state = PreprocessState {
|
|
||||||
file: path.to_str().unwrap().to_owned(),
|
|
||||||
total: total_size,
|
|
||||||
done: 0,
|
|
||||||
count: 0,
|
|
||||||
message: format!("Processing {} ...", path.to_str().unwrap()),
|
|
||||||
};
|
|
||||||
println!("Loading {} ...", path.to_str().unwrap());
|
|
||||||
while let Ok(n) = reader.read_line(&mut buffer) {
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buffer = buffer.trim_end().trim_end_matches(|c| c == ',').to_string();
|
|
||||||
if !buffer.is_empty() {
|
|
||||||
func(&buffer);
|
|
||||||
}
|
|
||||||
let pos = reader.seek(SeekFrom::Current(0)).unwrap();
|
|
||||||
state.done = pos;
|
|
||||||
state.count += 1;
|
|
||||||
if t_last.elapsed().as_millis() > 100 {
|
|
||||||
callback(&state)?;
|
|
||||||
t_last = Instant::now();
|
|
||||||
}
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_systems(
|
|
||||||
path: &PathBuf,
|
|
||||||
callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>,
|
|
||||||
) -> FnvHashMap<i32, System> {
|
|
||||||
let mut ret = FnvHashMap::default();
|
|
||||||
process(
|
|
||||||
path,
|
|
||||||
&mut |line| {
|
|
||||||
let sys_res: Result<System> = serde_json::from_str(&line);
|
|
||||||
if let Ok(sys) = sys_res {
|
|
||||||
ret.insert(sys.id, sys);
|
|
||||||
} else {
|
|
||||||
eprintln!("\nError parsing: {}\n\t{:?}\n", line, sys_res.unwrap_err());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_index(path: &PathBuf) -> std::io::Result<()> {
|
|
||||||
let mut wtr = BufWriter::new(File::create(path.with_extension("idx"))?);
|
|
||||||
let mut idx: Vec<u64> = Vec::new();
|
|
||||||
let mut records = (csv::Reader::from_path(path)?).into_deserialize::<SystemSerde>();
|
|
||||||
loop {
|
|
||||||
idx.push(records.reader().position().byte());
|
|
||||||
if records.next().is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bincode::serialize_into(&mut wtr, &idx).unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_bodies(
|
|
||||||
path: &PathBuf,
|
|
||||||
out_path: &PathBuf,
|
|
||||||
systems: &mut FnvHashMap<i32, System>,
|
|
||||||
callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
println!(
|
|
||||||
"Processing {} into {} ...",
|
|
||||||
path.to_str().unwrap(),
|
|
||||||
out_path.to_str().unwrap(),
|
|
||||||
);
|
|
||||||
let mut n: u32 = 0;
|
|
||||||
let mut wtr = csv::Writer::from_path(out_path)?;
|
|
||||||
process(
|
|
||||||
path,
|
|
||||||
&mut |line| {
|
|
||||||
if !line.contains("Star") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let body_res: Result<Body> = serde_json::from_str(&line);
|
|
||||||
if let Ok(body) = body_res {
|
|
||||||
if !body.body_type.contains("Star") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(sys) = systems.get(&body.systemId) {
|
|
||||||
let sub_type = body.subType;
|
|
||||||
let mult = get_mult(&sub_type);
|
|
||||||
let sys_name = sys.name.clone();
|
|
||||||
let rec = SystemSerde {
|
|
||||||
id: n,
|
|
||||||
star_type: sub_type,
|
|
||||||
system: sys_name,
|
|
||||||
body: body.name,
|
|
||||||
mult,
|
|
||||||
distance: body.distance,
|
|
||||||
x: sys.coords.x,
|
|
||||||
y: sys.coords.y,
|
|
||||||
z: sys.coords.z,
|
|
||||||
};
|
|
||||||
wtr.serialize(rec).unwrap();
|
|
||||||
n += 1;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
eprintln!("\nError parsing: {}\n\t{:?}\n", line, body_res.unwrap_err());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
println!("Total Systems: {}", n);
|
|
||||||
systems.clear();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preprocess_files(
|
|
||||||
bodies: &PathBuf,
|
|
||||||
systems: &PathBuf,
|
|
||||||
out_path: &PathBuf,
|
|
||||||
callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
if !out_path.exists() {
|
|
||||||
let mut systems = process_systems(systems, &callback);
|
|
||||||
process_bodies(bodies, out_path, &mut systems, &callback)?;
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"File '{}' exists, not overwriting it",
|
|
||||||
out_path.to_str().unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
println!("Building index...");
|
|
||||||
println!("Index result: {:?}", build_index(&out_path));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,40 +1,25 @@
|
||||||
use serde::{Deserialize, Serialize};
|
//! Spansh galaxy.json to csv converter
|
||||||
|
use crate::common::{get_mult, System};
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
use flate2::bufread::GzDecoder;
|
||||||
|
|
||||||
|
use log::*;
|
||||||
|
use serde::Deserialize;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader, BufWriter};
|
use std::io::{BufRead, BufReader, BufWriter, Seek};
|
||||||
|
use std::path::Path;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct SystemSerde {
|
struct GalaxyCoords {
|
||||||
pub id: u32,
|
|
||||||
pub star_type: String,
|
|
||||||
pub system: String,
|
|
||||||
pub body: String,
|
|
||||||
pub mult: f32,
|
|
||||||
pub distance: f32,
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_mult(star_type: &str) -> f32 {
|
|
||||||
if star_type.contains("White Dwarf") {
|
|
||||||
return 1.5;
|
|
||||||
}
|
|
||||||
if star_type.contains("Neutron") {
|
|
||||||
return 4.0;
|
|
||||||
}
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Coords {
|
|
||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
z: f32,
|
z: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
struct Body {
|
struct GalaxyBody {
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
body_type: String,
|
body_type: String,
|
||||||
|
@ -44,70 +29,73 @@ struct Body {
|
||||||
distance: f32,
|
distance: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
struct System {
|
struct GalaxySystem {
|
||||||
coords: Coords,
|
coords: GalaxyCoords,
|
||||||
name: String,
|
name: String,
|
||||||
bodies: Vec<Body>,
|
bodies: Vec<GalaxyBody>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_galaxy_dump(path: &str) -> std::io::Result<()> {
|
/// Load compressed galaxy.json from `path` and write `stars.csv` to `out_path`
|
||||||
let fh = File::create("stars.csv")?;
|
pub fn process_galaxy_dump(path: &Path, out_path: &Path) -> Result<()> {
|
||||||
let mut wtr = csv::Writer::from_writer(BufWriter::new(fh));
|
let out_path = out_path.with_extension("csv");
|
||||||
|
let mut wtr = csv::WriterBuilder::new()
|
||||||
|
.has_headers(false)
|
||||||
|
.from_writer(BufWriter::new(File::create(out_path)?));
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
let mut bz2_reader = std::process::Command::new("7z")
|
let rdr = BufReader::new(File::open(path)?);
|
||||||
.args(&["x", "-so", path])
|
let mut reader = BufReader::new(GzDecoder::new(rdr));
|
||||||
.stdout(std::process::Stdio::piped())
|
let mut count: usize = 0;
|
||||||
.spawn()
|
let mut total: usize = 0;
|
||||||
.unwrap_or_else(|err| {
|
let mut errors: usize = 0;
|
||||||
eprintln!("Failed to run 7z: {}", err);
|
let mut bodies: usize = 0;
|
||||||
eprintln!("Falling back to bzip2");
|
let mut systems = 0;
|
||||||
std::process::Command::new("bzip2")
|
let max_len = File::metadata(reader.get_ref().get_ref().get_ref())?.len();
|
||||||
.args(&["-d", "-c", path])
|
|
||||||
.stdout(std::process::Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
.expect("Failed to execute bzip2!")
|
|
||||||
});
|
|
||||||
let mut reader = BufReader::new(
|
|
||||||
bz2_reader
|
|
||||||
.stdout
|
|
||||||
.as_mut()
|
|
||||||
.expect("Failed to open stdout of child process"),
|
|
||||||
);
|
|
||||||
let mut count = 0;
|
|
||||||
while let Ok(n) = reader.read_line(&mut buffer) {
|
while let Ok(n) = reader.read_line(&mut buffer) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buffer = buffer
|
buffer = buffer
|
||||||
.trim()
|
.trim_end_matches(|c: char| c == ',' || c.is_whitespace())
|
||||||
.trim_end_matches(|c| c == ',')
|
|
||||||
.trim()
|
|
||||||
.to_string();
|
.to_string();
|
||||||
if !buffer.contains("Star") {
|
total += 1;
|
||||||
continue;
|
if let Ok(sys) = serde_json::from_str::<GalaxySystem>(&buffer) {
|
||||||
};
|
let mut sys_rec = System {
|
||||||
if let Ok(sys) = serde_json::from_str::<System>(&buffer) {
|
id: systems,
|
||||||
for b in &sys.bodies {
|
mult: 1.0,
|
||||||
if b.body_type == "Star" {
|
name: sys.name,
|
||||||
let s = SystemSerde {
|
num_bodies: 0,
|
||||||
id: count,
|
pos: [sys.coords.x, sys.coords.y, sys.coords.z],
|
||||||
star_type: b.sub_type.clone(),
|
has_scoopable: false,
|
||||||
distance: b.distance,
|
};
|
||||||
mult: get_mult(&b.sub_type),
|
for b in sys.bodies.iter().filter(|b| b.body_type == "Star").cloned() {
|
||||||
body: b.name.clone(),
|
sys_rec.mult = sys_rec.mult.max(get_mult(&b.sub_type));
|
||||||
system: sys.name.clone(),
|
sys_rec.num_bodies += 1;
|
||||||
x: sys.coords.x,
|
for c in "KGBFOAM".chars() {
|
||||||
y: sys.coords.y,
|
if b.sub_type.starts_with(c) {
|
||||||
z: sys.coords.z,
|
sys_rec.has_scoopable |= true;
|
||||||
};
|
break;
|
||||||
wtr.serialize(s)?;
|
}
|
||||||
count += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bodies += sys_rec.num_bodies as usize;
|
||||||
|
systems += 1;
|
||||||
|
count += 1;
|
||||||
|
wtr.serialize(sys_rec)?;
|
||||||
|
if count % 100_000 == 0 {
|
||||||
|
let cur_pos = reader.get_ref().get_ref().get_ref().stream_position()?;
|
||||||
|
let prc: f64 = ((cur_pos as f64) / (max_len as f64)) * 100.0;
|
||||||
|
info!("[{:.2} %] {} systems written", prc, count);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors += 1;
|
||||||
}
|
}
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
println!("Total: {}", count);
|
info!("Total: {}", total);
|
||||||
|
info!("Bodies: {}", bodies);
|
||||||
|
info!("Systems: {}", systems);
|
||||||
|
info!("Processed: {}", count);
|
||||||
|
info!("Errors: {}", errors);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
//! Elite: Dangerous Journal Loadout even parser
|
||||||
use crate::common::get_fsd_info;
|
use crate::common::get_fsd_info;
|
||||||
use crate::ship::Ship;
|
use crate::ship::Ship;
|
||||||
|
|
||||||
|
use eyre::Result;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -11,23 +13,23 @@ pub struct Event {
|
||||||
pub event: EventData,
|
pub event: EventData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde(tag = "event")]
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
|
#[serde(tag = "event")]
|
||||||
pub enum EventData {
|
pub enum EventData {
|
||||||
Loadout(Loadout),
|
Loadout(Loadout),
|
||||||
#[serde(other)]
|
#[serde(other)]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct Modifier {
|
pub struct Modifier {
|
||||||
label: String,
|
label: String,
|
||||||
value: f32,
|
value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct Engineering {
|
pub struct Engineering {
|
||||||
modifiers: Vec<Modifier>,
|
modifiers: Vec<Modifier>,
|
||||||
}
|
}
|
||||||
|
@ -72,55 +74,46 @@ impl Engineering {
|
||||||
|
|
||||||
impl Loadout {
|
impl Loadout {
|
||||||
fn get_booster(&self) -> Option<usize> {
|
fn get_booster(&self) -> Option<usize> {
|
||||||
self.modules
|
self.modules.iter().cloned().find_map(|m| {
|
||||||
.iter()
|
let Module { item, .. } = m;
|
||||||
.cloned()
|
if item.starts_with("int_guardianfsdbooster") {
|
||||||
.filter_map(|m| {
|
return item
|
||||||
let Module { item, .. } = m;
|
.chars()
|
||||||
if item.starts_with("int_guardianfsdbooster") {
|
.last()
|
||||||
return item
|
.unwrap()
|
||||||
.chars()
|
.to_digit(10)
|
||||||
.last()
|
.map(|v| v as usize);
|
||||||
.unwrap()
|
}
|
||||||
.to_digit(10)
|
return None;
|
||||||
.map(|v| v as usize);
|
})
|
||||||
}
|
|
||||||
return None;
|
|
||||||
})
|
|
||||||
.next()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fsd(&self) -> Option<(String, Option<Engineering>)> {
|
fn get_fsd(&self) -> Option<(String, Option<Engineering>)> {
|
||||||
self.modules
|
self.modules.iter().cloned().find_map(|m| {
|
||||||
.iter()
|
let Module {
|
||||||
.cloned()
|
slot,
|
||||||
.filter_map(|m| {
|
engineering,
|
||||||
let Module {
|
item,
|
||||||
slot,
|
} = m;
|
||||||
engineering,
|
if slot == "FrameShiftDrive" {
|
||||||
item,
|
return Some((item, engineering));
|
||||||
} = m;
|
}
|
||||||
if slot == "FrameShiftDrive" {
|
return None;
|
||||||
return Some((item, engineering));
|
})
|
||||||
}
|
|
||||||
return None;
|
|
||||||
})
|
|
||||||
.next()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_into_ship(self) -> Result<Ship, String> {
|
pub fn try_into_ship(self) -> Result<(String, Ship), String> {
|
||||||
let fsd = self.get_fsd().ok_or("No FSD found!")?;
|
let fsd = self.get_fsd().ok_or("No FSD found!")?;
|
||||||
let booster = self.get_booster().unwrap_or(0);
|
let booster = self.get_booster().unwrap_or(0);
|
||||||
let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$")
|
let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.captures(&fsd.0);
|
.captures(&fsd.0);
|
||||||
let fsd_type: (usize, usize) = fsd_type
|
let fsd_type: (usize, usize) = fsd_type
|
||||||
.map(|m| {
|
.and_then(|m| {
|
||||||
let s = m.get(1)?.as_str().to_owned().parse().ok()?;
|
let s = m.get(1)?.as_str().to_owned().parse().ok()?;
|
||||||
let c = m.get(2)?.as_str().to_owned().parse().ok()?;
|
let c = m.get(2)?.as_str().to_owned().parse().ok()?;
|
||||||
return Some((c, s));
|
return Some((c, s));
|
||||||
})
|
})
|
||||||
.flatten()
|
|
||||||
.ok_or(format!("Invalid FSD found: {}", &fsd.0))?;
|
.ok_or(format!("Invalid FSD found: {}", &fsd.0))?;
|
||||||
let eng = fsd
|
let eng = fsd
|
||||||
.1
|
.1
|
||||||
|
@ -141,18 +134,28 @@ impl Loadout {
|
||||||
let opt_mass = fsd_info
|
let opt_mass = fsd_info
|
||||||
.get("FSDOptimalMass")
|
.get("FSDOptimalMass")
|
||||||
.ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?;
|
.ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?;
|
||||||
return Ship::new(
|
let key = format!(
|
||||||
self.ship_name,
|
"[{}] {} ({})",
|
||||||
|
if !self.ship_name.is_empty() {
|
||||||
|
self.ship_name
|
||||||
|
} else {
|
||||||
|
"<NO NAME>".to_owned()
|
||||||
|
},
|
||||||
self.ship_ident,
|
self.ship_ident,
|
||||||
self.ship,
|
self.ship
|
||||||
self.unladen_mass,
|
|
||||||
self.fuel_capacity.main,
|
|
||||||
self.fuel_capacity.main,
|
|
||||||
fsd_type,
|
|
||||||
*max_fuel,
|
|
||||||
*opt_mass,
|
|
||||||
booster,
|
|
||||||
);
|
);
|
||||||
|
return Ok((
|
||||||
|
key,
|
||||||
|
Ship::new(
|
||||||
|
self.unladen_mass,
|
||||||
|
self.fuel_capacity.main,
|
||||||
|
self.fuel_capacity.main,
|
||||||
|
fsd_type,
|
||||||
|
*max_fuel,
|
||||||
|
*opt_mass,
|
||||||
|
booster,
|
||||||
|
)?,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
828
rust/src/lib.rs
828
rust/src/lib.rs
|
@ -1,144 +1,379 @@
|
||||||
// #![deny(warnings)]
|
|
||||||
#![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)]
|
#![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)]
|
||||||
|
//! # Elite: Danerous Long Range Router
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod edsm;
|
|
||||||
pub mod galaxy;
|
pub mod galaxy;
|
||||||
pub mod journal;
|
pub mod journal;
|
||||||
|
pub mod mmap_csv;
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
pub mod profiling;
|
||||||
pub mod route;
|
pub mod route;
|
||||||
|
pub mod search_algos;
|
||||||
pub mod ship;
|
pub mod ship;
|
||||||
|
|
||||||
|
use bincode::Options;
|
||||||
|
use csv::{Position, StringRecord};
|
||||||
|
use eddie::Levenshtein;
|
||||||
|
// =========================
|
||||||
|
use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
|
||||||
|
use std::alloc::System as SystemAlloc;
|
||||||
|
use std::cell::RefMut;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::io::{BufWriter, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::Instant;
|
||||||
|
#[cfg(not(feature = "profiling"))]
|
||||||
|
#[global_allocator]
|
||||||
|
static GLOBAL: &StatsAlloc<SystemAlloc> = &INSTRUMENTED_SYSTEM;
|
||||||
|
// =========================
|
||||||
|
#[cfg(not(feature = "profiling"))]
|
||||||
|
mod profiling {
|
||||||
|
pub fn init() {}
|
||||||
|
}
|
||||||
|
|
||||||
extern crate derivative;
|
extern crate derivative;
|
||||||
use crate::common::{find_matches, SysEntry};
|
use crate::common::{find_matches, grid_stats, EdLrrError, SysEntry, System};
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
use crate::profiling::*;
|
||||||
use crate::route::{Router, SearchState};
|
use crate::route::{Router, SearchState};
|
||||||
use crate::ship::Ship;
|
use crate::ship::Ship;
|
||||||
|
use eyre::Result;
|
||||||
|
#[cfg(not(feature = "profiling"))]
|
||||||
|
use log::*;
|
||||||
use pyo3::exceptions::*;
|
use pyo3::exceptions::*;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyDict, PyList, PyTuple};
|
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
|
||||||
use pyo3::PyObjectProtocol;
|
use pyo3::{create_exception, PyObjectProtocol};
|
||||||
use std::path::PathBuf;
|
use route::{LineCache, PyModeConfig};
|
||||||
|
use std::{
|
||||||
|
cell::RefCell, collections::HashMap, convert::TryInto, fs::File, io::BufReader, path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
#[global_allocator]
|
||||||
|
static GLOBAL: ProfiledAllocator<std::alloc::System> =
|
||||||
|
ProfiledAllocator::new(std::alloc::System, 1024);
|
||||||
|
|
||||||
|
create_exception!(_ed_lrr, RoutingError, PyException);
|
||||||
|
create_exception!(_ed_lrr, ProcessingError, PyException);
|
||||||
|
create_exception!(_ed_lrr, ResolveError, PyException);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum RangeOrShip {
|
||||||
|
Range(f32),
|
||||||
|
Ship(Ship),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromPyObject<'_> for RangeOrShip {
|
||||||
|
fn extract(ob: &PyAny) -> PyResult<Self> {
|
||||||
|
if let Ok(n) = ob.extract() {
|
||||||
|
return Ok(Self::Range(n));
|
||||||
|
}
|
||||||
|
let s: PyShip = ob.extract()?;
|
||||||
|
return Ok(Self::Ship(s.ship));
|
||||||
|
}
|
||||||
|
}
|
||||||
#[pyclass(dict)]
|
#[pyclass(dict)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[text_signature = "(callback, /)"]
|
#[pyo3(text_signature = "(callback, /)")]
|
||||||
struct PyRouter {
|
struct PyRouter {
|
||||||
router: Router,
|
router: Router,
|
||||||
primary_only: bool,
|
stars_path: Option<String>,
|
||||||
stars_path: String,
|
}
|
||||||
|
|
||||||
|
impl PyRouter {
|
||||||
|
fn check_stars(&self) -> PyResult<PathBuf> {
|
||||||
|
self.stars_path
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| PyErr::from(EdLrrError::RuntimeError("no stars.csv loaded".to_owned())))
|
||||||
|
.map(PathBuf::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyRouter {
|
impl PyRouter {
|
||||||
#[new]
|
#[new]
|
||||||
#[args(callback = "None")]
|
#[args(callback = "None")]
|
||||||
fn new(callback: Option<PyObject>, py: Python<'static>) -> PyResult<Self> {
|
fn new(callback: Option<PyObject>) -> Self {
|
||||||
Ok(PyRouter {
|
let mut router = Router::new();
|
||||||
router: Router::new(Box::new(
|
if callback.is_some() {
|
||||||
move |state: &SearchState| {
|
router.set_callback(Box::new(move |state: &SearchState| {
|
||||||
match callback.as_ref() {
|
let gil_guard = Python::acquire_gil();
|
||||||
Some(cb) => cb.call(py, (state.clone(),), None),
|
let py = gil_guard.python();
|
||||||
None => Ok(py.None()),
|
match callback.as_ref() {
|
||||||
}
|
Some(cb) => cb.call(py, (state.clone(),), None),
|
||||||
|
None => Ok(py.None()),
|
||||||
}
|
}
|
||||||
)),
|
}))
|
||||||
primary_only: false,
|
}
|
||||||
stars_path: String::from(""),
|
PyRouter {
|
||||||
})
|
router,
|
||||||
|
stars_path: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(ship, /)"]
|
#[args(primary_only = "false", immediate = "false")]
|
||||||
fn set_ship(&mut self, py: Python, ship: &PyShip) -> PyResult<PyObject> {
|
#[pyo3(text_signature = "(path, primary_only, /)")]
|
||||||
self.router.set_ship(ship.ship.clone());
|
fn load(&mut self, path: String, py: Python, immediate: bool) -> PyResult<PyObject> {
|
||||||
|
self.stars_path = Some(path);
|
||||||
|
if immediate {
|
||||||
|
let stars_path = self.check_stars()?;
|
||||||
|
let route_res = self.router.load(&stars_path);
|
||||||
|
if let Err(err_msg) = route_res {
|
||||||
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
|
};
|
||||||
|
}
|
||||||
Ok(py.None())
|
Ok(py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[args(primary_only = "false")]
|
#[pyo3(text_signature = "(/)")]
|
||||||
#[text_signature = "(path, primary_only, /)"]
|
fn unload(&mut self, py: Python) -> PyObject {
|
||||||
fn load(
|
self.router.unload();
|
||||||
&mut self,
|
py.None()
|
||||||
path: String,
|
}
|
||||||
primary_only: bool,
|
|
||||||
py: Python,
|
fn plot(&mut self, py: Python) -> PyResult<PyObject> {
|
||||||
) -> PyResult<PyObject> {
|
let stars_path = self.check_stars()?;
|
||||||
self.stars_path = path;
|
let route_res = self.router.load(&stars_path);
|
||||||
self.primary_only = primary_only;
|
if let Err(err_msg) = route_res {
|
||||||
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
|
};
|
||||||
|
let mut max_v = [0f32, 0f32, 0f32];
|
||||||
|
let mut min_v = [0f32, 0f32, 0f32];
|
||||||
|
for node in self.router.get_tree().iter() {
|
||||||
|
for i in 0..3 {
|
||||||
|
if node.pos[i] > max_v[i] {
|
||||||
|
max_v[i] = node.pos[i];
|
||||||
|
}
|
||||||
|
if node.pos[i] < min_v[i] {
|
||||||
|
min_v[i] = node.pos[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let plot_bbox: ((f32, f32), (f32, f32)) = ((min_v[0], max_v[0]), (min_v[2], max_v[2]));
|
||||||
|
Ok(plot_bbox.to_object(py))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_bfs(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
||||||
|
let stars_path = self.check_stars()?;
|
||||||
|
let route_res = self.router.load(&stars_path);
|
||||||
|
if let Err(err_msg) = route_res {
|
||||||
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
|
};
|
||||||
|
self.router
|
||||||
|
.precomp_bfs(range)
|
||||||
|
.map_err(PyErr::new::<RoutingError, _>)
|
||||||
|
.map(|_| py.None())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn precompute_graph(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
||||||
|
let stars_path = self.check_stars()?;
|
||||||
|
let route_res = self.router.load(&stars_path);
|
||||||
|
if let Err(err_msg) = route_res {
|
||||||
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
|
};
|
||||||
|
self.router
|
||||||
|
.precompute_graph(range)
|
||||||
|
.map_err(PyErr::new::<RoutingError, _>)
|
||||||
|
.map(|_| py.None())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nb_perf_test(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
||||||
|
let stars_path = self.check_stars()?;
|
||||||
|
let route_res = self.router.load(&stars_path);
|
||||||
|
if let Err(err_msg) = route_res {
|
||||||
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
|
};
|
||||||
|
let mut nbmap = BTreeMap::new();
|
||||||
|
let tree = self.router.get_tree();
|
||||||
|
let total_nodes = tree.size();
|
||||||
|
for (n, node) in tree.iter().enumerate() {
|
||||||
|
let nbs = self
|
||||||
|
.router
|
||||||
|
.neighbours(node, range)
|
||||||
|
.map(|nb| nb.id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
nbmap.insert(node.id, nbs);
|
||||||
|
if n % 100_000 == 0 {
|
||||||
|
println!("{}/{}", n, total_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("{}", nbmap.len());
|
||||||
Ok(py.None())
|
Ok(py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[args(greedyness = "0.5", num_workers = "0", beam_width = "0")]
|
fn precompute_neighbors(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
||||||
#[text_signature = "(hops, range, greedyness, beam_width, num_workers, /)"]
|
let stars_path = self.check_stars()?;
|
||||||
|
let route_res = self.router.load(&stars_path);
|
||||||
|
if let Err(err_msg) = route_res {
|
||||||
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
|
};
|
||||||
|
self.router
|
||||||
|
.precompute_all(range)
|
||||||
|
.map_err(PyErr::new::<RoutingError, _>)
|
||||||
|
.map(|_| py.None())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[args(
|
||||||
|
greedyness = "0.5",
|
||||||
|
max_dist = "0.0",
|
||||||
|
num_workers = "0",
|
||||||
|
beam_width = "BeamWidth::Absolute(0)"
|
||||||
|
)]
|
||||||
|
#[pyo3(text_signature = "(hops, range, mode, num_workers, /)")]
|
||||||
fn route(
|
fn route(
|
||||||
&mut self,
|
&mut self,
|
||||||
hops: &PyList,
|
hops: Vec<SysEntry>,
|
||||||
range: Option<f32>,
|
range: RangeOrShip,
|
||||||
greedyness: f32,
|
mode: Option<PyModeConfig>,
|
||||||
beam_width: usize,
|
|
||||||
num_workers: usize,
|
num_workers: usize,
|
||||||
py: Python,
|
) -> PyResult<Vec<common::System>> {
|
||||||
) -> PyResult<PyObject> {
|
let stars_path = self.check_stars()?;
|
||||||
let route_res = self
|
let route_res = self.router.load(&stars_path);
|
||||||
.router
|
|
||||||
.load(&PathBuf::from(self.stars_path.clone()), self.primary_only);
|
|
||||||
if let Err(err_msg) = route_res {
|
if let Err(err_msg) = route_res {
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
};
|
};
|
||||||
let mut sys_entries: Vec<SysEntry> = Vec::new();
|
info!("Resolving systems...");
|
||||||
for hop in hops {
|
let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) {
|
||||||
if let Ok(id) = hop.extract() {
|
Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(),
|
||||||
sys_entries.push(SysEntry::ID(id));
|
|
||||||
} else {
|
|
||||||
sys_entries.push(SysEntry::parse(hop.extract()?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("Resolving systems...");
|
|
||||||
let ids: Vec<u32> = match resolve(&sys_entries, &self.router.path) {
|
|
||||||
Ok(ids) => ids,
|
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
return Err(EdLrrError::ResolveError(err_msg).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match self
|
let mut is_default = false;
|
||||||
.router
|
let mut is_ship = false;
|
||||||
.compute_route(&ids, range, greedyness, beam_width, num_workers)
|
info!("{:?}", mode);
|
||||||
{
|
let mut mode = match mode {
|
||||||
Ok(route) => {
|
Some(mode) => mode,
|
||||||
let py_route: Vec<_> = route.iter().map(|hop| hop.to_object(py)).collect();
|
None => {
|
||||||
Ok(py_route.to_object(py))
|
let mode = PyModeConfig::default();
|
||||||
|
is_default = true;
|
||||||
|
mode
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if mode.mode.is_empty() {
|
||||||
|
if mode.ship.is_none() {
|
||||||
|
mode.mode = "bfs".to_string();
|
||||||
|
} else {
|
||||||
|
mode.mode = "ship".to_string();
|
||||||
|
if mode.ship_mode == *"" {
|
||||||
|
mode.ship_mode = "jumps".to_string();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err_msg) => Err(PyErr::new::<RuntimeError, _>(err_msg)),
|
|
||||||
}
|
}
|
||||||
|
let range = match range {
|
||||||
|
RangeOrShip::Range(r) => Some(r),
|
||||||
|
RangeOrShip::Ship(ship) => {
|
||||||
|
mode.mode = "ship".into();
|
||||||
|
mode.ship = Some(ship);
|
||||||
|
is_ship = true;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!("{:?}", mode);
|
||||||
|
let mode = mode.try_into()?;
|
||||||
|
if is_default && !is_ship {
|
||||||
|
warn!("no mode specified, defaulting to {}", mode);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "profiling"))]
|
||||||
|
let reg = Region::new(GLOBAL);
|
||||||
|
let res = match self.router.compute_route(&ids, range, mode, num_workers) {
|
||||||
|
Ok(route) => Ok(route),
|
||||||
|
Err(err_msg) => Err(PyErr::new::<RoutingError, _>(err_msg)),
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "profiling"))]
|
||||||
|
println!("{:?}", reg.change());
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perf_test(&self, callback: PyObject, py: Python) -> PyResult<PyObject> {
|
||||||
|
use common::TreeNode;
|
||||||
|
let node = TreeNode {
|
||||||
|
pos: [-65.21875, 7.75, -111.03125],
|
||||||
|
flags: 1,
|
||||||
|
id: 0,
|
||||||
|
};
|
||||||
|
let goal = TreeNode {
|
||||||
|
pos: [-9530.5, -910.28125, 19808.125],
|
||||||
|
flags: 1,
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
let kwargs = vec![("goal", goal), ("node", node)].into_py_dict(py);
|
||||||
|
let mut n: usize = 0;
|
||||||
|
let mut d: f64 = 0.0;
|
||||||
|
let num_loops = 10_000_000;
|
||||||
|
loop {
|
||||||
|
let pool = unsafe { Python::new_pool(py) };
|
||||||
|
let t_start = std::time::Instant::now();
|
||||||
|
for _ in 0..num_loops {
|
||||||
|
let val: f64 = callback.call(py, (), Some(kwargs))?.extract(py)?;
|
||||||
|
}
|
||||||
|
d += t_start.elapsed().as_secs_f64();
|
||||||
|
drop(pool);
|
||||||
|
n += num_loops;
|
||||||
|
let dt = std::time::Duration::from_secs_f64(d / (n as f64));
|
||||||
|
println!("{}: {:?}", n, dt);
|
||||||
|
}
|
||||||
|
Ok(py.None())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[args(grid_size = "1.0")]
|
||||||
|
#[pyo3(text_signature = "(grid_size)")]
|
||||||
|
fn get_grid(&self, grid_size: f32, py: Python) -> PyResult<PyObject> {
|
||||||
|
let stars_path = self.check_stars()?;
|
||||||
|
grid_stats(&stars_path, grid_size)
|
||||||
|
.map(|ret| ret.to_object(py))
|
||||||
|
.map_err(PyErr::new::<PyRuntimeError, _>)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[args(hops = "*")]
|
#[args(hops = "*")]
|
||||||
#[text_signature = "(sys_1, sys_2, ..., /)"]
|
#[pyo3(text_signature = "(sys_1, sys_2, ..., /)")]
|
||||||
fn resolve_systems(&self, hops: &PyTuple, py: Python) -> PyResult<PyObject> {
|
fn resolve(&self, hops: Vec<SysEntry>, py: Python) -> PyResult<PyObject> {
|
||||||
let mut sys_entries: Vec<SysEntry> = Vec::new();
|
info!("Resolving systems...");
|
||||||
for hop in hops {
|
let stars_path = self.check_stars()?;
|
||||||
if let Ok(id) = hop.extract() {
|
let systems: Vec<System> = match resolve(&hops, &stars_path, false) {
|
||||||
sys_entries.push(SysEntry::ID(id));
|
Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(),
|
||||||
} else {
|
|
||||||
sys_entries.push(SysEntry::parse(hop.extract()?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("Resolving systems...");
|
|
||||||
let stars_path = PathBuf::from(self.stars_path.clone());
|
|
||||||
let ids: Vec<u32> = match resolve(&sys_entries, &stars_path) {
|
|
||||||
Ok(ids) => ids,
|
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
return Err(EdLrrError::ResolveError(err_msg).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ret: Vec<(_, u32)> = hops.into_iter().zip(ids.into_iter()).collect();
|
let ret: Vec<(_, System)> = hops
|
||||||
|
.into_iter()
|
||||||
|
.zip(systems.iter())
|
||||||
|
.map(|(id, sys)| (id, sys.clone()))
|
||||||
|
.collect();
|
||||||
Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py))
|
Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[staticmethod]
|
fn str_tree_test(&self) -> common::EdLrrResult<()> {
|
||||||
fn preprocess_edsm() -> PyResult<()> {
|
use common::BKTree;
|
||||||
todo!("Implement EDSM Preprocessor")
|
const CHUNK_SIZE: usize = 4_000_000;
|
||||||
}
|
let path = self.check_stars()?;
|
||||||
|
let reader: csv::Reader<File> = csv::ReaderBuilder::new()
|
||||||
#[staticmethod]
|
.has_headers(false)
|
||||||
fn preprocess_galaxy() -> PyResult<()> {
|
.from_path(path)
|
||||||
todo!("Implement galaxy.json Preprocessor")
|
.map_err(EdLrrError::from)?;
|
||||||
|
let mut data: Vec<String> = Vec::with_capacity(CHUNK_SIZE);
|
||||||
|
let t_start = Instant::now();
|
||||||
|
let mut base_id=0;
|
||||||
|
let mut wr = BufWriter::new(File::create("test.bktree")?);
|
||||||
|
for sys in reader.into_deserialize::<System>() {
|
||||||
|
let sys = sys?;
|
||||||
|
data.push(sys.name);
|
||||||
|
if data.len()>CHUNK_SIZE {
|
||||||
|
let tree = BKTree::new(&data, base_id);
|
||||||
|
tree.dump(&mut wr)?;
|
||||||
|
base_id=sys.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !data.is_empty() {
|
||||||
|
let tree = BKTree::new(&data, base_id);
|
||||||
|
tree.dump(&mut wr)?;
|
||||||
|
}
|
||||||
|
wr.flush()?;
|
||||||
|
println!("Took: {:?}", t_start.elapsed());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,52 +388,91 @@ impl PyObjectProtocol for PyRouter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(entries: &[SysEntry], path: &PathBuf) -> Result<Vec<u32>, String> {
|
enum ResolveResult {
|
||||||
|
System(System),
|
||||||
|
ID(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolveResult {
|
||||||
|
fn into_id(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::System(sys) => sys.id,
|
||||||
|
Self::ID(id) => id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_system(self) -> System {
|
||||||
|
if let Self::System(sys) = self {
|
||||||
|
return sys;
|
||||||
|
}
|
||||||
|
panic!("Tried to unwrap ID into System");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<ResolveResult>, String> {
|
||||||
let mut names: Vec<String> = Vec::new();
|
let mut names: Vec<String> = Vec::new();
|
||||||
let mut ids: Vec<u32> = Vec::new();
|
|
||||||
let mut ret: Vec<u32> = Vec::new();
|
let mut ret: Vec<u32> = Vec::new();
|
||||||
|
let mut needs_rtree = false;
|
||||||
for ent in entries {
|
for ent in entries {
|
||||||
match ent {
|
match ent {
|
||||||
SysEntry::Name(name) => names.push(name.to_owned()),
|
SysEntry::Name(name) => names.push(name.to_owned()),
|
||||||
SysEntry::ID(id) => ids.push(*id),
|
SysEntry::Pos(_) => {
|
||||||
|
needs_rtree |= true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(format!(
|
return Err(format!("Source file {:?} does not exist!", path.display()));
|
||||||
"Source file \"{:?}\" does not exist!",
|
|
||||||
path.display()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
let name_ids = if !names.is_empty() {
|
||||||
let name_ids = find_matches(path, names, false)?;
|
mmap_csv::mmap_csv(path, names)?
|
||||||
|
} else {
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
let tmp_r = needs_rtree
|
||||||
|
.then(|| {
|
||||||
|
let mut r = Router::new();
|
||||||
|
r.load(path).map(|_| r)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
for ent in entries {
|
for ent in entries {
|
||||||
match ent {
|
match ent {
|
||||||
SysEntry::Name(name) => {
|
SysEntry::Name(name) => {
|
||||||
let ent_res = name_ids
|
let ent_res = name_ids
|
||||||
.get(&name.to_owned())
|
.get(name)
|
||||||
.ok_or(format!("System {} not found", name))?;
|
.ok_or(format!("System {} not found", name))?;
|
||||||
let sys = ent_res
|
let sys = ent_res
|
||||||
.1
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(format!("System {} not found", name))?;
|
.ok_or(format!("System {} not found", name))?;
|
||||||
if ent_res.0 < 0.75 {
|
ret.push(*sys);
|
||||||
println!(
|
|
||||||
"WARNING: {} match to {} with low confidence ({:.2}%)",
|
|
||||||
name,
|
|
||||||
sys.system,
|
|
||||||
ent_res.0 * 100.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ret.push(sys.id);
|
|
||||||
}
|
}
|
||||||
SysEntry::ID(id) => ret.push(*id),
|
SysEntry::ID(id) => ret.push(*id),
|
||||||
|
SysEntry::Pos((x, y, z)) => ret.push(
|
||||||
|
tmp_r
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.closest(&[*x, *y, *z])
|
||||||
|
.ok_or("No systems loaded!")?
|
||||||
|
.id,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ret)
|
if id_only {
|
||||||
|
return Ok(ret.iter().map(|id| ResolveResult::ID(*id)).collect());
|
||||||
|
} else {
|
||||||
|
let mut lc = route::LineCache::create(path)?;
|
||||||
|
let mut systems = vec![];
|
||||||
|
for id in ret {
|
||||||
|
let sys = ResolveResult::System(lc.get(id)?.unwrap());
|
||||||
|
systems.push(sys)
|
||||||
|
}
|
||||||
|
return Ok(systems);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(dict)]
|
#[pyclass(dict)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
struct PyShip {
|
struct PyShip {
|
||||||
ship: Ship,
|
ship: Ship,
|
||||||
}
|
}
|
||||||
|
@ -219,8 +493,8 @@ impl PyShip {
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> {
|
fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> {
|
||||||
match Ship::new_from_json(loadout) {
|
match Ship::new_from_json(loadout) {
|
||||||
Ok(ship) => Ok((PyShip { ship }).into_py(py)),
|
Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)),
|
||||||
Err(err_msg) => Err(PyErr::new::<ValueError, _>(err_msg)),
|
Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
|
@ -228,15 +502,15 @@ impl PyShip {
|
||||||
let mut ship = match Ship::new_from_journal() {
|
let mut ship = match Ship::new_from_journal() {
|
||||||
Ok(ship) => ship,
|
Ok(ship) => ship,
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ships: Vec<(PyObject, PyObject)> = ship
|
let ships: Vec<(PyObject, PyObject)> = ship
|
||||||
.drain()
|
.drain()
|
||||||
.map(|(k, v)| {
|
.map(|(ship_name, ship)| {
|
||||||
let k_py = k.to_object(py);
|
let ship_name_py = ship_name.to_object(py);
|
||||||
let v_py = (PyShip { ship: v }).into_py(py);
|
let ship_py = (PyShip { ship }).into_py(py);
|
||||||
(k_py, v_py)
|
(ship_name_py, ship_py)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
||||||
|
@ -246,239 +520,141 @@ impl PyShip {
|
||||||
self.ship.to_object(py)
|
self.ship.to_object(py)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(dist, /)"]
|
#[pyo3(text_signature = "(dist, /)")]
|
||||||
fn fuel_cost(&self, _py: Python, dist: f32) -> PyResult<f32> {
|
fn fuel_cost(&self, _py: Python, dist: f32) -> f32 {
|
||||||
Ok(self.ship.fuel_cost(dist))
|
self.ship.fuel_cost(dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(/)"]
|
#[getter]
|
||||||
fn range(&self, _py: Python) -> PyResult<f32> {
|
fn range(&self, _py: Python) -> f32 {
|
||||||
Ok(self.ship.range())
|
self.ship.range()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(/)"]
|
#[getter]
|
||||||
fn max_range(&self, _py: Python) -> PyResult<f32> {
|
fn max_range(&self, _py: Python) -> f32 {
|
||||||
Ok(self.ship.max_range())
|
self.ship.max_range()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(dist, /)"]
|
#[pyo3(text_signature = "(dist, /)")]
|
||||||
fn make_jump(&mut self, dist: f32, _py: Python) -> PyResult<Option<f32>> {
|
fn make_jump(&mut self, dist: f32, _py: Python) -> Option<f32> {
|
||||||
Ok(self.ship.make_jump(dist))
|
self.ship.make_jump(dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(dist, /)"]
|
#[pyo3(text_signature = "(dist, /)")]
|
||||||
fn can_jump(&self, dist: f32, _py: Python) -> PyResult<bool> {
|
fn can_jump(&self, dist: f32, _py: Python) -> bool {
|
||||||
Ok(self.ship.can_jump(dist))
|
self.ship.can_jump(dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[args(fuel_amount = "None")]
|
#[args(fuel_amount = "None")]
|
||||||
#[text_signature = "(fuel_amount, /)"]
|
#[pyo3(text_signature = "(fuel_amount, /)")]
|
||||||
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) -> PyResult<()> {
|
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) {
|
||||||
if let Some(fuel) = fuel_amount {
|
if let Some(fuel) = fuel_amount {
|
||||||
self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity)
|
self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity)
|
||||||
} else {
|
} else {
|
||||||
self.ship.fuel_mass = self.ship.fuel_capacity;
|
self.ship.fuel_mass = self.ship.fuel_capacity;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[text_signature = "(factor, /)"]
|
#[pyo3(text_signature = "(factor, /)")]
|
||||||
fn boost(&mut self, factor: f32, _py: Python) -> PyResult<()> {
|
fn boost(&mut self, factor: f32, _py: Python) {
|
||||||
self.ship.boost(factor);
|
self.ship.boost(factor);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PyShip {
|
||||||
|
fn get_ship(&self) -> Ship {
|
||||||
|
self.ship.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
fn preprocess_edsm(
|
||||||
|
_bodies_path: &str,
|
||||||
|
_systems_path: &str,
|
||||||
|
_out_path: &str,
|
||||||
|
_py: Python,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
Err(pyo3::exceptions::PyNotImplementedError::new_err(
|
||||||
|
"please use Spansh's Galaxy dump and preprocess_galaxy()",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_py_value(value: eval::Value, py: Python) -> PyResult<PyObject> {
|
||||||
|
type Value = eval::Value;
|
||||||
|
match value {
|
||||||
|
Value::String(s) => Ok(s.to_object(py)),
|
||||||
|
Value::Number(n) => {
|
||||||
|
if let Some(n) = n.as_u64() {
|
||||||
|
return Ok(n.to_object(py));
|
||||||
|
}
|
||||||
|
if let Some(n) = n.as_i64() {
|
||||||
|
return Ok(n.to_object(py));
|
||||||
|
}
|
||||||
|
return Ok(n.as_f64().unwrap().to_object(py));
|
||||||
|
}
|
||||||
|
Value::Bool(b) => Ok(b.to_object(py)),
|
||||||
|
Value::Array(mut t) => {
|
||||||
|
let mut res: Vec<PyObject> = vec![];
|
||||||
|
for v in t.drain(..) {
|
||||||
|
res.push(to_py_value(v, py)?);
|
||||||
|
}
|
||||||
|
Ok(PyTuple::new(py, &res).to_object(py))
|
||||||
|
}
|
||||||
|
Value::Object(o) => {
|
||||||
|
let res = PyDict::new(py);
|
||||||
|
for (k, v) in o.iter() {
|
||||||
|
res.set_item(k, to_py_value(v.clone(), py)?)?;
|
||||||
|
}
|
||||||
|
Ok(res.to_object(py))
|
||||||
|
}
|
||||||
|
Value::Null => Ok(py.None()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_py(res: Result<eval::Value, eval::Error>, py: Python) -> PyResult<PyObject> {
|
||||||
|
res.map_err(|e| PyErr::from(EdLrrError::EvalError(e)))
|
||||||
|
.and_then(|r| to_py_value(r, py))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
#[pyo3(text_signature = "(expr)")]
|
||||||
|
fn expr_test(expr: &str, py: Python) -> PyResult<PyObject> {
|
||||||
|
use eval::{to_value, Expr, Value};
|
||||||
|
let mut res = Expr::new(expr)
|
||||||
|
.compile()
|
||||||
|
.map_err(|e| PyErr::from(EdLrrError::EvalError(e)))?;
|
||||||
|
let mut hm: HashMap<&str, Value> = HashMap::new();
|
||||||
|
hm.insert("foo", to_value(42));
|
||||||
|
hm.insert("bar", to_value((-1, -2, -3)));
|
||||||
|
res = res.value("x", vec!["Hello", "world", "!"]);
|
||||||
|
res = res.value("y", 42);
|
||||||
|
res = res.value("p", (2.17, 5.14, 1.62));
|
||||||
|
res = res.value("hw", "Hello World!");
|
||||||
|
res = res.value("hm", hm);
|
||||||
|
to_py(res.exec(), py)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
#[pyo3(text_signature = "(path, out_path, /)")]
|
||||||
|
fn preprocess_galaxy(path: &str, out_path: &str) -> PyResult<()> {
|
||||||
|
use common::build_index;
|
||||||
|
use galaxy::process_galaxy_dump;
|
||||||
|
let path = PathBuf::from(path);
|
||||||
|
let out_path = PathBuf::from(out_path);
|
||||||
|
process_galaxy_dump(&path, &out_path).unwrap();
|
||||||
|
build_index(&out_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> {
|
pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
better_panic::install();
|
better_panic::install();
|
||||||
|
pyo3_log::init();
|
||||||
|
profiling::init();
|
||||||
m.add_class::<PyRouter>()?;
|
m.add_class::<PyRouter>()?;
|
||||||
m.add_class::<PyShip>()?;
|
m.add_class::<PyShip>()?;
|
||||||
/*
|
m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_galaxy))?;
|
||||||
#[pyfn(m, "get_ships_from_journal")]
|
m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_edsm))?;
|
||||||
fn get_ships_from_journal(py: Python) -> PyResult<PyObject> {
|
m.add_wrapped(pyo3::wrap_pyfunction!(expr_test))?;
|
||||||
let ship = match Ship::new_from_journal() {
|
|
||||||
Ok(ship) => ship,
|
|
||||||
Err(err_msg) => {
|
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let ships: Vec<(_,_)> = ship.iter().map(|(k,v)| (k.to_object(py),v.to_object(py))).collect();
|
|
||||||
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[pyfn(m, "get_ships_from_loadout")]
|
|
||||||
fn get_ship_from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> {
|
|
||||||
let ship = match Ship::new_from_json(loadout) {
|
|
||||||
Ok(ship) => ship,
|
|
||||||
Err(err_msg) => {
|
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(ship.to_object(py))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
/// Preprocess bodies.json and systemsWithCoordinates.json into stars.csv
|
|
||||||
#[pyfn(m, "preprocess")]
|
|
||||||
#[text_signature = "(infile_systems, infile_bodies, outfile, callback, /)"]
|
|
||||||
fn ed_lrr_preprocess(
|
|
||||||
py: Python<'static>,
|
|
||||||
infile_systems: String,
|
|
||||||
infile_bodies: String,
|
|
||||||
outfile: String,
|
|
||||||
callback: PyObject,
|
|
||||||
) -> PyResult<PyObject> {
|
|
||||||
use preprocess::*;
|
|
||||||
let state = PyDict::new(py);
|
|
||||||
let state_dict = PyDict::new(py);
|
|
||||||
callback.call(py, (state_dict,), None).unwrap();
|
|
||||||
let callback_wrapped = move |state: &PreprocessState| {
|
|
||||||
// println!("SEND: {:?}",state);
|
|
||||||
state_dict.set_item("file", state.file.clone())?;
|
|
||||||
state_dict.set_item("total", state.total)?;
|
|
||||||
state_dict.set_item("count", state.count)?;
|
|
||||||
state_dict.set_item("done", state.done)?;
|
|
||||||
state_dict.set_item("message", state.message.clone())?;
|
|
||||||
callback.call(py, (state_dict,), None)
|
|
||||||
};
|
|
||||||
preprocess_files(
|
|
||||||
&PathBuf::from(infile_bodies),
|
|
||||||
&PathBuf::from(infile_systems),
|
|
||||||
&PathBuf::from(outfile),
|
|
||||||
&callback_wrapped,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
Ok(state.to_object(py))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find system by name
|
|
||||||
#[pyfn(m, "find_sys")]
|
|
||||||
#[text_signature = "(sys_names, sys_list_path, /)"]
|
|
||||||
fn find_sys(py: Python, sys_names: Vec<String>, sys_list: String) -> PyResult<PyObject> {
|
|
||||||
let path = PathBuf::from(sys_list);
|
|
||||||
match find_matches(&path, sys_names, false) {
|
|
||||||
Ok(vals) => {
|
|
||||||
let ret = PyDict::new(py);
|
|
||||||
for (key, (diff, sys)) in vals {
|
|
||||||
let ret_dict = PyDict::new(py);
|
|
||||||
if let Some(val) = sys {
|
|
||||||
let pos = PyList::new(py, val.pos.iter());
|
|
||||||
ret_dict.set_item("star_type", val.star_type.clone())?;
|
|
||||||
ret_dict.set_item("system", val.system.clone())?;
|
|
||||||
ret_dict.set_item("body", val.body.clone())?;
|
|
||||||
ret_dict.set_item("distance", val.distance)?;
|
|
||||||
ret_dict.set_item("pos", pos)?;
|
|
||||||
ret_dict.set_item("id", val.id)?;
|
|
||||||
ret.set_item(key, (diff, ret_dict).to_object(py))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(ret.to_object(py))
|
|
||||||
}
|
|
||||||
Err(e) => Err(PyErr::new::<ValueError, _>(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute a Route using the suplied parameters
|
|
||||||
#[pyfn(m, "route")]
|
|
||||||
#[text_signature = "(hops, range, mode, primary, permute, keep_first, keep_last, greedyness, precomp, path, num_workers, callback, /)"]
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn py_route(
|
|
||||||
py: Python<'static>,
|
|
||||||
hops: Vec<&str>,
|
|
||||||
range: f32,
|
|
||||||
mode: String,
|
|
||||||
primary: bool,
|
|
||||||
permute: bool,
|
|
||||||
keep_first: bool,
|
|
||||||
keep_last: bool,
|
|
||||||
greedyness: Option<f32>,
|
|
||||||
precomp: Option<String>,
|
|
||||||
path: String,
|
|
||||||
num_workers: Option<usize>,
|
|
||||||
callback: PyObject,
|
|
||||||
) -> PyResult<PyObject> {
|
|
||||||
use route::*;
|
|
||||||
let num_workers = num_workers.unwrap_or(1);
|
|
||||||
let mode = match Mode::parse(&mode) {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(PyErr::new::<ValueError, _>(e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let state_dict = PyDict::new(py);
|
|
||||||
{
|
|
||||||
let cb_res = callback.call(py, (state_dict,), None);
|
|
||||||
if cb_res.is_err() {
|
|
||||||
println!("Error: {:?}", cb_res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let callback_wrapped = move |state: &SearchState| {
|
|
||||||
state_dict.set_item("mode", state.mode.clone())?;
|
|
||||||
state_dict.set_item("system", state.system.clone())?;
|
|
||||||
state_dict.set_item("body", state.body.clone())?;
|
|
||||||
state_dict.set_item("depth", state.depth)?;
|
|
||||||
state_dict.set_item("queue_size", state.queue_size)?;
|
|
||||||
state_dict.set_item("d_rem", state.d_rem)?;
|
|
||||||
state_dict.set_item("d_total", state.d_total)?;
|
|
||||||
state_dict.set_item("prc_done", state.prc_done)?;
|
|
||||||
state_dict.set_item("n_seen", state.n_seen)?;
|
|
||||||
state_dict.set_item("prc_seen", state.prc_seen)?;
|
|
||||||
state_dict.set_item("from", state.from.clone())?;
|
|
||||||
state_dict.set_item("to", state.to.clone())?;
|
|
||||||
let cb_res = callback.call(py, (state_dict,), None);
|
|
||||||
if cb_res.is_err() {
|
|
||||||
println!("Error: {:?}", cb_res);
|
|
||||||
}
|
|
||||||
cb_res
|
|
||||||
};
|
|
||||||
let hops: Vec<SysEntry> = (hops.iter().map(|v| SysEntry::from_str(&v)).collect::<Result<Vec<SysEntry>,_>>())?;
|
|
||||||
println!("Resolving systems...");
|
|
||||||
let hops: Vec<u32> = match resolve(&hops, &PathBuf::from(&path)) {
|
|
||||||
Ok(ids) => ids,
|
|
||||||
Err(err_msg) => {
|
|
||||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let opts = RouteOpts {
|
|
||||||
systems: hops,
|
|
||||||
range: Some(range),
|
|
||||||
file_path: PathBuf::from(path),
|
|
||||||
precomp_file: precomp.map(PathBuf::from),
|
|
||||||
callback: Box::new(callback_wrapped),
|
|
||||||
mode,
|
|
||||||
factor: greedyness,
|
|
||||||
precompute: false,
|
|
||||||
permute,
|
|
||||||
keep_first,
|
|
||||||
keep_last,
|
|
||||||
primary,
|
|
||||||
workers: num_workers,
|
|
||||||
};
|
|
||||||
match route(opts) {
|
|
||||||
Ok(Some(route)) => {
|
|
||||||
let hops = route.iter().map(|hop| {
|
|
||||||
let pos = PyList::new(py, hop.pos.iter());
|
|
||||||
let elem = PyDict::new(py);
|
|
||||||
elem.set_item("star_type", hop.star_type.clone()).unwrap();
|
|
||||||
elem.set_item("system", hop.system.clone()).unwrap();
|
|
||||||
elem.set_item("body", hop.body.clone()).unwrap();
|
|
||||||
elem.set_item("distance", hop.distance).unwrap();
|
|
||||||
elem.set_item("pos", pos).unwrap();
|
|
||||||
elem
|
|
||||||
});
|
|
||||||
let lst = PyList::new(py, hops);
|
|
||||||
Ok(lst.to_object(py))
|
|
||||||
}
|
|
||||||
Ok(None) => Ok(py.None()),
|
|
||||||
Err(e) => Err(PyErr::new::<ValueError, _>(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
use crate::common::{EdLrrError, EdLrrResult, System};
|
||||||
|
use crate::info;
|
||||||
|
use csv_core::{ReadFieldResult, Reader};
|
||||||
|
use memmap::Mmap;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub fn mmap_csv(path: &Path, query: Vec<String>) -> Result<HashMap<String, Option<u32>>, String> {
|
||||||
|
let file = File::open(path).map_err(|e| e.to_string())?;
|
||||||
|
let mm = unsafe { Mmap::map(&file) }.map_err(|e| e.to_string())?;
|
||||||
|
let mut best = query
|
||||||
|
.iter()
|
||||||
|
.map(|s| (s, (s.as_bytes(), usize::MAX, u32::MAX)))
|
||||||
|
.collect::<Vec<(&String, (_, usize, u32))>>();
|
||||||
|
let t_start = std::time::Instant::now();
|
||||||
|
let dist = eddie::slice::DamerauLevenshtein::new();
|
||||||
|
let mut row = 0;
|
||||||
|
{
|
||||||
|
let mut data = &mm[..];
|
||||||
|
let mut rdr = Reader::new();
|
||||||
|
let mut field = [0; 1024];
|
||||||
|
let mut fieldidx = 0;
|
||||||
|
loop {
|
||||||
|
let (result, nread, nwrite) = rdr.read_field(data, &mut field);
|
||||||
|
data = &data[nread..];
|
||||||
|
let field = &field[..nwrite];
|
||||||
|
match result {
|
||||||
|
ReadFieldResult::InputEmpty => {}
|
||||||
|
ReadFieldResult::OutputFull => {
|
||||||
|
return Err("Encountered field larget than 1024 bytes!".to_string());
|
||||||
|
}
|
||||||
|
ReadFieldResult::Field { record_end } => {
|
||||||
|
if fieldidx == 1 {
|
||||||
|
for (_, (name_b, best_dist, id)) in best.iter_mut() {
|
||||||
|
let d = dist.distance(name_b, field);
|
||||||
|
if d < *best_dist {
|
||||||
|
*best_dist = d;
|
||||||
|
*id = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record_end {
|
||||||
|
fieldidx = 0;
|
||||||
|
row += 1;
|
||||||
|
} else {
|
||||||
|
fieldidx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This case happens when the CSV reader has successfully exhausted
|
||||||
|
// all input.
|
||||||
|
ReadFieldResult::End => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let search_result = best
|
||||||
|
.drain(..)
|
||||||
|
.map(|(query_name, (_, _, idx))| (query_name.clone(), Some(idx)))
|
||||||
|
.collect::<HashMap<String, Option<u32>>>();
|
||||||
|
let rate = (row as f64) / t_start.elapsed().as_secs_f64();
|
||||||
|
info!(
|
||||||
|
"Took: {:.2?}, {:.2} systems/second",
|
||||||
|
t_start.elapsed(),
|
||||||
|
rate
|
||||||
|
);
|
||||||
|
Ok(search_result)
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
#![cfg(feature = "profiling")]
|
||||||
|
use tracing::subscriber::set_global_default;
|
||||||
|
pub use tracing::{debug, error, info, span, trace, warn, Level};
|
||||||
|
pub use tracing::{debug_span, error_span, info_span, trace_span, warn_span};
|
||||||
|
use tracing_chrome::ChromeLayerBuilder;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::Registry;
|
||||||
|
use tracing_tracy::TracyLayer;
|
||||||
|
pub use tracy_client::ProfiledAllocator;
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
let (chrome_layer, _guard) = ChromeLayerBuilder::new().build();
|
||||||
|
let subscriber = Registry::default().with(chrome_layer);
|
||||||
|
set_global_default(subscriber).expect("setting default subscriber failed");
|
||||||
|
}
|
1992
rust/src/route.rs
1992
rust/src/route.rs
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::common::TreeNode;
|
||||||
|
use crate::route::{Router, SearchState};
|
||||||
|
use fnv::FnvHashMap;
|
||||||
|
|
||||||
|
trait SearchAlgoImpl<State = (), Weight: Ord = ()> {
|
||||||
|
fn get_weight(&mut self, systems: &TreeNode, router: &Router) -> Option<Weight>;
|
||||||
|
|
||||||
|
fn get_neighbors(
|
||||||
|
&mut self,
|
||||||
|
system: &TreeNode,
|
||||||
|
router: &Router,
|
||||||
|
range: f32,
|
||||||
|
) -> Vec<(Weight, TreeNode)> {
|
||||||
|
let mut ret = vec![];
|
||||||
|
for nb in router.neighbours(system, range) {
|
||||||
|
if let Some(w) = self.get_weight(nb, router) {
|
||||||
|
ret.push((w, *nb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchAlgo<'a> {
|
||||||
|
algo: Box<dyn SearchAlgoImpl>,
|
||||||
|
prev: FnvHashMap<u32, u32>,
|
||||||
|
state: Option<SearchState>,
|
||||||
|
router: &'a Router,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BFS(usize);
|
||||||
|
|
||||||
|
impl SearchAlgoImpl for BFS {
|
||||||
|
fn get_weight(&mut self, _system: &TreeNode, _router: &Router) -> Option<()> {
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SearchAlgo<'a> {
|
||||||
|
fn new(router: &'a Router, algo: Box<dyn SearchAlgoImpl>) -> Self {
|
||||||
|
Self {
|
||||||
|
algo,
|
||||||
|
prev: FnvHashMap::default(),
|
||||||
|
state: None,
|
||||||
|
router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test(&mut self) {
|
||||||
|
// self.algo.get_neighbors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
a = 1 - acos(dot(u/Length(u),v/Length(v)))/PI
|
||||||
|
*/
|
|
@ -1,5 +1,7 @@
|
||||||
|
//! Ship fuel consumption and jump range calculations
|
||||||
use crate::common::get_fsd_booster_info;
|
use crate::common::get_fsd_booster_info;
|
||||||
use crate::journal::*;
|
use crate::journal::*;
|
||||||
|
use eyre::Result;
|
||||||
use pyo3::conversion::ToPyObject;
|
use pyo3::conversion::ToPyObject;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyDict;
|
use pyo3::types::PyDict;
|
||||||
|
@ -10,21 +12,25 @@ use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Frame Shift Drive information
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct FSD {
|
pub struct FSD {
|
||||||
|
/// Rating
|
||||||
pub rating_val: f32,
|
pub rating_val: f32,
|
||||||
|
/// Class
|
||||||
pub class_val: f32,
|
pub class_val: f32,
|
||||||
|
/// Optimized Mass
|
||||||
pub opt_mass: f32,
|
pub opt_mass: f32,
|
||||||
|
/// Max fuel per jump
|
||||||
pub max_fuel: f32,
|
pub max_fuel: f32,
|
||||||
|
/// Boost factor
|
||||||
pub boost: f32,
|
pub boost: f32,
|
||||||
|
/// Guardian booster bonus range
|
||||||
pub guardian_booster: f32,
|
pub guardian_booster: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
pub name: String,
|
|
||||||
pub ident: String,
|
|
||||||
pub ship_type: String,
|
|
||||||
pub base_mass: f32,
|
pub base_mass: f32,
|
||||||
pub fuel_mass: f32,
|
pub fuel_mass: f32,
|
||||||
pub fuel_capacity: f32,
|
pub fuel_capacity: f32,
|
||||||
|
@ -33,9 +39,6 @@ pub struct Ship {
|
||||||
|
|
||||||
impl Ship {
|
impl Ship {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: String,
|
|
||||||
ident: String,
|
|
||||||
ship_type: String,
|
|
||||||
base_mass: f32,
|
base_mass: f32,
|
||||||
fuel_mass: f32,
|
fuel_mass: f32,
|
||||||
fuel_capacity: f32,
|
fuel_capacity: f32,
|
||||||
|
@ -57,15 +60,12 @@ impl Ship {
|
||||||
if fsd_type.1 < 2 || fsd_type.1 > 8 {
|
if fsd_type.1 < 2 || fsd_type.1 > 8 {
|
||||||
return Err(format!("Invalid class: {}", fsd_type.1));
|
return Err(format!("Invalid class: {}", fsd_type.1));
|
||||||
};
|
};
|
||||||
|
|
||||||
if guardian_booster!=0 {
|
if guardian_booster != 0 {
|
||||||
return Err("Guardian booster not yet implemented!".to_owned())
|
return Err("Guardian booster not yet implemented!".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = Self {
|
let ret = Self {
|
||||||
name,
|
|
||||||
ident,
|
|
||||||
ship_type,
|
|
||||||
fuel_capacity,
|
fuel_capacity,
|
||||||
fuel_mass,
|
fuel_mass,
|
||||||
base_mass,
|
base_mass,
|
||||||
|
@ -81,8 +81,8 @@ impl Ship {
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_json(data: &str) -> Result<Self, String> {
|
pub fn new_from_json(data: &str) -> Result<(String, Ship), String> {
|
||||||
match serde_json::from_str::<Event>(&data) {
|
match serde_json::from_str::<Event>(data) {
|
||||||
Ok(Event {
|
Ok(Event {
|
||||||
event: EventData::Unknown,
|
event: EventData::Unknown,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -101,7 +101,7 @@ impl Ship {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_journal() -> Result<HashMap<String, Self>, String> {
|
pub fn new_from_journal() -> Result<HashMap<String, Ship>, String> {
|
||||||
let mut ret = HashMap::new();
|
let mut ret = HashMap::new();
|
||||||
let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap();
|
let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap();
|
||||||
let mut journals: Vec<PathBuf> = Vec::new();
|
let mut journals: Vec<PathBuf> = Vec::new();
|
||||||
|
@ -110,12 +110,10 @@ impl Ship {
|
||||||
userprofile.push("Frontier Developments");
|
userprofile.push("Frontier Developments");
|
||||||
userprofile.push("Elite Dangerous");
|
userprofile.push("Elite Dangerous");
|
||||||
if let Ok(iter) = userprofile.read_dir() {
|
if let Ok(iter) = userprofile.read_dir() {
|
||||||
for entry in iter {
|
for entry in iter.flatten() {
|
||||||
if let Ok(entry) = entry {
|
if re.is_match(&entry.file_name().to_string_lossy()) {
|
||||||
if re.is_match(&entry.file_name().to_string_lossy()) {
|
journals.push(entry.path());
|
||||||
journals.push(entry.path());
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
journals.sort();
|
journals.sort();
|
||||||
|
@ -133,16 +131,7 @@ impl Ship {
|
||||||
}) => {}
|
}) => {}
|
||||||
Ok(ev) => {
|
Ok(ev) => {
|
||||||
if let Some(loadout) = ev.get_loadout() {
|
if let Some(loadout) = ev.get_loadout() {
|
||||||
let mut ship = loadout.try_into_ship()?;
|
let (key, ship) = loadout.try_into_ship()?;
|
||||||
if ship.name == "" {
|
|
||||||
ship.name = "<NO NAME>".to_owned();
|
|
||||||
}
|
|
||||||
let key = format!(
|
|
||||||
"[{}] {} ({})",
|
|
||||||
ship.ident,
|
|
||||||
ship.name,
|
|
||||||
ship.ship_type.to_ascii_lowercase()
|
|
||||||
);
|
|
||||||
ret.insert(key, ship);
|
ret.insert(key, ship);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +168,7 @@ impl Ship {
|
||||||
Some(cost)
|
Some(cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jump_range(&self, fuel: f32, booster: bool) -> f32 {
|
pub fn jump_range(&self, fuel: f32, booster: bool) -> f32 {
|
||||||
let mass = self.base_mass + fuel;
|
let mass = self.base_mass + fuel;
|
||||||
let mut fuel = self.fsd.max_fuel.min(fuel);
|
let mut fuel = self.fsd.max_fuel.min(fuel);
|
||||||
if booster {
|
if booster {
|
||||||
|
@ -198,6 +187,10 @@ impl Ship {
|
||||||
return self.jump_range(self.fuel_mass, true);
|
return self.jump_range(self.fuel_mass, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn full_range(&self) -> f32 {
|
||||||
|
return self.jump_range(self.fuel_capacity, true);
|
||||||
|
}
|
||||||
|
|
||||||
fn boost_fuel_mult(&self) -> f32 {
|
fn boost_fuel_mult(&self) -> f32 {
|
||||||
if self.fsd.guardian_booster == 0.0 {
|
if self.fsd.guardian_booster == 0.0 {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
|
@ -208,6 +201,21 @@ impl Ship {
|
||||||
return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val);
|
return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fuel_cost_for_jump(&self, fuel_mass: f32, dist: f32, boost: f32) -> Option<(f32, f32)> {
|
||||||
|
if dist == 0.0 {
|
||||||
|
return Some((0.0, 0.0));
|
||||||
|
}
|
||||||
|
let mass = self.base_mass + fuel_mass;
|
||||||
|
let opt_mass = self.fsd.opt_mass * boost;
|
||||||
|
let base_cost = (dist * mass) / opt_mass;
|
||||||
|
let fuel_cost = (self.fsd.rating_val * 0.001 * base_cost.powf(self.fsd.class_val))
|
||||||
|
/ self.boost_fuel_mult();
|
||||||
|
if fuel_cost > self.fsd.max_fuel || fuel_cost > fuel_mass {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
return Some((fuel_cost, fuel_mass - fuel_cost));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fuel_cost(&self, d: f32) -> f32 {
|
pub fn fuel_cost(&self, d: f32) -> f32 {
|
||||||
if d == 0.0 {
|
if d == 0.0 {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
@ -220,29 +228,6 @@ impl Ship {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)]
|
|
||||||
pub struct FSD {
|
|
||||||
pub rating_val: f32,
|
|
||||||
pub class_val: f32,
|
|
||||||
pub opt_mass: f32,
|
|
||||||
pub max_fuel: f32,
|
|
||||||
pub boost: f32,
|
|
||||||
pub guardian_booster: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)]
|
|
||||||
pub struct Ship {
|
|
||||||
pub name: String,
|
|
||||||
pub ident: String,
|
|
||||||
pub ship_type: String,
|
|
||||||
pub base_mass: f32,
|
|
||||||
pub fuel_mass: f32,
|
|
||||||
pub fuel_capacity: f32,
|
|
||||||
pub fsd: FSD,
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
impl FSD {
|
impl FSD {
|
||||||
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
||||||
let elem = PyDict::new(py);
|
let elem = PyDict::new(py);
|
||||||
|
@ -259,9 +244,6 @@ impl FSD {
|
||||||
impl Ship {
|
impl Ship {
|
||||||
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
||||||
let elem = PyDict::new(py);
|
let elem = PyDict::new(py);
|
||||||
elem.set_item("name", self.name.clone())?;
|
|
||||||
elem.set_item("ident", self.ident.clone())?;
|
|
||||||
elem.set_item("ship_type", self.ship_type.clone())?;
|
|
||||||
elem.set_item("base_mass", self.base_mass)?;
|
elem.set_item("base_mass", self.base_mass)?;
|
||||||
elem.set_item("fuel_mass", self.fuel_mass)?;
|
elem.set_item("fuel_mass", self.fuel_mass)?;
|
||||||
elem.set_item("fuel_capacity", self.fuel_capacity)?;
|
elem.set_item("fuel_capacity", self.fuel_capacity)?;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
#[inline(always)]
|
||||||
|
fn veclen(v: &[f32; 3]) -> f32 {
|
||||||
|
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
let dx = p1[0] - p2[0];
|
||||||
|
let dy = p1[1] - p2[1];
|
||||||
|
let dz = p1[2] - p2[2];
|
||||||
|
|
||||||
|
dx * dx + dy * dy + dz * dz
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
dist2(p1, p2).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn distm(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
|
||||||
|
let dx = (p1[0] - p2[0]).abs();
|
||||||
|
let dy = (p1[1] - p2[1]).abs();
|
||||||
|
let dz = (p1[2] - p2[2]).abs();
|
||||||
|
dx + dy + dz
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dot product (cosine of angle) between two 3D vectors
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot_vec_dist(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let z: [f32; 3] = [0.0; 3];
|
||||||
|
let lm = dist(u, &z) * dist(v, &z);
|
||||||
|
((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dot product (cosine of angle) between two 3D vectors
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot_vec_len(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let lm = veclen(u) * veclen(v);
|
||||||
|
((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ndot_iter(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
|
let l_1: f32 = u.iter().map(|e| e * e).sum();
|
||||||
|
let l_2: f32 = v.iter().map(|e| e * e).sum();
|
||||||
|
let lm = (l_1 * l_2).sqrt();
|
||||||
|
let mut ret = 0.0;
|
||||||
|
for (a, b) in u.iter().zip(v.iter()) {
|
||||||
|
ret += a * b;
|
||||||
|
}
|
||||||
|
ret / lm
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod dot_impl_tests {
|
||||||
|
#[test]
|
||||||
|
fn test_dot_impls() {
|
||||||
|
use super::*;
|
||||||
|
let v1 = [1.0, 2.0, 3.0];
|
||||||
|
let v2 = [4.0, 5.0, 6.0];
|
||||||
|
let d1 = ndot_vec_dist(&v1, &v2);
|
||||||
|
let d2 = ndot_vec_len(&v1, &v2);
|
||||||
|
let d3 = ndot_iter(&v1, &v2);
|
||||||
|
assert!((d1 - d2) < 0.01);
|
||||||
|
assert!((d2 - d3) < 0.01);
|
||||||
|
assert!((d3 - d1) < 0.01);
|
||||||
|
}
|
||||||
|
}
|
170
setup.py
170
setup.py
|
@ -1,76 +1,82 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
from setuptools_rust import Binding, RustExtension, Strip
|
from setuptools_rust import Binding, RustExtension, Strip
|
||||||
with open('README.md', 'r') as fh:
|
import os
|
||||||
|
|
||||||
|
with open("README.md", "r") as fh:
|
||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
extras_require = {
|
extras_require = {
|
||||||
'build': ['pyinstaller', 'pywin32'],
|
"build": ["pyinstaller", "pywin32"],
|
||||||
'test': [
|
"test": [
|
||||||
'pytest',
|
"pytest",
|
||||||
'pytest-cov',
|
"pytest-cov",
|
||||||
'pytest-dependency',
|
"pytest-dependency",
|
||||||
'pytest-benchmark[histogram]',
|
"pytest-benchmark[histogram]",
|
||||||
'pytest-metadata',
|
"pytest-metadata",
|
||||||
'pytest-flake8',
|
"pytest-flake8",
|
||||||
'pytest-flask',
|
"pytest-flask",
|
||||||
'pytest-mock',
|
"pytest-mock",
|
||||||
'pytest-flask-sqlalchemy',
|
"pytest-flask-sqlalchemy",
|
||||||
'pytest-steps',
|
"pytest-steps",
|
||||||
'pytest-xdist',
|
"pytest-xdist",
|
||||||
'flake8-bugbear',
|
"flake8-bugbear",
|
||||||
'flake8-comprehensions',
|
"flake8-comprehensions",
|
||||||
'cohesion',
|
"cohesion",
|
||||||
'hypothesis',
|
"hypothesis",
|
||||||
'flaky'
|
"flaky",
|
||||||
],
|
],
|
||||||
'dev': [
|
"dev": [
|
||||||
'black; python_version >= "3.6"',
|
'black; python_version >= "3.6"',
|
||||||
'jinja2',
|
"jinja2",
|
||||||
'tsp',
|
"tsp",
|
||||||
'flake8',
|
"flake8",
|
||||||
'flake8-bugbear',
|
"flake8-bugbear",
|
||||||
'flake8-comprehensions',
|
"flake8-comprehensions",
|
||||||
'cohesion',
|
"cohesion",
|
||||||
'pre-commit',
|
"pre-commit",
|
||||||
'ipython',
|
"ipython",
|
||||||
'flask-konch',
|
"flask-konch",
|
||||||
'setuptools_rust'
|
"setuptools_rust",
|
||||||
],
|
],
|
||||||
'gui': ['PyQt5', 'pyperclip'],
|
"gui": ["PyQt5", "pyperclip"],
|
||||||
'web': [
|
"web": [
|
||||||
'flask',
|
"flask",
|
||||||
'gevent',
|
"gevent",
|
||||||
'webargs',
|
"webargs",
|
||||||
'flask-executor',
|
"flask-executor",
|
||||||
'flask-wtf',
|
"flask-wtf",
|
||||||
'flask-user',
|
"flask-user",
|
||||||
'flask-debugtoolbar',
|
"flask-debugtoolbar",
|
||||||
'flask-bootstrap4',
|
"flask-bootstrap4",
|
||||||
'flask-sqlalchemy',
|
"flask-sqlalchemy",
|
||||||
'flask-nav',
|
"flask-nav",
|
||||||
'flask-admin',
|
"flask-admin",
|
||||||
'sqlalchemy_utils[password]',
|
"sqlalchemy_utils[password]",
|
||||||
'python-dotenv',
|
"python-dotenv",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
extras_require['all'] = sorted(set(sum(extras_require.values(), [])))
|
extras_require["all"] = sorted(set(sum(extras_require.values(), [])))
|
||||||
|
|
||||||
|
# os.environ["RUSTC_WRAPPER"]='"{}" /c echo'.format(os.environ['COMSPEC'])
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
use_scm_version={'write_to': '__version__.py'},
|
use_scm_version={"write_to": "__version__.py"},
|
||||||
name='ed_lrr_gui',
|
name="ed_lrr_gui",
|
||||||
author='Daniel Seiller',
|
author="Daniel Seiller",
|
||||||
author_email='earthnuker@gmail.com',
|
author_email="earthnuker@gmail.com",
|
||||||
description='Elite: Dangerous long range route plotter',
|
description="Elite: Dangerous long range route plotter",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type="text/markdown",
|
||||||
url='https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui',
|
url="https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui",
|
||||||
rust_extensions=[
|
rust_extensions=[
|
||||||
RustExtension(
|
RustExtension(
|
||||||
'_ed_lrr',
|
"_ed_lrr",
|
||||||
path='rust/Cargo.toml',
|
path="rust/Cargo.toml",
|
||||||
binding=Binding.PyO3,
|
binding=Binding.PyO3,
|
||||||
strip=Strip.No,
|
strip=Strip.No,
|
||||||
|
rustc_flags=["--emit=asm"],
|
||||||
|
# features=["profiling"],
|
||||||
debug=False,
|
debug=False,
|
||||||
native=True,
|
native=True,
|
||||||
quiet=True,
|
quiet=True,
|
||||||
|
@ -78,38 +84,38 @@ setup(
|
||||||
],
|
],
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': ['ed_lrr = ed_lrr_gui.__main__:main'],
|
"console_scripts": ["ed_lrr = ed_lrr_gui.__main__:main"],
|
||||||
'gui_scripts': ['ed_lrr_gui = ed_lrr_gui.__main__:gui_main']
|
"gui_scripts": ["ed_lrr_gui = ed_lrr_gui.__main__:gui_main"],
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'appdirs',
|
"appdirs",
|
||||||
'PyYAML',
|
"PyYAML",
|
||||||
'requests',
|
"requests",
|
||||||
'python-dateutil',
|
"python-dateutil",
|
||||||
'click',
|
"click",
|
||||||
'tqdm',
|
"tqdm",
|
||||||
'click-default-group',
|
"click-default-group",
|
||||||
'profig',
|
"profig",
|
||||||
'ujson',
|
"ujson",
|
||||||
'colorama',
|
"colorama",
|
||||||
'svgwrite',
|
"svgwrite",
|
||||||
|
"coloredlogs",
|
||||||
],
|
],
|
||||||
setup_requires=['setuptools', 'setuptools-rust',
|
setup_requires=["setuptools", "setuptools-rust", "setuptools-scm", "wheel"],
|
||||||
'setuptools-scm', 'wheel'],
|
dependency_links=["https://github.com/Nuitka/Nuitka/archive/develop.zip"],
|
||||||
dependency_links=['https://github.com/Nuitka/Nuitka/archive/develop.zip'],
|
|
||||||
extras_require=extras_require,
|
extras_require=extras_require,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'License :: OSI Approved :: MIT License',
|
"License :: OSI Approved :: MIT License",
|
||||||
'Programming Language :: Rust',
|
"Programming Language :: Rust",
|
||||||
'Programming Language :: Python',
|
"Programming Language :: Python",
|
||||||
'Programming Language :: Python :: 3',
|
"Programming Language :: Python :: 3",
|
||||||
'Programming Language :: Python :: 3.5',
|
"Programming Language :: Python :: 3.5",
|
||||||
'Programming Language :: Python :: 3.6',
|
"Programming Language :: Python :: 3.6",
|
||||||
'Programming Language :: Python :: 3.7',
|
"Programming Language :: Python :: 3.7",
|
||||||
'Programming Language :: Python :: 3.8',
|
"Programming Language :: Python :: 3.8",
|
||||||
'Programming Language :: Python :: Implementation :: CPython',
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
'Operating System :: Windows',
|
"Operating System :: Windows",
|
||||||
'Operating System :: Linux',
|
"Operating System :: Linux",
|
||||||
],
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import coloredlogs
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
|
||||||
|
|
||||||
|
|
||||||
|
class DeltaTimeFormatter(coloredlogs.ColoredFormatter):
|
||||||
|
def format(self, record):
|
||||||
|
seconds = record.relativeCreated / 1000
|
||||||
|
duration = timedelta(seconds=seconds)
|
||||||
|
record.delta = str(duration)
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
coloredlogs.ColoredFormatter = DeltaTimeFormatter
|
||||||
|
logfmt = " | ".join(
|
||||||
|
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
|
||||||
|
)
|
||||||
|
loglevel = "info"
|
||||||
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
|
if not isinstance(numeric_level, int):
|
||||||
|
raise ValueError("Invalid log level: %s" % loglevel)
|
||||||
|
coloredlogs.install(level=numeric_level, fmt=logfmt)
|
||||||
|
sys.path.append(".")
|
||||||
|
import _ed_lrr
|
||||||
|
|
||||||
|
ships = _ed_lrr.PyShip.from_journal()
|
||||||
|
r = _ed_lrr.PyRouter(print)
|
||||||
|
r.load("stars.csv")
|
||||||
|
ship = max(ships.values(), key=lambda s: s.max_range)
|
||||||
|
system_names = ["Sol", "Colonia"]
|
||||||
|
systems = r.resolve(*system_names)
|
||||||
|
sys_ids = {k: v["id"] for k, v in systems.items()}
|
||||||
|
|
||||||
|
route = r.route(
|
||||||
|
[sys_ids[system_names[0]], sys_ids[system_names[1]]],
|
||||||
|
7.84,
|
||||||
|
{"mode": "bfs","greedyness":0.0},
|
||||||
|
)
|
||||||
|
|
||||||
|
for n,s in enumerate(route,1):
|
||||||
|
print(n,s)
|
|
@ -0,0 +1,289 @@
|
||||||
|
{
|
||||||
|
"timestamp": "2019-09-25T21:29:51Z",
|
||||||
|
"event": "Loadout",
|
||||||
|
"Ship": "asp",
|
||||||
|
"ShipID": 0,
|
||||||
|
"ShipName": "Nightmaregreen_N",
|
||||||
|
"ShipIdent": "NMGR_N",
|
||||||
|
"HullValue": 6144793,
|
||||||
|
"ModulesValue": 33042643,
|
||||||
|
"HullHealth": 1.000000,
|
||||||
|
"UnladenMass": 347.200012,
|
||||||
|
"CargoCapacity": 0,
|
||||||
|
"MaxJumpRange": 56.372398,
|
||||||
|
"FuelCapacity": {
|
||||||
|
"Main": 64.000000,
|
||||||
|
"Reserve": 0.630000
|
||||||
|
},
|
||||||
|
"Rebuy": 1959374,
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Slot": "ShipCockpit",
|
||||||
|
"Item": "asp_cockpit",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "CargoHatch",
|
||||||
|
"Item": "modularcargobaydoor",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint1",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"AmmoInClip": 1,
|
||||||
|
"AmmoInHopper": 2,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint2",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"AmmoInClip": 1,
|
||||||
|
"AmmoInHopper": 2,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint3",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"AmmoInClip": 1,
|
||||||
|
"AmmoInHopper": 2,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint4",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"AmmoInClip": 1,
|
||||||
|
"AmmoInHopper": 2,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "PaintJob",
|
||||||
|
"Item": "paintjob_asp_operator_red",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Armour",
|
||||||
|
"Item": "asp_armour_grade1",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "PowerPlant",
|
||||||
|
"Item": "int_powerplant_size5_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "MainEngines",
|
||||||
|
"Item": "int_engine_size4_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "FrameShiftDrive",
|
||||||
|
"Item": "int_hyperdrive_size5_class5",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000,
|
||||||
|
"Engineering": {
|
||||||
|
"Engineer": "Felicity Farseer",
|
||||||
|
"EngineerID": 300100,
|
||||||
|
"BlueprintID": 128673694,
|
||||||
|
"BlueprintName": "FSD_LongRange",
|
||||||
|
"Level": 5,
|
||||||
|
"Quality": 1.000000,
|
||||||
|
"ExperimentalEffect": "special_fsd_heavy",
|
||||||
|
"ExperimentalEffect_Localised": "Mass Manager",
|
||||||
|
"Modifiers": [
|
||||||
|
{
|
||||||
|
"Label": "Mass",
|
||||||
|
"Value": 26.000000,
|
||||||
|
"OriginalValue": 20.000000,
|
||||||
|
"LessIsGood": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Label": "Integrity",
|
||||||
|
"Value": 93.840004,
|
||||||
|
"OriginalValue": 120.000000,
|
||||||
|
"LessIsGood": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Label": "PowerDraw",
|
||||||
|
"Value": 0.690000,
|
||||||
|
"OriginalValue": 0.600000,
|
||||||
|
"LessIsGood": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Label": "FSDOptimalMass",
|
||||||
|
"Value": 1692.599976,
|
||||||
|
"OriginalValue": 1050.000000,
|
||||||
|
"LessIsGood": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "LifeSupport",
|
||||||
|
"Item": "int_lifesupport_size4_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "PowerDistributor",
|
||||||
|
"Item": "int_powerdistributor_size4_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Radar",
|
||||||
|
"Item": "int_sensors_size5_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "FuelTank",
|
||||||
|
"Item": "int_fueltank_size5_class3",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Decal1",
|
||||||
|
"Item": "decal_explorer_starblazer",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Decal2",
|
||||||
|
"Item": "decal_explorer_starblazer",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Decal3",
|
||||||
|
"Item": "decal_explorer_starblazer",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "ShipName0",
|
||||||
|
"Item": "nameplate_shipname_white",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "ShipName1",
|
||||||
|
"Item": "nameplate_shipname_white",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "ShipID0",
|
||||||
|
"Item": "nameplate_shipid_white",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "ShipID1",
|
||||||
|
"Item": "nameplate_shipid_white",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot01_Size6",
|
||||||
|
"Item": "int_fuelscoop_size6_class5",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot02_Size5",
|
||||||
|
"Item": "int_fueltank_size5_class3",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot03_Size3",
|
||||||
|
"Item": "int_repairer_size3_class5",
|
||||||
|
"On": false,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot04_Size3",
|
||||||
|
"Item": "int_shieldgenerator_size3_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot05_Size3",
|
||||||
|
"Item": "int_buggybay_size2_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot06_Size2",
|
||||||
|
"Item": "int_detailedsurfacescanner_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot07_Size2",
|
||||||
|
"Item": "int_dockingcomputer_standard",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot08_Size1",
|
||||||
|
"Item": "int_supercruiseassist",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "PlanetaryApproachSuite",
|
||||||
|
"Item": "int_planetapproachsuite",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "VesselVoice",
|
||||||
|
"Item": "voicepack_eden",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Health": 1.000000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
{
|
||||||
|
"timestamp": "2019-09-25T21:29:51Z",
|
||||||
|
"event": "Loadout",
|
||||||
|
"Ship": "asp",
|
||||||
|
"ShipName": "Nightmaregreen_G",
|
||||||
|
"ShipIdent": "NMGR_G",
|
||||||
|
"HullValue": 6144793,
|
||||||
|
"ModulesValue": 33181682,
|
||||||
|
"UnladenMass": 348.500061,
|
||||||
|
"CargoCapacity": 0,
|
||||||
|
"MaxJumpRange": 60.164637,
|
||||||
|
"FuelCapacity": {
|
||||||
|
"Main": 64,
|
||||||
|
"Reserve": 0.63
|
||||||
|
},
|
||||||
|
"Rebuy": 1966323,
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Slot": "CargoHatch",
|
||||||
|
"Item": "modularcargobaydoor",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint1",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 3071
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint2",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 3071
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint3",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 3071
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "TinyHardpoint4",
|
||||||
|
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 3071
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Armour",
|
||||||
|
"Item": "asp_armour_grade1",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "PowerPlant",
|
||||||
|
"Item": "int_powerplant_size5_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Value": 140523
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "MainEngines",
|
||||||
|
"Item": "int_engine_size4_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 52325
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "FrameShiftDrive",
|
||||||
|
"Item": "int_hyperdrive_size5_class5",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 4478716,
|
||||||
|
"Engineering": {
|
||||||
|
"BlueprintName": "FSD_LongRange",
|
||||||
|
"Level": 5,
|
||||||
|
"Quality": 1,
|
||||||
|
"ExperimentalEffect": "special_fsd_heavy",
|
||||||
|
"Modifiers": [
|
||||||
|
{
|
||||||
|
"Label": "Mass",
|
||||||
|
"Value": 26.000061,
|
||||||
|
"OriginalValue": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Label": "Integrity",
|
||||||
|
"Value": 93.839832,
|
||||||
|
"OriginalValue": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Label": "PowerDraw",
|
||||||
|
"Value": 0.690001,
|
||||||
|
"OriginalValue": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Label": "FSDOptimalMass",
|
||||||
|
"Value": 1692.58667,
|
||||||
|
"OriginalValue": 1050
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "LifeSupport",
|
||||||
|
"Item": "int_lifesupport_size4_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 24895
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "PowerDistributor",
|
||||||
|
"Item": "int_powerdistributor_size4_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 24895
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Radar",
|
||||||
|
"Item": "int_sensors_size5_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 69709
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "FuelTank",
|
||||||
|
"Item": "int_fueltank_size5_class3",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Value": 85776
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot01_Size6",
|
||||||
|
"Item": "int_fuelscoop_size6_class5",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 25240068
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot02_Size5",
|
||||||
|
"Item": "int_fueltank_size5_class3",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 1,
|
||||||
|
"Value": 85776
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot03_Size3",
|
||||||
|
"Item": "int_repairer_size3_class5",
|
||||||
|
"On": false,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 2302911
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot04_Size3",
|
||||||
|
"Item": "int_shieldgenerator_size3_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 16506
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot05_Size3",
|
||||||
|
"Item": "int_buggybay_size2_class2",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 18954
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot06_Size2",
|
||||||
|
"Item": "int_detailedsurfacescanner_tiny",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 219375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot07_Size2",
|
||||||
|
"Item": "int_dockingcomputer_standard",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 3949
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Slot": "Slot08_Size1",
|
||||||
|
"Item": "int_guardianfsdbooster_size1",
|
||||||
|
"On": true,
|
||||||
|
"Priority": 0,
|
||||||
|
"Value": 405020
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -79,18 +79,18 @@ class Test_PyRouter(object): # noqa: H601
|
||||||
err = "Failed to resolve {}".format(name)
|
err = "Failed to resolve {}".format(name)
|
||||||
assert name in resolved_systems, err
|
assert name in resolved_systems, err
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["Test_ED_LRR::test_load_and_resolve"])
|
@pytest.mark.dependency(depends=["Test_PyRouter::test_load_and_resolve"])
|
||||||
|
@flaky(max_runs=10, min_passes=5)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"greedyness", greedyness, ids=lambda v: "greedyness:{}".format(v)
|
"greedyness", greedyness, ids=lambda v: "greedyness:{}".format(v)
|
||||||
)
|
)
|
||||||
@flaky(max_runs=10, min_passes=5)
|
|
||||||
def test_zero_range_fails(self, py_router, greedyness):
|
def test_zero_range_fails(self, py_router, greedyness):
|
||||||
r, resolved_systems = py_router
|
r, resolved_systems = py_router
|
||||||
waypoints = random.sample(list(resolved_systems.values()), k=2)
|
waypoints = random.sample(list(resolved_systems.values()), k=2)
|
||||||
err = pytest.raises(RuntimeError, r.route, waypoints, 0, greedyness)
|
err = pytest.raises(RuntimeError, r.route, waypoints, 0, greedyness)
|
||||||
err.match(r"No route from .* to .* found!")
|
err.match(r"No route from .* to .* found!")
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["Test_ED_LRR::test_load_and_resolve"])
|
@pytest.mark.dependency(depends=["Test_PyRouter::test_load_and_resolve"])
|
||||||
@flaky(max_runs=10, min_passes=2)
|
@flaky(max_runs=10, min_passes=2)
|
||||||
@pytest.mark.parametrize("workers", n_workers, ids=idf("workers"))
|
@pytest.mark.parametrize("workers", n_workers, ids=idf("workers"))
|
||||||
@pytest.mark.parametrize("jump_range", ranges, ids=idf("range"))
|
@pytest.mark.parametrize("jump_range", ranges, ids=idf("range"))
|
||||||
|
|
Loading…
Reference in New Issue