2022-02-23
This commit is contained in:
parent
35a0c40d14
commit
dc68cce9ed
80 changed files with 859345 additions and 4387 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -26,3 +26,4 @@ ed_lrr_gui/web/ed_lrr_web_ui.db
|
|||
__version__.py
|
||||
.nox/
|
||||
dist_vis.py
|
||||
img/**
|
6
.ipynb_checkpoints/Untitled-checkpoint.ipynb
Normal file
6
.ipynb_checkpoints/Untitled-checkpoint.ipynb
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"cells": [],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
904
.ipynb_checkpoints/heuristic_vis-checkpoint.ipynb
Normal file
904
.ipynb_checkpoints/heuristic_vis-checkpoint.ipynb
Normal file
|
@ -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
|
||||
}
|
505
.qt_for_python/uic/ed_lrr.py
Normal file
505
.qt_for_python/uic/ed_lrr.py
Normal file
|
@ -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
|
||||
|
79
.qt_for_python/uic/widget_route.py
Normal file
79
.qt_for_python/uic/widget_route.py
Normal file
|
@ -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}}$$
|
||||
|
||||
$$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
|
||||
|
|
49
TODO.md
Normal file
49
TODO.md
Normal file
|
@ -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
|
6
beam_stack_impl.py
Normal file
6
beam_stack_impl.py
Normal file
|
@ -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)
|
84
benchmark_sweep.py
Normal file
84
benchmark_sweep.py
Normal file
|
@ -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 os
|
||||
|
||||
os.environ.setdefault("CELERY_CONFIG_MODULE", "celeryconfig")
|
||||
app = Celery("ed_lrr")
|
||||
app.config_from_envvar("CELERY_CONFIG_MODULE")
|
||||
app.config_from_object(__import__("celeryconfig"))
|
||||
|
||||
|
||||
@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)
|
1
docs_mdbook/.gitignore
vendored
Normal file
1
docs_mdbook/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
|
@ -4,8 +4,8 @@
|
|||
"en"
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"markdown",
|
||||
"latex",
|
||||
"plaintext"
|
||||
"plaintext",
|
||||
"git-commit"
|
||||
]
|
||||
}
|
||||
}
|
6
docs_mdbook/.vscode/vscode-kanban.json
vendored
Normal file
6
docs_mdbook/.vscode/vscode-kanban.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"todo": [],
|
||||
"in-progress": [],
|
||||
"testing": [],
|
||||
"done": []
|
||||
}
|
22
docs_mdbook/book.toml
Normal file
22
docs_mdbook/book.toml
Normal file
|
@ -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.
|
12
docs_mdbook/src/SUMMARY.md
Normal file
12
docs_mdbook/src/SUMMARY.md
Normal file
|
@ -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)
|
1
docs_mdbook/src/ed_lrr/_index.md
Normal file
1
docs_mdbook/src/ed_lrr/_index.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Elite Dangerous Long Range Router
|
4
docs_mdbook/src/ed_lrr/graph.md
Normal file
4
docs_mdbook/src/ed_lrr/graph.md
Normal file
|
@ -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
|
31
docs_mdbook/src/ed_lrr/modes.md
Normal file
31
docs_mdbook/src/ed_lrr/modes.md
Normal file
|
@ -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}
|
||||
$$
|
2
docs_mdbook/src/ed_lrr/precomp.md
Normal file
2
docs_mdbook/src/ed_lrr/precomp.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Graph precomputation
|
||||
|
58
docs_mdbook/src/ed_lrr/py_api.md
Normal file
58
docs_mdbook/src/ed_lrr/py_api.md
Normal file
|
@ -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)
|
||||
)
|
||||
```
|
17
docs_mdbook/src/fsd.asy
Normal file
17
docs_mdbook/src/fsd.asy
Normal file
|
@ -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);
|
1
docs_mdbook/src/intro/_index.md
Normal file
1
docs_mdbook/src/intro/_index.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Intro
|
19
docs_mdbook/src/intro/ed_travel.md
Normal file
19
docs_mdbook/src/intro/ed_travel.md
Normal file
|
@ -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
|
36
docs_mdbook/src/intro/fsd_fuel.md
Normal file
36
docs_mdbook/src/intro/fsd_fuel.md
Normal file
|
@ -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
|
25
docs_mdbook/src/intro/graph_algos.md
Normal file
25
docs_mdbook/src/intro/graph_algos.md
Normal file
|
@ -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
|
47
docs_mdbook/src/range.asy
Normal file
47
docs_mdbook/src/range.asy
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
24
ed_lrr_gui.code-workspace
Normal file
24
ed_lrr_gui.code-workspace
Normal file
|
@ -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",
|
||||
metavar="<float>",
|
||||
default=cfg["route.greediness"],
|
||||
help="Greedyness factor for A-Star",
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"--mode",
|
||||
"-m",
|
||||
default=cfg["route.mode"],
|
||||
help="Search mode",
|
||||
type=click.Choice(["bfs", "bfs_old", "a-star", "greedy"]),
|
||||
help="Greedyness factor (0.0=BFS, 1.0=Greedy)",
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
|
|
108
fsd_eq.py
Normal file
108
fsd_eq.py
Normal file
|
@ -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))
|
904
heuristic_vis.ipynb
Normal file
904
heuristic_vis.ipynb
Normal file
|
@ -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 -*-
|
||||
import svgwrite
|
||||
import svgpathtools
|
||||
import random
|
||||
import tempfile
|
||||
import os
|
||||
from math import sin, cos, pi
|
||||
import tsp as m_tsp
|
||||
|
||||
|
@ -30,8 +33,6 @@ def make_points(n, size, min_dist=0):
|
|||
px, py = random.random(), random.random()
|
||||
px *= size / 2
|
||||
py *= size / 2
|
||||
px += 70
|
||||
py += 70
|
||||
valid = True
|
||||
for p in points:
|
||||
if dist2(p, (px, py)) < min_dist:
|
||||
|
@ -60,9 +61,9 @@ def generate(seed, name=None, small=False):
|
|||
size = 1000
|
||||
if name is None:
|
||||
name = seed
|
||||
dwg = svgwrite.Drawing(filename="out/{}.svg".format(name))
|
||||
dwg.defs.add(dwg.style(".background { fill: #222; }"))
|
||||
dwg.add(dwg.rect(size=("100%", "100%"), class_="background"))
|
||||
out_path = "out/{}.svg".format(name)
|
||||
dwg = svgwrite.Drawing(filename=out_path)
|
||||
dwg.defs.add(dwg.style(".background { fill: #222 }"))
|
||||
print("Generating points...")
|
||||
color = "#eee"
|
||||
pos = make_points(num_points, size, min_dist=min_dist)
|
||||
|
@ -74,12 +75,7 @@ def generate(seed, name=None, small=False):
|
|||
x2 /= sd
|
||||
y1 /= sd
|
||||
y2 /= sd
|
||||
dwg.add(dwg.line(
|
||||
(x1, y1),
|
||||
(x2, y2),
|
||||
stroke_width=w,
|
||||
stroke=color
|
||||
))
|
||||
dwg.add(dwg.line((x1, y1), (x2, y2), stroke_width=w, stroke=color))
|
||||
|
||||
for (px, py) in pos:
|
||||
base_r = 3
|
||||
|
@ -111,17 +107,13 @@ def generate(seed, name=None, small=False):
|
|||
random.random()
|
||||
random.random()
|
||||
random.random()
|
||||
random.random()
|
||||
continue
|
||||
r += ring_step(random.random())
|
||||
ring_col = color
|
||||
if random.random() > 0.75:
|
||||
ring_col = "#ea0"
|
||||
circ = dwg.add(dwg.circle(
|
||||
(px, py),
|
||||
r=r,
|
||||
stroke_width=w,
|
||||
stroke=ring_col
|
||||
))
|
||||
circ = dwg.add(dwg.circle((px, py), r=r, stroke_width=w, stroke=ring_col))
|
||||
circ.fill(color, opacity=0)
|
||||
d = random.random() * pi * 2
|
||||
dx = cos(d)
|
||||
|
@ -136,10 +128,27 @@ def generate(seed, name=None, small=False):
|
|||
)
|
||||
)
|
||||
moon.fill(ring_col)
|
||||
|
||||
dwg.save()
|
||||
dwg.fit()
|
||||
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", small=True)
|
||||
|
|
BIN
icon/out/icon_1.png
Normal file
BIN
icon/out/icon_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
|
@ -1,2 +1,2 @@
|
|||
<?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 |
BIN
icon/out/icon_1_pad.png
Normal file
BIN
icon/out/icon_1_pad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
|
@ -1,2 +1,2 @@
|
|||
<?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 |
132
imgui_test/test.py
Normal file
132
imgui_test/test.py
Normal file
|
@ -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()
|
1
logs/route_log_beam_0.json
Normal file
1
logs/route_log_beam_0.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"route": [], "dt": 292.124997}
|
839494
logs/route_log_beam_0.txt
Normal file
839494
logs/route_log_beam_0.txt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -28,14 +28,14 @@ versions += ["3"]
|
|||
nox.options.keywords = "test"
|
||||
|
||||
|
||||
@nox.session(venv_backend="conda")
|
||||
@nox.session(python=versions,venv_backend="conda")
|
||||
def devenv(session):
|
||||
"""Set up development environment"""
|
||||
global path
|
||||
location = os.path.abspath(session._runner.venv.location_name)
|
||||
session.env["PATH"] = os.pathsep.join([location, path, location])
|
||||
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}"')
|
||||
|
||||
|
||||
|
|
64
process_route_log.rs
Normal file
64
process_route_log.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
79
render_heatmap_datashader.py
Normal file
79
render_heatmap_datashader.py
Normal file
|
@ -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()
|
79
render_heatmap_img_vaex.py
Normal file
79
render_heatmap_img_vaex.py
Normal file
|
@ -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)
|
208
render_heatmap_vid_vaex.py
Normal file
208
render_heatmap_vid_vaex.py
Normal file
|
@ -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)
|
||||
|
5356
route_log_max.txt
Normal file
5356
route_log_max.txt
Normal file
File diff suppressed because it is too large
Load diff
5356
route_log_min.txt
Normal file
5356
route_log_min.txt
Normal file
File diff suppressed because it is too large
Load diff
19
rust/.vscode/settings.json
vendored
Normal file
19
rust/.vscode/settings.json
vendored
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
1660
rust/Cargo.lock
generated
1660
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
121
rust/Cargo.toml
121
rust/Cargo.toml
|
@ -1,42 +1,79 @@
|
|||
[package]
|
||||
name = "ed_lrr"
|
||||
version = "0.2.0"
|
||||
authors = [ "Daniel Seiller <earthnuker@gmail.com>",]
|
||||
edition = "2018"
|
||||
repository = "https://gitlab.com/Earthnuker/ed_lrr.git"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
crate-type = [ "cdylib",]
|
||||
name = "_ed_lrr"
|
||||
|
||||
[dependencies]
|
||||
csv = "1.1.3"
|
||||
humantime = "2.0.1"
|
||||
permutohedron = "0.2.4"
|
||||
serde_json = "1.0.55"
|
||||
fnv = "1.0.7"
|
||||
bincode = "1.2.1"
|
||||
sha3 = "0.9.0"
|
||||
byteorder = "1.3.4"
|
||||
strsim = "0.10.0"
|
||||
rstar = "0.8.0"
|
||||
crossbeam-channel = "0.4.2"
|
||||
better-panic = "0.2.0"
|
||||
derivative = "2.1.1"
|
||||
dict_derive = "0.2.0"
|
||||
num_cpus = "1.13.0"
|
||||
regex = "1.3.9"
|
||||
chrono = "0.4.11"
|
||||
|
||||
[dependencies.pyo3]
|
||||
git = "https://github.com/PyO3/pyo3"
|
||||
features = [ "extension-module",]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.112"
|
||||
features = [ "derive",]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
[package]
|
||||
name = "ed_lrr"
|
||||
version = "0.2.0"
|
||||
authors = ["Daniel Seiller <earthnuker@gmail.com>"]
|
||||
edition = "2018"
|
||||
repository = "https://gitlab.com/Earthnuker/ed_lrr.git"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
name = "_ed_lrr"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
lto = "fat"
|
||||
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.15.1", features = ["extension-module","eyre"] }
|
||||
csv = "1.1.6"
|
||||
humantime = "2.1.0"
|
||||
permutohedron = "0.2.4"
|
||||
serde_json = "1.0.74"
|
||||
fnv = "1.0.7"
|
||||
bincode = "1.3.3"
|
||||
sha3 = "0.10.0"
|
||||
byteorder = "1.4.3"
|
||||
rstar = "0.9.2"
|
||||
crossbeam-channel = "0.5.2"
|
||||
better-panic = "0.3.0"
|
||||
derivative = "2.2.0"
|
||||
dict_derive = "0.4.0"
|
||||
regex = "1.5.4"
|
||||
num_cpus = "1.13.1"
|
||||
eddie = "0.4.2"
|
||||
thiserror = "1.0.30"
|
||||
pyo3-log = "0.5.0"
|
||||
log = "0.4.14"
|
||||
flate2 = "1.0.22"
|
||||
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
|
||||
|
|
29
rust/analyze_logs.py
Normal file
29
rust/analyze_logs.py
Normal file
|
@ -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))
|
142
rust/benches/dot_bench.rs
Normal file
142
rust/benches/dot_bench.rs
Normal file
|
@ -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
rust/deps.svg
Normal file
0
rust/deps.svg
Normal file
46
rust/multi_test.py
Normal file
46
rust/multi_test.py
Normal file
|
@ -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
rust/route_log.txt
Normal file
0
rust/route_log.txt
Normal file
221
rust/run_test.py
Normal file
221
rust/run_test.py
Normal file
|
@ -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 pyo3::conversion::ToPyObject;
|
||||
use eyre::Result;
|
||||
use log::*;
|
||||
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 std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use sha3::{Digest, Sha3_256};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
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> {
|
||||
// Data from https://elite-dangerous.fandom.com/wiki/Guardian_Frame_Shift_Drive_Booster
|
||||
let ret = match class {
|
||||
|
@ -22,6 +266,7 @@ pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> {
|
|||
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> {
|
||||
let mut ret = HashMap::new();
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
if star_type.contains("White Dwarf") {
|
||||
return 1.5;
|
||||
|
@ -78,133 +324,403 @@ pub fn get_mult(star_type: &str) -> f32 {
|
|||
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 {
|
||||
ID(u32),
|
||||
Name(String),
|
||||
Pos((f32, f32, f32)),
|
||||
}
|
||||
|
||||
impl SysEntry {
|
||||
pub fn parse(s: &str) -> Self {
|
||||
if let Ok(n) = s.parse() {
|
||||
SysEntry::ID(n)
|
||||
} else {
|
||||
SysEntry::Name(s.to_owned())
|
||||
impl ToPyObject for SysEntry {
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
match self {
|
||||
Self::ID(id) => id.to_object(py),
|
||||
Self::Name(name) => name.to_object(py),
|
||||
Self::Pos(pos) => pos.to_object(py),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_matches(
|
||||
path: &PathBuf,
|
||||
pub fn grid_stats(
|
||||
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>,
|
||||
exact: bool,
|
||||
) -> Result<HashMap<String, (f64, Option<System>)>, String> {
|
||||
let mut best: HashMap<String, (f64, Option<System>)> = HashMap::new();
|
||||
) -> HashMap<String, (f64, Option<u32>)> {
|
||||
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() {
|
||||
return Ok(best);
|
||||
}
|
||||
for name in &names {
|
||||
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,
|
||||
Err(e) => {
|
||||
return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e));
|
||||
}
|
||||
};
|
||||
let systems = reader.deserialize::<SystemSerde>();
|
||||
for sys in systems {
|
||||
let sys = sys.unwrap();
|
||||
for name in &names {
|
||||
let t_start = std::time::Instant::now();
|
||||
let mut processed: usize = 0;
|
||||
for record in rdr.byte_records().flat_map(|v| v.ok()) {
|
||||
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| {
|
||||
if (exact) && (&sys.system == name) {
|
||||
*ent = (1.0, Some(sys.clone().build()))
|
||||
} 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()))
|
||||
}
|
||||
if score > &ent.0 {
|
||||
*ent = (*score, *sys);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct TreeNode {
|
||||
/// System ID
|
||||
pub id: u32,
|
||||
/// Position in space
|
||||
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 {
|
||||
pub fn get(&self, router: &Router) -> Option<System> {
|
||||
let mut cache = router.cache.as_ref().unwrap().lock().unwrap();
|
||||
cache.get(self.id)
|
||||
/// Retrieve matching [System] for this tree node
|
||||
pub fn get(&self, router: &Router) -> Result<Option<System>, String> {
|
||||
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)]
|
||||
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 {
|
||||
/// Unique System id
|
||||
pub id: u32,
|
||||
pub star_type: String,
|
||||
pub system: String,
|
||||
pub body: String,
|
||||
/// 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,
|
||||
pub distance: f32,
|
||||
/// Position
|
||||
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 {
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
let pos = PyTuple::new(py, self.pos.iter());
|
||||
let elem = PyDict::new(py);
|
||||
elem.set_item("star_type", self.star_type.clone()).unwrap();
|
||||
elem.set_item("system", self.system.clone()).unwrap();
|
||||
elem.set_item("body", self.body.clone()).unwrap();
|
||||
elem.set_item("distance", self.distance).unwrap();
|
||||
elem.set_item("mult", self.mult).unwrap();
|
||||
elem.set_item("id", self.id).unwrap();
|
||||
elem.set_item("pos", pos).unwrap();
|
||||
elem.to_object(py)
|
||||
let d = PyDict::new(py);
|
||||
d.set_item("id", self.id).unwrap();
|
||||
d.set_item("name", self.name.clone()).unwrap();
|
||||
d.set_item("num_bodies", self.num_bodies).unwrap();
|
||||
d.set_item("has_scoopable", self.has_scoopable).unwrap();
|
||||
d.set_item("mult", self.mult).unwrap();
|
||||
d.set_item("pos", (self.pos[0], self.pos[1], self.pos[2]))
|
||||
.unwrap();
|
||||
return d.to_object(py);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,7 +729,7 @@ impl System {
|
|||
TreeNode {
|
||||
id: self.id,
|
||||
pos: self.pos,
|
||||
mult: self.mult,
|
||||
flags: self.get_flags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,3 +745,118 @@ impl PartialOrd for System {
|
|||
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!();
|
||||
}
|
||||
}
|
||||
|
|
0
rust/src/dot_impls.rs
Normal file
0
rust/src/dot_impls.rs
Normal file
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::io::{BufRead, BufReader, BufWriter};
|
||||
use std::io::{BufRead, BufReader, BufWriter, Seek};
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
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,
|
||||
}
|
||||
|
||||
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 {
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct GalaxyCoords {
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Body {
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct GalaxyBody {
|
||||
name: String,
|
||||
#[serde(rename = "type")]
|
||||
body_type: String,
|
||||
|
@ -44,70 +29,73 @@ struct Body {
|
|||
distance: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct System {
|
||||
coords: Coords,
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct GalaxySystem {
|
||||
coords: GalaxyCoords,
|
||||
name: String,
|
||||
bodies: Vec<Body>,
|
||||
bodies: Vec<GalaxyBody>,
|
||||
}
|
||||
|
||||
pub fn process_galaxy_dump(path: &str) -> std::io::Result<()> {
|
||||
let fh = File::create("stars.csv")?;
|
||||
let mut wtr = csv::Writer::from_writer(BufWriter::new(fh));
|
||||
/// Load compressed galaxy.json from `path` and write `stars.csv` to `out_path`
|
||||
pub fn process_galaxy_dump(path: &Path, out_path: &Path) -> Result<()> {
|
||||
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 bz2_reader = std::process::Command::new("7z")
|
||||
.args(&["x", "-so", path])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to run 7z: {}", err);
|
||||
eprintln!("Falling back to bzip2");
|
||||
std::process::Command::new("bzip2")
|
||||
.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;
|
||||
let rdr = BufReader::new(File::open(path)?);
|
||||
let mut reader = BufReader::new(GzDecoder::new(rdr));
|
||||
let mut count: usize = 0;
|
||||
let mut total: usize = 0;
|
||||
let mut errors: usize = 0;
|
||||
let mut bodies: usize = 0;
|
||||
let mut systems = 0;
|
||||
let max_len = File::metadata(reader.get_ref().get_ref().get_ref())?.len();
|
||||
while let Ok(n) = reader.read_line(&mut buffer) {
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
buffer = buffer
|
||||
.trim()
|
||||
.trim_end_matches(|c| c == ',')
|
||||
.trim()
|
||||
.trim_end_matches(|c: char| c == ',' || c.is_whitespace())
|
||||
.to_string();
|
||||
if !buffer.contains("Star") {
|
||||
continue;
|
||||
};
|
||||
if let Ok(sys) = serde_json::from_str::<System>(&buffer) {
|
||||
for b in &sys.bodies {
|
||||
if b.body_type == "Star" {
|
||||
let s = SystemSerde {
|
||||
id: count,
|
||||
star_type: b.sub_type.clone(),
|
||||
distance: b.distance,
|
||||
mult: get_mult(&b.sub_type),
|
||||
body: b.name.clone(),
|
||||
system: sys.name.clone(),
|
||||
x: sys.coords.x,
|
||||
y: sys.coords.y,
|
||||
z: sys.coords.z,
|
||||
};
|
||||
wtr.serialize(s)?;
|
||||
count += 1;
|
||||
total += 1;
|
||||
if let Ok(sys) = serde_json::from_str::<GalaxySystem>(&buffer) {
|
||||
let mut sys_rec = System {
|
||||
id: systems,
|
||||
mult: 1.0,
|
||||
name: sys.name,
|
||||
num_bodies: 0,
|
||||
pos: [sys.coords.x, sys.coords.y, sys.coords.z],
|
||||
has_scoopable: false,
|
||||
};
|
||||
for b in sys.bodies.iter().filter(|b| b.body_type == "Star").cloned() {
|
||||
sys_rec.mult = sys_rec.mult.max(get_mult(&b.sub_type));
|
||||
sys_rec.num_bodies += 1;
|
||||
for c in "KGBFOAM".chars() {
|
||||
if b.sub_type.starts_with(c) {
|
||||
sys_rec.has_scoopable |= true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
println!("Total: {}", count);
|
||||
info!("Total: {}", total);
|
||||
info!("Bodies: {}", bodies);
|
||||
info!("Systems: {}", systems);
|
||||
info!("Processed: {}", count);
|
||||
info!("Errors: {}", errors);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Elite: Dangerous Journal Loadout even parser
|
||||
use crate::common::get_fsd_info;
|
||||
use crate::ship::Ship;
|
||||
|
||||
use eyre::Result;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
@ -11,23 +13,23 @@ pub struct Event {
|
|||
pub event: EventData,
|
||||
}
|
||||
|
||||
#[serde(tag = "event")]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[serde(tag = "event")]
|
||||
pub enum EventData {
|
||||
Loadout(Loadout),
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Modifier {
|
||||
label: String,
|
||||
value: f32,
|
||||
}
|
||||
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Engineering {
|
||||
modifiers: Vec<Modifier>,
|
||||
}
|
||||
|
@ -72,55 +74,46 @@ impl Engineering {
|
|||
|
||||
impl Loadout {
|
||||
fn get_booster(&self) -> Option<usize> {
|
||||
self.modules
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(|m| {
|
||||
let Module { item, .. } = m;
|
||||
if item.starts_with("int_guardianfsdbooster") {
|
||||
return item
|
||||
.chars()
|
||||
.last()
|
||||
.unwrap()
|
||||
.to_digit(10)
|
||||
.map(|v| v as usize);
|
||||
}
|
||||
return None;
|
||||
})
|
||||
.next()
|
||||
self.modules.iter().cloned().find_map(|m| {
|
||||
let Module { item, .. } = m;
|
||||
if item.starts_with("int_guardianfsdbooster") {
|
||||
return item
|
||||
.chars()
|
||||
.last()
|
||||
.unwrap()
|
||||
.to_digit(10)
|
||||
.map(|v| v as usize);
|
||||
}
|
||||
return None;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_fsd(&self) -> Option<(String, Option<Engineering>)> {
|
||||
self.modules
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(|m| {
|
||||
let Module {
|
||||
slot,
|
||||
engineering,
|
||||
item,
|
||||
} = m;
|
||||
if slot == "FrameShiftDrive" {
|
||||
return Some((item, engineering));
|
||||
}
|
||||
return None;
|
||||
})
|
||||
.next()
|
||||
self.modules.iter().cloned().find_map(|m| {
|
||||
let Module {
|
||||
slot,
|
||||
engineering,
|
||||
item,
|
||||
} = m;
|
||||
if slot == "FrameShiftDrive" {
|
||||
return Some((item, engineering));
|
||||
}
|
||||
return None;
|
||||
})
|
||||
}
|
||||
|
||||
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 booster = self.get_booster().unwrap_or(0);
|
||||
let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$")
|
||||
.unwrap()
|
||||
.captures(&fsd.0);
|
||||
let fsd_type: (usize, usize) = fsd_type
|
||||
.map(|m| {
|
||||
.and_then(|m| {
|
||||
let s = m.get(1)?.as_str().to_owned().parse().ok()?;
|
||||
let c = m.get(2)?.as_str().to_owned().parse().ok()?;
|
||||
return Some((c, s));
|
||||
})
|
||||
.flatten()
|
||||
.ok_or(format!("Invalid FSD found: {}", &fsd.0))?;
|
||||
let eng = fsd
|
||||
.1
|
||||
|
@ -141,18 +134,28 @@ impl Loadout {
|
|||
let opt_mass = fsd_info
|
||||
.get("FSDOptimalMass")
|
||||
.ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?;
|
||||
return Ship::new(
|
||||
self.ship_name,
|
||||
let key = format!(
|
||||
"[{}] {} ({})",
|
||||
if !self.ship_name.is_empty() {
|
||||
self.ship_name
|
||||
} else {
|
||||
"<NO NAME>".to_owned()
|
||||
},
|
||||
self.ship_ident,
|
||||
self.ship,
|
||||
self.unladen_mass,
|
||||
self.fuel_capacity.main,
|
||||
self.fuel_capacity.main,
|
||||
fsd_type,
|
||||
*max_fuel,
|
||||
*opt_mass,
|
||||
booster,
|
||||
self.ship
|
||||
);
|
||||
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)]
|
||||
//! # Elite: Danerous Long Range Router
|
||||
pub mod common;
|
||||
pub mod edsm;
|
||||
pub mod galaxy;
|
||||
pub mod journal;
|
||||
pub mod mmap_csv;
|
||||
#[cfg(feature = "profiling")]
|
||||
pub mod profiling;
|
||||
pub mod route;
|
||||
pub mod search_algos;
|
||||
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;
|
||||
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::ship::Ship;
|
||||
use eyre::Result;
|
||||
#[cfg(not(feature = "profiling"))]
|
||||
use log::*;
|
||||
use pyo3::exceptions::*;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyList, PyTuple};
|
||||
use pyo3::PyObjectProtocol;
|
||||
use std::path::PathBuf;
|
||||
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
|
||||
use pyo3::{create_exception, PyObjectProtocol};
|
||||
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)]
|
||||
#[derive(Debug)]
|
||||
#[text_signature = "(callback, /)"]
|
||||
#[pyo3(text_signature = "(callback, /)")]
|
||||
struct PyRouter {
|
||||
router: Router,
|
||||
primary_only: bool,
|
||||
stars_path: String,
|
||||
stars_path: Option<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]
|
||||
impl PyRouter {
|
||||
#[new]
|
||||
#[args(callback = "None")]
|
||||
fn new(callback: Option<PyObject>, py: Python<'static>) -> PyResult<Self> {
|
||||
Ok(PyRouter {
|
||||
router: Router::new(Box::new(
|
||||
move |state: &SearchState| {
|
||||
match callback.as_ref() {
|
||||
Some(cb) => cb.call(py, (state.clone(),), None),
|
||||
None => Ok(py.None()),
|
||||
}
|
||||
fn new(callback: Option<PyObject>) -> Self {
|
||||
let mut router = Router::new();
|
||||
if callback.is_some() {
|
||||
router.set_callback(Box::new(move |state: &SearchState| {
|
||||
let gil_guard = Python::acquire_gil();
|
||||
let py = gil_guard.python();
|
||||
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, /)"]
|
||||
fn set_ship(&mut self, py: Python, ship: &PyShip) -> PyResult<PyObject> {
|
||||
self.router.set_ship(ship.ship.clone());
|
||||
#[args(primary_only = "false", immediate = "false")]
|
||||
#[pyo3(text_signature = "(path, primary_only, /)")]
|
||||
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())
|
||||
}
|
||||
|
||||
#[args(primary_only = "false")]
|
||||
#[text_signature = "(path, primary_only, /)"]
|
||||
fn load(
|
||||
&mut self,
|
||||
path: String,
|
||||
primary_only: bool,
|
||||
py: Python,
|
||||
) -> PyResult<PyObject> {
|
||||
self.stars_path = path;
|
||||
self.primary_only = primary_only;
|
||||
#[pyo3(text_signature = "(/)")]
|
||||
fn unload(&mut self, py: Python) -> PyObject {
|
||||
self.router.unload();
|
||||
py.None()
|
||||
}
|
||||
|
||||
fn plot(&mut self, 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 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())
|
||||
}
|
||||
|
||||
#[args(greedyness = "0.5", num_workers = "0", beam_width = "0")]
|
||||
#[text_signature = "(hops, range, greedyness, beam_width, num_workers, /)"]
|
||||
fn precompute_neighbors(&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_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(
|
||||
&mut self,
|
||||
hops: &PyList,
|
||||
range: Option<f32>,
|
||||
greedyness: f32,
|
||||
beam_width: usize,
|
||||
hops: Vec<SysEntry>,
|
||||
range: RangeOrShip,
|
||||
mode: Option<PyModeConfig>,
|
||||
num_workers: usize,
|
||||
py: Python,
|
||||
) -> PyResult<PyObject> {
|
||||
let route_res = self
|
||||
.router
|
||||
.load(&PathBuf::from(self.stars_path.clone()), self.primary_only);
|
||||
) -> PyResult<Vec<common::System>> {
|
||||
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::<ValueError, _>(err_msg));
|
||||
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||
};
|
||||
let mut sys_entries: Vec<SysEntry> = Vec::new();
|
||||
for hop in hops {
|
||||
if let Ok(id) = hop.extract() {
|
||||
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,
|
||||
info!("Resolving systems...");
|
||||
let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) {
|
||||
Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(),
|
||||
Err(err_msg) => {
|
||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
||||
return Err(EdLrrError::ResolveError(err_msg).into());
|
||||
}
|
||||
};
|
||||
match self
|
||||
.router
|
||||
.compute_route(&ids, range, greedyness, beam_width, num_workers)
|
||||
{
|
||||
Ok(route) => {
|
||||
let py_route: Vec<_> = route.iter().map(|hop| hop.to_object(py)).collect();
|
||||
Ok(py_route.to_object(py))
|
||||
let mut is_default = false;
|
||||
let mut is_ship = false;
|
||||
info!("{:?}", mode);
|
||||
let mut mode = match mode {
|
||||
Some(mode) => mode,
|
||||
None => {
|
||||
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 = "*")]
|
||||
#[text_signature = "(sys_1, sys_2, ..., /)"]
|
||||
fn resolve_systems(&self, hops: &PyTuple, py: Python) -> PyResult<PyObject> {
|
||||
let mut sys_entries: Vec<SysEntry> = Vec::new();
|
||||
for hop in hops {
|
||||
if let Ok(id) = hop.extract() {
|
||||
sys_entries.push(SysEntry::ID(id));
|
||||
} 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,
|
||||
#[pyo3(text_signature = "(sys_1, sys_2, ..., /)")]
|
||||
fn resolve(&self, hops: Vec<SysEntry>, py: Python) -> PyResult<PyObject> {
|
||||
info!("Resolving systems...");
|
||||
let stars_path = self.check_stars()?;
|
||||
let systems: Vec<System> = match resolve(&hops, &stars_path, false) {
|
||||
Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(),
|
||||
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))
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn preprocess_edsm() -> PyResult<()> {
|
||||
todo!("Implement EDSM Preprocessor")
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn preprocess_galaxy() -> PyResult<()> {
|
||||
todo!("Implement galaxy.json Preprocessor")
|
||||
fn str_tree_test(&self) -> common::EdLrrResult<()> {
|
||||
use common::BKTree;
|
||||
const CHUNK_SIZE: usize = 4_000_000;
|
||||
let path = self.check_stars()?;
|
||||
let reader: csv::Reader<File> = csv::ReaderBuilder::new()
|
||||
.has_headers(false)
|
||||
.from_path(path)
|
||||
.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 ids: Vec<u32> = Vec::new();
|
||||
let mut ret: Vec<u32> = Vec::new();
|
||||
let mut needs_rtree = false;
|
||||
for ent in entries {
|
||||
match ent {
|
||||
SysEntry::Name(name) => names.push(name.to_owned()),
|
||||
SysEntry::ID(id) => ids.push(*id),
|
||||
SysEntry::Pos(_) => {
|
||||
needs_rtree |= true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if !path.exists() {
|
||||
return Err(format!(
|
||||
"Source file \"{:?}\" does not exist!",
|
||||
path.display()
|
||||
));
|
||||
return Err(format!("Source file {:?} does not exist!", path.display()));
|
||||
}
|
||||
|
||||
let name_ids = find_matches(path, names, false)?;
|
||||
let name_ids = if !names.is_empty() {
|
||||
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 {
|
||||
match ent {
|
||||
SysEntry::Name(name) => {
|
||||
let ent_res = name_ids
|
||||
.get(&name.to_owned())
|
||||
.get(name)
|
||||
.ok_or(format!("System {} not found", name))?;
|
||||
let sys = ent_res
|
||||
.1
|
||||
.as_ref()
|
||||
.ok_or(format!("System {} not found", name))?;
|
||||
if ent_res.0 < 0.75 {
|
||||
println!(
|
||||
"WARNING: {} match to {} with low confidence ({:.2}%)",
|
||||
name,
|
||||
sys.system,
|
||||
ent_res.0 * 100.0
|
||||
);
|
||||
}
|
||||
ret.push(sys.id);
|
||||
ret.push(*sys);
|
||||
}
|
||||
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)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct PyShip {
|
||||
ship: Ship,
|
||||
}
|
||||
|
@ -219,8 +493,8 @@ impl PyShip {
|
|||
#[staticmethod]
|
||||
fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> {
|
||||
match Ship::new_from_json(loadout) {
|
||||
Ok(ship) => Ok((PyShip { ship }).into_py(py)),
|
||||
Err(err_msg) => Err(PyErr::new::<ValueError, _>(err_msg)),
|
||||
Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)),
|
||||
Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)),
|
||||
}
|
||||
}
|
||||
#[staticmethod]
|
||||
|
@ -228,15 +502,15 @@ impl PyShip {
|
|||
let mut ship = match Ship::new_from_journal() {
|
||||
Ok(ship) => ship,
|
||||
Err(err_msg) => {
|
||||
return Err(PyErr::new::<ValueError, _>(err_msg));
|
||||
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||
}
|
||||
};
|
||||
let ships: Vec<(PyObject, PyObject)> = ship
|
||||
.drain()
|
||||
.map(|(k, v)| {
|
||||
let k_py = k.to_object(py);
|
||||
let v_py = (PyShip { ship: v }).into_py(py);
|
||||
(k_py, v_py)
|
||||
.map(|(ship_name, ship)| {
|
||||
let ship_name_py = ship_name.to_object(py);
|
||||
let ship_py = (PyShip { ship }).into_py(py);
|
||||
(ship_name_py, ship_py)
|
||||
})
|
||||
.collect();
|
||||
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
||||
|
@ -246,239 +520,141 @@ impl PyShip {
|
|||
self.ship.to_object(py)
|
||||
}
|
||||
|
||||
#[text_signature = "(dist, /)"]
|
||||
fn fuel_cost(&self, _py: Python, dist: f32) -> PyResult<f32> {
|
||||
Ok(self.ship.fuel_cost(dist))
|
||||
#[pyo3(text_signature = "(dist, /)")]
|
||||
fn fuel_cost(&self, _py: Python, dist: f32) -> f32 {
|
||||
self.ship.fuel_cost(dist)
|
||||
}
|
||||
|
||||
#[text_signature = "(/)"]
|
||||
fn range(&self, _py: Python) -> PyResult<f32> {
|
||||
Ok(self.ship.range())
|
||||
#[getter]
|
||||
fn range(&self, _py: Python) -> f32 {
|
||||
self.ship.range()
|
||||
}
|
||||
|
||||
#[text_signature = "(/)"]
|
||||
fn max_range(&self, _py: Python) -> PyResult<f32> {
|
||||
Ok(self.ship.max_range())
|
||||
#[getter]
|
||||
fn max_range(&self, _py: Python) -> f32 {
|
||||
self.ship.max_range()
|
||||
}
|
||||
|
||||
#[text_signature = "(dist, /)"]
|
||||
fn make_jump(&mut self, dist: f32, _py: Python) -> PyResult<Option<f32>> {
|
||||
Ok(self.ship.make_jump(dist))
|
||||
#[pyo3(text_signature = "(dist, /)")]
|
||||
fn make_jump(&mut self, dist: f32, _py: Python) -> Option<f32> {
|
||||
self.ship.make_jump(dist)
|
||||
}
|
||||
|
||||
#[text_signature = "(dist, /)"]
|
||||
fn can_jump(&self, dist: f32, _py: Python) -> PyResult<bool> {
|
||||
Ok(self.ship.can_jump(dist))
|
||||
#[pyo3(text_signature = "(dist, /)")]
|
||||
fn can_jump(&self, dist: f32, _py: Python) -> bool {
|
||||
self.ship.can_jump(dist)
|
||||
}
|
||||
|
||||
#[args(fuel_amount = "None")]
|
||||
#[text_signature = "(fuel_amount, /)"]
|
||||
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) -> PyResult<()> {
|
||||
#[pyo3(text_signature = "(fuel_amount, /)")]
|
||||
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) {
|
||||
if let Some(fuel) = fuel_amount {
|
||||
self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity)
|
||||
} else {
|
||||
self.ship.fuel_mass = self.ship.fuel_capacity;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[text_signature = "(factor, /)"]
|
||||
fn boost(&mut self, factor: f32, _py: Python) -> PyResult<()> {
|
||||
#[pyo3(text_signature = "(factor, /)")]
|
||||
fn boost(&mut self, factor: f32, _py: Python) {
|
||||
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]
|
||||
pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
better_panic::install();
|
||||
|
||||
pyo3_log::init();
|
||||
profiling::init();
|
||||
m.add_class::<PyRouter>()?;
|
||||
m.add_class::<PyShip>()?;
|
||||
/*
|
||||
#[pyfn(m, "get_ships_from_journal")]
|
||||
fn get_ships_from_journal(py: Python) -> PyResult<PyObject> {
|
||||
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))
|
||||
}
|
||||
*/
|
||||
m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_galaxy))?;
|
||||
m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_edsm))?;
|
||||
m.add_wrapped(pyo3::wrap_pyfunction!(expr_test))?;
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
69
rust/src/mmap_csv.rs
Normal file
69
rust/src/mmap_csv.rs
Normal file
|
@ -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)
|
||||
}
|
15
rust/src/profiling.rs
Normal file
15
rust/src/profiling.rs
Normal file
|
@ -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
56
rust/src/search_algos/mod.rs
Normal file
56
rust/src/search_algos/mod.rs
Normal file
|
@ -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::journal::*;
|
||||
use eyre::Result;
|
||||
use pyo3::conversion::ToPyObject;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyDict;
|
||||
|
@ -10,21 +12,25 @@ use std::fs::File;
|
|||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Frame Shift Drive information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FSD {
|
||||
/// Rating
|
||||
pub rating_val: f32,
|
||||
/// Class
|
||||
pub class_val: f32,
|
||||
/// Optimized Mass
|
||||
pub opt_mass: f32,
|
||||
/// Max fuel per jump
|
||||
pub max_fuel: f32,
|
||||
/// Boost factor
|
||||
pub boost: f32,
|
||||
/// Guardian booster bonus range
|
||||
pub guardian_booster: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
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,
|
||||
|
@ -33,9 +39,6 @@ pub struct Ship {
|
|||
|
||||
impl Ship {
|
||||
pub fn new(
|
||||
name: String,
|
||||
ident: String,
|
||||
ship_type: String,
|
||||
base_mass: f32,
|
||||
fuel_mass: f32,
|
||||
fuel_capacity: f32,
|
||||
|
@ -57,15 +60,12 @@ impl Ship {
|
|||
if fsd_type.1 < 2 || fsd_type.1 > 8 {
|
||||
return Err(format!("Invalid class: {}", fsd_type.1));
|
||||
};
|
||||
|
||||
if guardian_booster!=0 {
|
||||
return Err("Guardian booster not yet implemented!".to_owned())
|
||||
|
||||
if guardian_booster != 0 {
|
||||
return Err("Guardian booster not yet implemented!".to_owned());
|
||||
}
|
||||
|
||||
let ret = Self {
|
||||
name,
|
||||
ident,
|
||||
ship_type,
|
||||
fuel_capacity,
|
||||
fuel_mass,
|
||||
base_mass,
|
||||
|
@ -81,8 +81,8 @@ impl Ship {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn new_from_json(data: &str) -> Result<Self, String> {
|
||||
match serde_json::from_str::<Event>(&data) {
|
||||
pub fn new_from_json(data: &str) -> Result<(String, Ship), String> {
|
||||
match serde_json::from_str::<Event>(data) {
|
||||
Ok(Event {
|
||||
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 re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap();
|
||||
let mut journals: Vec<PathBuf> = Vec::new();
|
||||
|
@ -110,12 +110,10 @@ impl Ship {
|
|||
userprofile.push("Frontier Developments");
|
||||
userprofile.push("Elite Dangerous");
|
||||
if let Ok(iter) = userprofile.read_dir() {
|
||||
for entry in iter {
|
||||
if let Ok(entry) = entry {
|
||||
if re.is_match(&entry.file_name().to_string_lossy()) {
|
||||
journals.push(entry.path());
|
||||
};
|
||||
}
|
||||
for entry in iter.flatten() {
|
||||
if re.is_match(&entry.file_name().to_string_lossy()) {
|
||||
journals.push(entry.path());
|
||||
};
|
||||
}
|
||||
}
|
||||
journals.sort();
|
||||
|
@ -133,16 +131,7 @@ impl Ship {
|
|||
}) => {}
|
||||
Ok(ev) => {
|
||||
if let Some(loadout) = ev.get_loadout() {
|
||||
let mut 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()
|
||||
);
|
||||
let (key, ship) = loadout.try_into_ship()?;
|
||||
ret.insert(key, ship);
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +168,7 @@ impl Ship {
|
|||
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 mut fuel = self.fsd.max_fuel.min(fuel);
|
||||
if booster {
|
||||
|
@ -198,6 +187,10 @@ impl Ship {
|
|||
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 {
|
||||
if self.fsd.guardian_booster == 0.0 {
|
||||
return 1.0;
|
||||
|
@ -208,6 +201,21 @@ impl Ship {
|
|||
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 {
|
||||
if d == 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 {
|
||||
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
||||
let elem = PyDict::new(py);
|
||||
|
@ -259,9 +244,6 @@ impl FSD {
|
|||
impl Ship {
|
||||
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
||||
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("fuel_mass", self.fuel_mass)?;
|
||||
elem.set_item("fuel_capacity", self.fuel_capacity)?;
|
||||
|
|
69
rust/tests/dot_impls.rs
Normal file
69
rust/tests/dot_impls.rs
Normal file
|
@ -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 -*-
|
||||
from setuptools import find_packages, setup
|
||||
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()
|
||||
|
||||
extras_require = {
|
||||
'build': ['pyinstaller', 'pywin32'],
|
||||
'test': [
|
||||
'pytest',
|
||||
'pytest-cov',
|
||||
'pytest-dependency',
|
||||
'pytest-benchmark[histogram]',
|
||||
'pytest-metadata',
|
||||
'pytest-flake8',
|
||||
'pytest-flask',
|
||||
'pytest-mock',
|
||||
'pytest-flask-sqlalchemy',
|
||||
'pytest-steps',
|
||||
'pytest-xdist',
|
||||
'flake8-bugbear',
|
||||
'flake8-comprehensions',
|
||||
'cohesion',
|
||||
'hypothesis',
|
||||
'flaky'
|
||||
"build": ["pyinstaller", "pywin32"],
|
||||
"test": [
|
||||
"pytest",
|
||||
"pytest-cov",
|
||||
"pytest-dependency",
|
||||
"pytest-benchmark[histogram]",
|
||||
"pytest-metadata",
|
||||
"pytest-flake8",
|
||||
"pytest-flask",
|
||||
"pytest-mock",
|
||||
"pytest-flask-sqlalchemy",
|
||||
"pytest-steps",
|
||||
"pytest-xdist",
|
||||
"flake8-bugbear",
|
||||
"flake8-comprehensions",
|
||||
"cohesion",
|
||||
"hypothesis",
|
||||
"flaky",
|
||||
],
|
||||
'dev': [
|
||||
"dev": [
|
||||
'black; python_version >= "3.6"',
|
||||
'jinja2',
|
||||
'tsp',
|
||||
'flake8',
|
||||
'flake8-bugbear',
|
||||
'flake8-comprehensions',
|
||||
'cohesion',
|
||||
'pre-commit',
|
||||
'ipython',
|
||||
'flask-konch',
|
||||
'setuptools_rust'
|
||||
"jinja2",
|
||||
"tsp",
|
||||
"flake8",
|
||||
"flake8-bugbear",
|
||||
"flake8-comprehensions",
|
||||
"cohesion",
|
||||
"pre-commit",
|
||||
"ipython",
|
||||
"flask-konch",
|
||||
"setuptools_rust",
|
||||
],
|
||||
'gui': ['PyQt5', 'pyperclip'],
|
||||
'web': [
|
||||
'flask',
|
||||
'gevent',
|
||||
'webargs',
|
||||
'flask-executor',
|
||||
'flask-wtf',
|
||||
'flask-user',
|
||||
'flask-debugtoolbar',
|
||||
'flask-bootstrap4',
|
||||
'flask-sqlalchemy',
|
||||
'flask-nav',
|
||||
'flask-admin',
|
||||
'sqlalchemy_utils[password]',
|
||||
'python-dotenv',
|
||||
"gui": ["PyQt5", "pyperclip"],
|
||||
"web": [
|
||||
"flask",
|
||||
"gevent",
|
||||
"webargs",
|
||||
"flask-executor",
|
||||
"flask-wtf",
|
||||
"flask-user",
|
||||
"flask-debugtoolbar",
|
||||
"flask-bootstrap4",
|
||||
"flask-sqlalchemy",
|
||||
"flask-nav",
|
||||
"flask-admin",
|
||||
"sqlalchemy_utils[password]",
|
||||
"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(
|
||||
use_scm_version={'write_to': '__version__.py'},
|
||||
name='ed_lrr_gui',
|
||||
author='Daniel Seiller',
|
||||
author_email='earthnuker@gmail.com',
|
||||
description='Elite: Dangerous long range route plotter',
|
||||
use_scm_version={"write_to": "__version__.py"},
|
||||
name="ed_lrr_gui",
|
||||
author="Daniel Seiller",
|
||||
author_email="earthnuker@gmail.com",
|
||||
description="Elite: Dangerous long range route plotter",
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui',
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui",
|
||||
rust_extensions=[
|
||||
RustExtension(
|
||||
'_ed_lrr',
|
||||
path='rust/Cargo.toml',
|
||||
"_ed_lrr",
|
||||
path="rust/Cargo.toml",
|
||||
binding=Binding.PyO3,
|
||||
strip=Strip.No,
|
||||
rustc_flags=["--emit=asm"],
|
||||
# features=["profiling"],
|
||||
debug=False,
|
||||
native=True,
|
||||
quiet=True,
|
||||
|
@ -78,38 +84,38 @@ setup(
|
|||
],
|
||||
packages=find_packages(),
|
||||
entry_points={
|
||||
'console_scripts': ['ed_lrr = ed_lrr_gui.__main__:main'],
|
||||
'gui_scripts': ['ed_lrr_gui = ed_lrr_gui.__main__:gui_main']
|
||||
"console_scripts": ["ed_lrr = ed_lrr_gui.__main__:main"],
|
||||
"gui_scripts": ["ed_lrr_gui = ed_lrr_gui.__main__:gui_main"],
|
||||
},
|
||||
install_requires=[
|
||||
'appdirs',
|
||||
'PyYAML',
|
||||
'requests',
|
||||
'python-dateutil',
|
||||
'click',
|
||||
'tqdm',
|
||||
'click-default-group',
|
||||
'profig',
|
||||
'ujson',
|
||||
'colorama',
|
||||
'svgwrite',
|
||||
"appdirs",
|
||||
"PyYAML",
|
||||
"requests",
|
||||
"python-dateutil",
|
||||
"click",
|
||||
"tqdm",
|
||||
"click-default-group",
|
||||
"profig",
|
||||
"ujson",
|
||||
"colorama",
|
||||
"svgwrite",
|
||||
"coloredlogs",
|
||||
],
|
||||
setup_requires=['setuptools', 'setuptools-rust',
|
||||
'setuptools-scm', 'wheel'],
|
||||
dependency_links=['https://github.com/Nuitka/Nuitka/archive/develop.zip'],
|
||||
setup_requires=["setuptools", "setuptools-rust", "setuptools-scm", "wheel"],
|
||||
dependency_links=["https://github.com/Nuitka/Nuitka/archive/develop.zip"],
|
||||
extras_require=extras_require,
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Rust',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Operating System :: Windows',
|
||||
'Operating System :: Linux',
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Operating System :: Windows",
|
||||
"Operating System :: Linux",
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
|
|
45
test_route.py
Normal file
45
test_route.py
Normal file
|
@ -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)
|
289
tests/data/ships/base.json
Normal file
289
tests/data/ships/base.json
Normal file
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
193
tests/data/ships/guardian.json
Normal file
193
tests/data/ships/guardian.json
Normal file
|
@ -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)
|
||||
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(
|
||||
"greedyness", greedyness, ids=lambda v: "greedyness:{}".format(v)
|
||||
)
|
||||
@flaky(max_runs=10, min_passes=5)
|
||||
def test_zero_range_fails(self, py_router, greedyness):
|
||||
r, resolved_systems = py_router
|
||||
waypoints = random.sample(list(resolved_systems.values()), k=2)
|
||||
err = pytest.raises(RuntimeError, r.route, waypoints, 0, greedyness)
|
||||
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)
|
||||
@pytest.mark.parametrize("workers", n_workers, ids=idf("workers"))
|
||||
@pytest.mark.parametrize("jump_range", ranges, ids=idf("range"))
|
||||
|
|
Loading…
Reference in a new issue