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 | __version__.py | ||||||
| .nox/ | .nox/ | ||||||
| dist_vis.py | 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}}$$ | $$dist = \frac{- B_{g} \cdot m_{fuel} - B_{g} \cdot m_{ship} + boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$ | ||||||
| 
 | 
 | ||||||
| $$e_{fuel} = \frac{l \cdot \left(\frac{10.0^{- \frac{3.0}{p}} \cdot \left(B_{g} + dist\right) \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}}{\min\left(f_{max}, m_{fuel}\right)}$$ | $$e_{fuel} = \frac{l \cdot \left(\frac{10.0^{- \frac{3.0}{p}} \cdot \left(B_{g} + dist\right) \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}}{\min\left(f_{max}, m_{fuel}\right)}$$ | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| --- |  | ||||||
| 
 |  | ||||||
| # TODO |  | ||||||
| 
 |  | ||||||
| ## Routing |  | ||||||
| 
 |  | ||||||
| - Implement Neutron Mode |  | ||||||
|   - Filter for neutron stars, plot route, then plot "fine" router between waypoints |  | ||||||
|     - What Jump-Range to use for neutron route? `max_range*4`? |  | ||||||
| - Implement Bidir BFS |  | ||||||
| - Optimized All-Pairs BFS for graph precomputation |  | ||||||
| - Take fuel consumption into account (WIP) |  | ||||||
|   - Guardian Booster support (Done?) |  | ||||||
|   - Economic routing |  | ||||||
| - Custom weights and filtering for routing |  | ||||||
| 
 |  | ||||||
| ## GUI |  | ||||||
| 
 |  | ||||||
| - Implement estimate time to completion display for route computation and preprocessing (Done?) |  | ||||||
| - Export route as: |  | ||||||
|   - JSON |  | ||||||
|   - HTML (WIP) |  | ||||||
|   - CSV |  | ||||||
|   - SVG |  | ||||||
| 
 |  | ||||||
| ## Installer |  | ||||||
| 
 |  | ||||||
| - Update PATH from installer |  | ||||||
| 
 |  | ||||||
| ## Preprocessing |  | ||||||
| 
 |  | ||||||
| - Build index over `systemsWithCoordinates.json` instead of loading it into RAM (reuse modified `LineCache` from `router.rs`) |  | ||||||
| - Finish `galaxy.jsonl` preprocessor |  | ||||||
| - Implement Python interface to preprocessor |  | ||||||
| 
 |  | ||||||
| ## Misc |  | ||||||
| 
 |  | ||||||
| - Luigi based Task queue for distributed routing |  | ||||||
| - Full route tree computation |  | ||||||
|   - overlap elimination |  | ||||||
|  |  | ||||||
							
								
								
									
										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 _ed_lrr | ||||||
| import os | import os | ||||||
| 
 | 
 | ||||||
| os.environ.setdefault("CELERY_CONFIG_MODULE", "celeryconfig") |  | ||||||
| app = Celery("ed_lrr") | app = Celery("ed_lrr") | ||||||
| app.config_from_envvar("CELERY_CONFIG_MODULE") | app.config_from_object(__import__("celeryconfig")) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.task(bind=True) | @app.task(bind=True) | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| MD = $(wildcard src/*.md) |  | ||||||
| DOTS = $(wildcard src/*.dot) |  | ||||||
| ASYS = $(wildcard src/*.asy) |  | ||||||
| PYS = $(wildcard src/img_*.py) |  | ||||||
| PDFS = $(MD:src/%.md=out/%.pdf) |  | ||||||
| 
 |  | ||||||
| IMG_PDFS = $(ASYS:src/%.asy=img/%.pdf) $(PYS:src/img_%.py=img/%.pdf) $(DOTS:src/%.dot=img/%.pdf) |  | ||||||
| 
 |  | ||||||
| IMGS = $(IMG_PDFS) |  | ||||||
| 
 |  | ||||||
| TEMPLATE = eisvogel |  | ||||||
| PDF_ENGINE = xelatex |  | ||||||
| PANDOC = pandoc |  | ||||||
| PANDOC_OPTIONS = -F panflute -F pandoc-citeproc --pdf-engine=$(PDF_ENGINE) --template $(TEMPLATE) -N --standalone --listings |  | ||||||
| 
 |  | ||||||
| GRAPHVIZ = dot |  | ||||||
| GRAPHVIZ_OPTIONS = -Tpdf |  | ||||||
| 
 |  | ||||||
| ASY = asy |  | ||||||
| ASY_OPTIONS = -noV -f pdf |  | ||||||
| 
 |  | ||||||
| PYTHON = python |  | ||||||
| PYTHON_OPTIONS = |  | ||||||
| 
 |  | ||||||
| mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) |  | ||||||
| current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) |  | ||||||
| 
 |  | ||||||
| .PHONY: clean all default |  | ||||||
| all: $(PDFS) |  | ||||||
| default: all |  | ||||||
| 
 |  | ||||||
| out/%.pdf: src/%.md $(IMGS) Makefile |  | ||||||
| 	$(PANDOC) $(PANDOC_OPTIONS) -o $@ $< |  | ||||||
| 
 |  | ||||||
| img/%.pdf: src/%.dot |  | ||||||
| 	$(GRAPHVIZ) $(GRAPHVIZ_OPTIONS) -o $@ $< |  | ||||||
| 
 |  | ||||||
| img/%.pdf: src/img_%.py |  | ||||||
| 	$(PYTHON) $(PYTHON_OPTIONS) $< $@ |  | ||||||
| 
 |  | ||||||
| img/%.pdf: src/%.asy |  | ||||||
| 	$(ASY) $(ASY_OPTIONS) -o $@ $< |  | ||||||
| 
 |  | ||||||
| watch: |  | ||||||
| 	watchexec -w src -w data -w filters -w Makefile make all |  | ||||||
| 
 |  | ||||||
| clean: |  | ||||||
| 	-rm $(PDFS) $(IMGS) |  | ||||||
|  | @ -1,158 +0,0 @@ | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
| import contextlib |  | ||||||
| import csv |  | ||||||
| import datetime |  | ||||||
| import hashlib |  | ||||||
| import io |  | ||||||
| import os |  | ||||||
| import re |  | ||||||
| import subprocess as SP |  | ||||||
| import sys |  | ||||||
| import tempfile |  | ||||||
| from functools import partial |  | ||||||
| 
 |  | ||||||
| import panflute as pf |  | ||||||
| from dateutil.parser import parse as dateparse |  | ||||||
| from jinja2 import Environment, PackageLoader, Template, select_autoescape |  | ||||||
| from panflute import * |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def remove_pound(elem, doc): |  | ||||||
|     if type(elem) == Str: |  | ||||||
|         return Str(elem.text.lstrip("#")) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def fix_color(elem, doc): |  | ||||||
|     if type(elem) == MetaMap: |  | ||||||
|         for k in elem.content: |  | ||||||
|             if k.endswith("-color"): |  | ||||||
|                 elem[k] = elem[k].walk(remove_pound) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def update_date(elem, doc): |  | ||||||
|     if type(elem) == MetaMap: |  | ||||||
|         datefmt = doc.get_metadata("datefmt", "%Y-%m-%d") |  | ||||||
|         today = datetime.date.today().strftime(datefmt) |  | ||||||
|         date = dateparse(doc.get_metadata("date", today)).date() |  | ||||||
|         elem["date"] = MetaInlines(Str(date.strftime(datefmt))) |  | ||||||
|         return elem |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def csv_table(elem, doc): |  | ||||||
|     if type(elem) == Para and len(elem.content) == 1 and type(elem.content[0]) == Image: |  | ||||||
|         elem = elem.content[0] |  | ||||||
|         ext = os.path.splitext(elem.url)[1][1:] |  | ||||||
|         if ext == "csv": |  | ||||||
|             caption = elem.content |  | ||||||
|             has_header = elem.attributes.get("has-header", "false").lower() == "true" |  | ||||||
|             with open(elem.url) as f: |  | ||||||
|                 reader = csv.reader(f) |  | ||||||
|                 body = [] |  | ||||||
|                 for row in reader: |  | ||||||
|                     cells = [TableCell(Plain(Str(x))) for x in row] |  | ||||||
|                     body.append(TableRow(*cells)) |  | ||||||
|             header = body.pop(0) if has_header else None |  | ||||||
|             ret = Table(*body, header=header, caption=caption) |  | ||||||
|             return ret |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def code_refs(elem, doc): |  | ||||||
|     if type(elem) == Cite: |  | ||||||
|         label = elem.content[0] |  | ||||||
|         if type(label) == Str: |  | ||||||
|             label = label.text |  | ||||||
|             filename = re.findall(r"^\[@lst:(.*)\]$", label) or [None] |  | ||||||
|             if filename[0] in doc.inc_files: |  | ||||||
|                 return [ |  | ||||||
|                     RawInline( |  | ||||||
|                         "\\hyperref[{}]{{{}}}".format(filename[0], filename[0]), |  | ||||||
|                         format="tex", |  | ||||||
|                     ) |  | ||||||
|                 ] |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def include_code(elem, doc): |  | ||||||
|     if type(elem) == CodeBlock: |  | ||||||
|         if "include" in elem.attributes: |  | ||||||
|             filepath = elem.attributes.pop("include") |  | ||||||
|             filename = os.path.split(filepath)[-1] |  | ||||||
|             try: |  | ||||||
|                 elem.text += elem.text + open(filepath, encoding="utf-8").read() |  | ||||||
|                 elem.attributes["caption"] = filename |  | ||||||
|                 doc.inc_files.append(filename) |  | ||||||
|             except Exception as e: |  | ||||||
|                 elem.text += "Error: {}".format(e) |  | ||||||
|             return [RawBlock("\\label{{{}}}".format(filename), format="tex"), elem] |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def py_eval(options, data, element, doc): |  | ||||||
|     out_buffer = io.StringIO() |  | ||||||
|     with contextlib.redirect_stdout(out_buffer): |  | ||||||
|         exec(data, doc.pyenv) |  | ||||||
|     out_buffer.seek(0) |  | ||||||
|     return convert_text(out_buffer.read()) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def jinja_py_filt(doc, file): |  | ||||||
|     env = {} |  | ||||||
|     code = open(file, encoding="utf-8").read() |  | ||||||
|     exec(code, env) |  | ||||||
|     return env["main"](doc) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def prepare(doc): |  | ||||||
|     doc.inc_files = [] |  | ||||||
|     doc.env = Environment() |  | ||||||
|     doc.pyenv = {} |  | ||||||
|     filters = {"py": partial(jinja_py_filt, doc)} |  | ||||||
|     doc.env.filters.update(filters) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def process_templates(elem, doc): |  | ||||||
|     if type(elem) == CodeBlock: |  | ||||||
|         if elem.classes == ["@"]: |  | ||||||
|             args = {"meta": doc.get_metadata()} |  | ||||||
|             return convert_text(doc.env.from_string(elem.text).render(args)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def yaml_filt(elem, doc): |  | ||||||
|     tags = {"eval": py_eval} |  | ||||||
|     return yaml_filter(elem, doc, tags=tags, strict_yaml=True) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def checkboxes(elem, doc): |  | ||||||
|     if type(elem) in [Para, Plain]: |  | ||||||
|         val = re.findall(r"^\[([xX]|\ )\] (.*)$", stringify(elem)) |  | ||||||
|         if val: |  | ||||||
|             val = val[0][0].lower() == "x" |  | ||||||
|         else: |  | ||||||
|             return elem |  | ||||||
|         marker = { |  | ||||||
|             True: RawInline("$\\boxtimes$", format="latex"), |  | ||||||
|             False: RawInline("$\\square$", format="latex"), |  | ||||||
|         }[val] |  | ||||||
|         cont = [] |  | ||||||
|         if val: |  | ||||||
|             cont += elem.content[2:] |  | ||||||
|         else: |  | ||||||
|             cont += elem.content[4:] |  | ||||||
|         return Plain(marker, Space, *cont) |  | ||||||
|     return elem |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def main(doc=None): |  | ||||||
|     f = [ |  | ||||||
|         process_templates, |  | ||||||
|         update_date, |  | ||||||
|         csv_table, |  | ||||||
|         include_code, |  | ||||||
|         fix_color, |  | ||||||
|         code_refs, |  | ||||||
|         yaml_filt, |  | ||||||
|         checkboxes, |  | ||||||
|     ] |  | ||||||
|     return run_filters(f, prepare=prepare, doc=doc) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     main() |  | ||||||
|  | @ -1,92 +0,0 @@ | ||||||
| --- |  | ||||||
| # Metadata |  | ||||||
| title: ED_LRR |  | ||||||
| author: |  | ||||||
| - Daniel Seiller <earthnuker@gmail.com> |  | ||||||
| subtitle: 'Elite Dangerous: Long-Range Router' |  | ||||||
| 
 |  | ||||||
| # Formating |  | ||||||
| toc: true |  | ||||||
| lang: en |  | ||||||
| colorlinks: true |  | ||||||
| papersize: a4 |  | ||||||
| numbersections: true |  | ||||||
| 
 |  | ||||||
| #Panflute options |  | ||||||
| panflute-filters: [multifilter] |  | ||||||
| panflute-path: 'filters' |  | ||||||
| 
 |  | ||||||
| #Template options |  | ||||||
| titlepage: true |  | ||||||
| toc-own-page: false |  | ||||||
| --- |  | ||||||
| 
 |  | ||||||
| \pagebreak |  | ||||||
| 
 |  | ||||||
| # Implementation |  | ||||||
| 
 |  | ||||||
| ## `stars.csv` format |  | ||||||
| 
 |  | ||||||
| ### Columns |  | ||||||
| 
 |  | ||||||
| | Name      | Content                                                      | |  | ||||||
| | --------- | ------------------------------------------------------------ | |  | ||||||
| | id        | unique ID-Number (not equal to id or id64, just a sequential number) | |  | ||||||
| | star_type | Type of Star                                                 | |  | ||||||
| | system    | Name of System                                               | |  | ||||||
| | body      | Name of Star                                                 | |  | ||||||
| | mult      | Jump Range Multiplier (1.5 for White Dwarfs, 4.0 for Neutron Stars) | |  | ||||||
| | distance  | Distance from arrival in Ls                                  | |  | ||||||
| | x,y,z     | Position in Galactic Coordinates with Sol at (0,0,0)         | |  | ||||||
| 
 |  | ||||||
| ## `stars.idx` format |  | ||||||
| 
 |  | ||||||
| `bincode` serialized data: |  | ||||||
| 
 |  | ||||||
| - **[u64]**: List of byte offset for records (entry 0=first recod, entry 1= second record, etc) |  | ||||||
| 
 |  | ||||||
| ## Routing Algorithms |  | ||||||
| 
 |  | ||||||
| ### Breadth-First Search (BFS) |  | ||||||
| 
 |  | ||||||
| Standard Breadth-First Search, always finds the shortest route |  | ||||||
| 
 |  | ||||||
| ### A*-Search |  | ||||||
| 
 |  | ||||||
| Modified A*-Search with adjustable "greediness". Priority Queue weighted by $\text{number of jumps from start system} + (\text{estimated number of jumps to target system} * \text{greediness})$ |  | ||||||
| 
 |  | ||||||
| A greediness of 0 is equivalent to BFS and a greediness of $\infty$ is equivalent to Greedy-Search |  | ||||||
| 
 |  | ||||||
| ### Greedy-Search |  | ||||||
| 
 |  | ||||||
| Priority Queue weighted by minimum distance to target, prefers systems with high multiplier (Neutron Stars and White Dwarfs) |  | ||||||
| 
 |  | ||||||
| ## Optimizations |  | ||||||
| 
 |  | ||||||
| ### Ellipse elimination |  | ||||||
| 
 |  | ||||||
| Only consider systems within an ellipsoid with source and destination as the foci, the width of the ellipsoid is adjustable |  | ||||||
| 
 |  | ||||||
| ## Routing Graphs |  | ||||||
| 
 |  | ||||||
| ### File format |  | ||||||
| 
 |  | ||||||
| `bincode` serialized data: |  | ||||||
| 
 |  | ||||||
| - *bool* **primary**: flag to indicate that graph only includes primary stars |  | ||||||
| - *f32* **range**: jump range for routing graph |  | ||||||
| - *[u8]* **file_hash**: sha3 hash of `stars.csv` from which graph was generated |  | ||||||
| - *String* **path**: path to `stars.csv` from which graph was generated |  | ||||||
| - *FnvHashMap* **graph**: Hashmap mapping systems to the systems from which they can be rached, traversed from destination system backwards to source to reconstruct route |  | ||||||
| 
 |  | ||||||
| # Usage |  | ||||||
| 
 |  | ||||||
| <!-- |  | ||||||
| TODO: Add screenshots |  | ||||||
| --> |  | ||||||
| 
 |  | ||||||
| ## Preprocessing Data |  | ||||||
| 
 |  | ||||||
| ## Plotting a Route |  | ||||||
| 
 |  | ||||||
| # [Changelog](https://gitlab.com/Earthnuker/ed_lrr/blob/pyqt_gui/CHANGELOG.md) |  | ||||||
|  | @ -1,83 +0,0 @@ | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
| import heapq |  | ||||||
| import sys |  | ||||||
| 
 |  | ||||||
| import numpy as np |  | ||||||
| import pylab as PL |  | ||||||
| from scipy.spatial.ckdtree import cKDTree |  | ||||||
| 
 |  | ||||||
| exit() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def vec(a, b): |  | ||||||
|     return b - a |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def bfs(points): |  | ||||||
|     return |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def in_ellipse(p, f1, f2, r, offset=0): |  | ||||||
|     df = ((f1 - f2) ** 2).sum(0) ** 0.5 |  | ||||||
|     d_f1 = ((p - f1) ** 2).sum(1) ** 0.5 |  | ||||||
|     d_f2 = ((p - f2) ** 2).sum(1) ** 0.5 |  | ||||||
|     return (d_f1 + d_f2) < (df * (1 + r)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| num_points = 100000 |  | ||||||
| 
 |  | ||||||
| p_orig = np.random.normal(0, 10, size=(num_points, 2)) |  | ||||||
| tree = cKDTree(p_orig) |  | ||||||
| f1 = np.array([0, -30]) |  | ||||||
| f2 = -f1  # np.random.normal(0, 20, (3,)) |  | ||||||
| # r = 2 ** ((n / cnt) - cnt) |  | ||||||
| 
 |  | ||||||
| mask = in_ellipse(p_orig, f1, f2, 0.1) |  | ||||||
| 
 |  | ||||||
| p = p_orig[mask] |  | ||||||
| p_orig = p_orig[~mask] |  | ||||||
| 
 |  | ||||||
| colors = np.random.random(p.shape[0]) |  | ||||||
| fig = PL.gcf() |  | ||||||
| PL.scatter( |  | ||||||
|     p_orig[:, 0], |  | ||||||
|     p_orig[:, 1], |  | ||||||
|     marker=".", |  | ||||||
|     s=0.2, |  | ||||||
|     edgecolor="None", |  | ||||||
|     c=[(0.0, 0.0, 0.0)], |  | ||||||
|     alpha=0.75, |  | ||||||
|     rasterized=True, |  | ||||||
| ) |  | ||||||
| PL.scatter( |  | ||||||
|     p[:, 0], |  | ||||||
|     p[:, 1], |  | ||||||
|     marker="s", |  | ||||||
|     s=0.2, |  | ||||||
|     edgecolor="None", |  | ||||||
|     c=colors, |  | ||||||
|     rasterized=True |  | ||||||
| ) |  | ||||||
| PL.plot(f1[0], f1[1], "r.", label="Source") |  | ||||||
| PL.plot(f2[0], f2[1], "g.", label="Destination") |  | ||||||
| 
 |  | ||||||
| max_v = max( |  | ||||||
|     p_orig[:, 0].max(), |  | ||||||
|     p_orig[:, 1].max(), |  | ||||||
|     f1[0], f1[1], |  | ||||||
|     f2[0], f2[1] |  | ||||||
| ) + 2 |  | ||||||
| 
 |  | ||||||
| min_v = min( |  | ||||||
|     p_orig[:, 0].min(), |  | ||||||
|     p_orig[:, 1].min(), |  | ||||||
|     f1[0], f1[1], |  | ||||||
|     f2[0], f2[1] |  | ||||||
| ) - 2 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| PL.xlim(min_v, max_v) |  | ||||||
| PL.ylim(min_v, max_v) |  | ||||||
| 
 |  | ||||||
| PL.legend() |  | ||||||
| PL.savefig(sys.argv[1], dpi=1200) |  | ||||||
							
								
								
									
										1
									
								
								docs_mdbook/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs_mdbook/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | book | ||||||
|  | @ -4,8 +4,8 @@ | ||||||
|         "en" |         "en" | ||||||
|     ], |     ], | ||||||
|     "spellright.documentTypes": [ |     "spellright.documentTypes": [ | ||||||
|         "markdown", |  | ||||||
|         "latex", |         "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", |     "-g", | ||||||
|     metavar="<float>", |     metavar="<float>", | ||||||
|     default=cfg["route.greediness"], |     default=cfg["route.greediness"], | ||||||
|     help="Greedyness factor for A-Star", |     help="Greedyness factor (0.0=BFS, 1.0=Greedy)", | ||||||
|     show_default=True, |  | ||||||
| ) |  | ||||||
| @click.option( |  | ||||||
|     "--mode", |  | ||||||
|     "-m", |  | ||||||
|     default=cfg["route.mode"], |  | ||||||
|     help="Search mode", |  | ||||||
|     type=click.Choice(["bfs", "bfs_old", "a-star", "greedy"]), |  | ||||||
|     show_default=True, |     show_default=True, | ||||||
| ) | ) | ||||||
| @click.option( | @click.option( | ||||||
|  |  | ||||||
							
								
								
									
										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 -*- | # -*- coding: utf-8 -*- | ||||||
| import svgwrite | import svgwrite | ||||||
|  | import svgpathtools | ||||||
| import random | import random | ||||||
|  | import tempfile | ||||||
|  | import os | ||||||
| from math import sin, cos, pi | from math import sin, cos, pi | ||||||
| import tsp as m_tsp | import tsp as m_tsp | ||||||
| 
 | 
 | ||||||
|  | @ -30,8 +33,6 @@ def make_points(n, size, min_dist=0): | ||||||
|         px, py = random.random(), random.random() |         px, py = random.random(), random.random() | ||||||
|         px *= size / 2 |         px *= size / 2 | ||||||
|         py *= size / 2 |         py *= size / 2 | ||||||
|         px += 70 |  | ||||||
|         py += 70 |  | ||||||
|         valid = True |         valid = True | ||||||
|         for p in points: |         for p in points: | ||||||
|             if dist2(p, (px, py)) < min_dist: |             if dist2(p, (px, py)) < min_dist: | ||||||
|  | @ -60,9 +61,9 @@ def generate(seed, name=None, small=False): | ||||||
|     size = 1000 |     size = 1000 | ||||||
|     if name is None: |     if name is None: | ||||||
|         name = seed |         name = seed | ||||||
|     dwg = svgwrite.Drawing(filename="out/{}.svg".format(name)) |     out_path = "out/{}.svg".format(name) | ||||||
|     dwg.defs.add(dwg.style(".background { fill: #222; }")) |     dwg = svgwrite.Drawing(filename=out_path) | ||||||
|     dwg.add(dwg.rect(size=("100%", "100%"), class_="background")) |     dwg.defs.add(dwg.style(".background { fill: #222 }")) | ||||||
|     print("Generating points...") |     print("Generating points...") | ||||||
|     color = "#eee" |     color = "#eee" | ||||||
|     pos = make_points(num_points, size, min_dist=min_dist) |     pos = make_points(num_points, size, min_dist=min_dist) | ||||||
|  | @ -74,12 +75,7 @@ def generate(seed, name=None, small=False): | ||||||
|             x2 /= sd |             x2 /= sd | ||||||
|             y1 /= sd |             y1 /= sd | ||||||
|             y2 /= sd |             y2 /= sd | ||||||
|         dwg.add(dwg.line( |         dwg.add(dwg.line((x1, y1), (x2, y2), stroke_width=w, stroke=color)) | ||||||
|             (x1, y1), |  | ||||||
|             (x2, y2), |  | ||||||
|             stroke_width=w, |  | ||||||
|             stroke=color |  | ||||||
|         )) |  | ||||||
| 
 | 
 | ||||||
|     for (px, py) in pos: |     for (px, py) in pos: | ||||||
|         base_r = 3 |         base_r = 3 | ||||||
|  | @ -111,17 +107,13 @@ def generate(seed, name=None, small=False): | ||||||
|                 random.random() |                 random.random() | ||||||
|                 random.random() |                 random.random() | ||||||
|                 random.random() |                 random.random() | ||||||
|  |                 random.random() | ||||||
|                 continue |                 continue | ||||||
|             r += ring_step(random.random()) |             r += ring_step(random.random()) | ||||||
|             ring_col = color |             ring_col = color | ||||||
|             if random.random() > 0.75: |             if random.random() > 0.75: | ||||||
|                 ring_col = "#ea0" |                 ring_col = "#ea0" | ||||||
|             circ = dwg.add(dwg.circle( |             circ = dwg.add(dwg.circle((px, py), r=r, stroke_width=w, stroke=ring_col)) | ||||||
|                 (px, py), |  | ||||||
|                 r=r, |  | ||||||
|                 stroke_width=w, |  | ||||||
|                 stroke=ring_col |  | ||||||
|             )) |  | ||||||
|             circ.fill(color, opacity=0) |             circ.fill(color, opacity=0) | ||||||
|             d = random.random() * pi * 2 |             d = random.random() * pi * 2 | ||||||
|             dx = cos(d) |             dx = cos(d) | ||||||
|  | @ -136,10 +128,27 @@ def generate(seed, name=None, small=False): | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             moon.fill(ring_col) |             moon.fill(ring_col) | ||||||
| 
 |     dwg.fit() | ||||||
|     dwg.save() |     path = tempfile.TemporaryDirectory() | ||||||
|  |     filename = os.path.join(path.name, "out.svg") | ||||||
|  |     dwg.saveas(filename) | ||||||
|  |     paths, attrs = svgpathtools.svg2paths(filename) | ||||||
|  |     bbox = [float("inf"), float("-inf"), float("inf"), float("-inf")] | ||||||
|  |     for path in paths: | ||||||
|  |         path_bbox = path.bbox() | ||||||
|  |         bbox[0] = min(bbox[0], path_bbox[0])  # xmin | ||||||
|  |         bbox[1] = max(bbox[1], path_bbox[1])  # xmax | ||||||
|  |         bbox[2] = min(bbox[2], path_bbox[2])  # ymin | ||||||
|  |         bbox[3] = max(bbox[3], path_bbox[3])  # ymax | ||||||
|  |     px = bbox[0] | ||||||
|  |     sx = (bbox[1] - bbox[0]) | ||||||
|  |     py = bbox[2] | ||||||
|  |     sy = (bbox[3] - bbox[2]) | ||||||
|  |     dwg.add(dwg.rect(x=px, y=px, size=(sx, sy), class_="background")) | ||||||
|  |     dwg.elements.insert(1, dwg.elements.pop(-1)) | ||||||
|  |     dwg.saveas(out_path) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| seed = -4 | seed = -5 | ||||||
| generate(seed, "icon_1", small=False) | generate(seed, "icon_1", small=False) | ||||||
| generate(seed, "icon_1_small", small=True) | generate(seed, "icon_1_small", small=True) | ||||||
|  |  | ||||||
							
								
								
									
										
											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" ?> | <?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" ?> | <?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.options.keywords = "test" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @nox.session(venv_backend="conda") | @nox.session(python=versions,venv_backend="conda") | ||||||
| def devenv(session): | def devenv(session): | ||||||
|     """Set up development environment""" |     """Set up development environment""" | ||||||
|     global path |     global path | ||||||
|     location = os.path.abspath(session._runner.venv.location_name) |     location = os.path.abspath(session._runner.venv.location_name) | ||||||
|     session.env["PATH"] = os.pathsep.join([location, path, location]) |     session.env["PATH"] = os.pathsep.join([location, path, location]) | ||||||
|     session.conda_install("pycrypto", "ujson") |     session.conda_install("pycrypto", "ujson") | ||||||
|     session.install("--no-cache-dir", "-e",".[all]") |     session.install("--no-cache-dir", "-e",".") | ||||||
|     logger.warning(f'Devenv set up, now run "conda activate {location}"') |     logger.warning(f'Devenv set up, now run "conda activate {location}"') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										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] | [package] | ||||||
| name = "ed_lrr" | name = "ed_lrr" | ||||||
| version = "0.2.0" | version = "0.2.0" | ||||||
| authors = [ "Daniel Seiller <earthnuker@gmail.com>",] | authors = ["Daniel Seiller <earthnuker@gmail.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| repository = "https://gitlab.com/Earthnuker/ed_lrr.git" | repository = "https://gitlab.com/Earthnuker/ed_lrr.git" | ||||||
| license = "MIT" | license = "MIT" | ||||||
| 
 | 
 | ||||||
| [lib] | [lib] | ||||||
| crate-type = [ "cdylib",] | crate-type = ["cdylib"] | ||||||
| name = "_ed_lrr" | name = "_ed_lrr" | ||||||
| 
 | 
 | ||||||
| [dependencies] | [profile.release] | ||||||
| csv = "1.1.3" | codegen-units = 1 | ||||||
| humantime = "2.0.1" | opt-level = 3 | ||||||
| permutohedron = "0.2.4" | debug = true | ||||||
| serde_json = "1.0.55" | lto = "fat" | ||||||
| fnv = "1.0.7" | 
 | ||||||
| bincode = "1.2.1" | 
 | ||||||
| sha3 = "0.9.0" | [dependencies] | ||||||
| byteorder = "1.3.4" | pyo3 = { version = "0.15.1", features = ["extension-module","eyre"] } | ||||||
| strsim = "0.10.0" | csv = "1.1.6" | ||||||
| rstar = "0.8.0" | humantime = "2.1.0" | ||||||
| crossbeam-channel = "0.4.2" | permutohedron = "0.2.4" | ||||||
| better-panic = "0.2.0" | serde_json = "1.0.74" | ||||||
| derivative = "2.1.1" | fnv = "1.0.7" | ||||||
| dict_derive = "0.2.0" | bincode = "1.3.3" | ||||||
| num_cpus = "1.13.0" | sha3 = "0.10.0" | ||||||
| regex = "1.3.9" | byteorder = "1.4.3" | ||||||
| chrono = "0.4.11" | rstar = "0.9.2" | ||||||
| 
 | crossbeam-channel = "0.5.2" | ||||||
| [dependencies.pyo3] | better-panic = "0.3.0" | ||||||
| git = "https://github.com/PyO3/pyo3" | derivative = "2.2.0" | ||||||
| features = [ "extension-module",] | dict_derive = "0.4.0" | ||||||
| 
 | regex = "1.5.4" | ||||||
| [dependencies.serde] | num_cpus = "1.13.1" | ||||||
| version = "1.0.112" | eddie = "0.4.2" | ||||||
| features = [ "derive",] | thiserror = "1.0.30" | ||||||
| 
 | pyo3-log = "0.5.0" | ||||||
| [profile.release] | log = "0.4.14" | ||||||
| codegen-units = 1 | flate2 = "1.0.22" | ||||||
| lto = true | eval = "0.4.3" | ||||||
|  | pythonize = "0.15.0" | ||||||
|  | itertools = "0.10.3" | ||||||
|  | intmap = "0.7.1" | ||||||
|  | diff-struct = "0.4.1" | ||||||
|  | rustc-hash = "1.1.0" | ||||||
|  | stats_alloc = "0.1.8" | ||||||
|  | 
 | ||||||
|  | tracing = { version = "0.1.29", optional = true } | ||||||
|  | tracing-subscriber = { version = "0.3.5", optional = true } | ||||||
|  | tracing-tracy = { version = "0.8.0", optional = true } | ||||||
|  | tracing-unwrap = { version = "0.9.2", optional = true } | ||||||
|  | tracy-client = { version = "0.12.6", optional = true } | ||||||
|  | tracing-chrome = "0.4.0" | ||||||
|  | rand = "0.8.4" | ||||||
|  | eyre = "0.6.6" | ||||||
|  | memmap = "0.7.0" | ||||||
|  | csv-core = "0.1.10" | ||||||
|  | postcard = { version = "0.7.3", features = ["alloc"] } | ||||||
|  | nohash-hasher = "0.2.0" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | [features] | ||||||
|  | profiling = ["tracing","tracing-subscriber","tracing-tracy","tracing-unwrap","tracy-client"] | ||||||
|  | 
 | ||||||
|  | [dev-dependencies] | ||||||
|  | criterion = { version = "0.3.5", features = ["real_blackbox"] } | ||||||
|  | rand = "0.8.4" | ||||||
|  | rand_distr = "0.4.2" | ||||||
|  | 
 | ||||||
|  | [dependencies.serde] | ||||||
|  | version = "1.0.133" | ||||||
|  | features = ["derive"] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | [[bench]] | ||||||
|  | name = "dot_bench" | ||||||
|  | harness = false | ||||||
|  |  | ||||||
							
								
								
									
										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 dict_derive::IntoPyObject; | ||||||
| use pyo3::conversion::ToPyObject; | use eyre::Result; | ||||||
|  | use log::*; | ||||||
| use pyo3::prelude::*; | use pyo3::prelude::*; | ||||||
| use pyo3::types::{PyDict, PyTuple}; | use pyo3::types::PyDict; | ||||||
|  | use pyo3::{conversion::ToPyObject, create_exception}; | ||||||
|  | use pythonize::depythonize; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use std::cmp::Ordering; | use sha3::{Digest, Sha3_256}; | ||||||
| use std::collections::HashMap; | use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; | ||||||
| use std::path::PathBuf; | use std::hash::{Hash, Hasher, BuildHasherDefault}; | ||||||
|  | use std::io::Write; | ||||||
|  | use std::ops::{Deref, DerefMut}; | ||||||
|  | use std::path::Path; | ||||||
|  | use std::str::FromStr; | ||||||
|  | use std::thread; | ||||||
|  | use std::{cmp::Ordering, cmp::Reverse, collections::BinaryHeap}; | ||||||
|  | use std::{ | ||||||
|  |     fs::File, | ||||||
|  |     io::{BufReader, BufWriter}, | ||||||
|  |     path::PathBuf, | ||||||
|  | }; | ||||||
|  | use nohash_hasher::NoHashHasher; | ||||||
|  | use thiserror::Error; | ||||||
| 
 | 
 | ||||||
|  | #[inline(always)] | ||||||
|  | pub fn heuristic(range: f32, node: &TreeNode, goal: &TreeNode) -> f32 { | ||||||
|  |     // distance remaining after jumping from node towards goal
 | ||||||
|  |     let a2 = dist2(&node.pos, &goal.pos); | ||||||
|  |     let mult=node.get_mult(); | ||||||
|  |     let b2 = range * range * mult*mult; | ||||||
|  |     return (a2 - b2).max(0.0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Min-heap priority queue using f32 as priority
 | ||||||
|  | pub struct MinFHeap<T: Ord>(pub BinaryHeap<(Reverse<F32>, T)>); | ||||||
|  | /// Max-heap priority queue using f32 as priority
 | ||||||
|  | pub struct MaxFHeap<T: Ord>(pub BinaryHeap<(F32, T)>); | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> MaxFHeap<T> { | ||||||
|  |     /// Create new, empty priority queue
 | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         MaxFHeap(BinaryHeap::new()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// push value `item` with priority `w` into queue
 | ||||||
|  |     pub fn push(&mut self, w: f32, item: T) { | ||||||
|  |         self.0.push((F32(w), item)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Remove and return largest item and priority
 | ||||||
|  |     pub fn pop(&mut self) -> Option<(f32, T)> { | ||||||
|  |         self.0.pop().map(|(F32(w), item)| (w, item)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> Default for MaxFHeap<T> { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         return MaxFHeap(BinaryHeap::new()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> Deref for MaxFHeap<T> { | ||||||
|  |     type Target = BinaryHeap<(F32, T)>; | ||||||
|  |     fn deref(&self) -> &Self::Target { | ||||||
|  |         &self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> DerefMut for MaxFHeap<T> { | ||||||
|  |     fn deref_mut(&mut self) -> &mut Self::Target { | ||||||
|  |         &mut self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> MinFHeap<T> { | ||||||
|  |     /// Create new, empty priority queue
 | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         MinFHeap(BinaryHeap::new()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// push value `item` with priority `w` into queue
 | ||||||
|  |     pub fn push(&mut self, w: f32, item: T) { | ||||||
|  |         self.0.push((Reverse(F32(w)), item)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Remove and return smallest item and priority
 | ||||||
|  |     pub fn pop(&mut self) -> Option<(f32, T)> { | ||||||
|  |         self.0.pop().map(|(Reverse(F32(w)), item)| (w, item)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> Default for MinFHeap<T> { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         return MinFHeap(BinaryHeap::new()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> Deref for MinFHeap<T> { | ||||||
|  |     type Target = BinaryHeap<(Reverse<F32>, T)>; | ||||||
|  |     fn deref(&self) -> &Self::Target { | ||||||
|  |         &self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Ord> DerefMut for MinFHeap<T> { | ||||||
|  |     fn deref_mut(&mut self) -> &mut Self::Target { | ||||||
|  |         &mut self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | /// ED LRR error type
 | ||||||
|  | #[derive(Error, Debug)] | ||||||
|  | pub enum EdLrrError { | ||||||
|  |     #[error("failed to compute route from {from:?} to {to:?}: {reason}")] | ||||||
|  |     RouteError { | ||||||
|  |         from: Option<System>, | ||||||
|  |         to: Option<System>, | ||||||
|  |         reason: String, | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     #[error("failed to find system matching {0:?}")] | ||||||
|  |     ResolveError(String), | ||||||
|  | 
 | ||||||
|  |     #[error("runtime error: {0:?}")] | ||||||
|  |     RuntimeError(String), | ||||||
|  | 
 | ||||||
|  |     #[error("Failed to process {0}")] | ||||||
|  |     ProcessingError(PathBuf), | ||||||
|  | 
 | ||||||
|  |     #[error(transparent)] | ||||||
|  |     EvalError(#[from] eval::Error), | ||||||
|  | 
 | ||||||
|  |     #[error(transparent)] | ||||||
|  |     CSVError(#[from] csv::Error), | ||||||
|  | 
 | ||||||
|  |     #[error(transparent)] | ||||||
|  |     IOError(#[from] std::io::Error), | ||||||
|  | 
 | ||||||
|  |     #[error(transparent)] | ||||||
|  |     BincodeError(#[from] Box<bincode::ErrorKind>), | ||||||
|  | 
 | ||||||
|  |     #[error(transparent)] | ||||||
|  |     PyError(#[from] pyo3::PyErr), | ||||||
|  | 
 | ||||||
|  |     #[error(transparent)] | ||||||
|  |     Error(#[from] eyre::Error), | ||||||
|  | 
 | ||||||
|  |     #[error("unknown error")] | ||||||
|  |     Unknown, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub mod py_exceptions { | ||||||
|  |     use super::*; | ||||||
|  |     pub use pyo3::exceptions::*; | ||||||
|  |     create_exception!(_ed_lrr, RouteError, PyException); | ||||||
|  |     create_exception!(_ed_lrr, ResolveError, PyException); | ||||||
|  |     create_exception!(_ed_lrr, EdLrrException, PyException); | ||||||
|  |     create_exception!(_ed_lrr, ProcessingError, PyException); | ||||||
|  |     create_exception!(_ed_lrr, FileFormatError, PyException); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FromStr for EdLrrError { | ||||||
|  |     type Err = (); | ||||||
|  | 
 | ||||||
|  |     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||||
|  |         Ok(Self::RuntimeError(s.to_owned())) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::convert::From<String> for EdLrrError { | ||||||
|  |     fn from(s: String) -> Self { | ||||||
|  |         Self::RuntimeError(s) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::convert::From<EdLrrError> for PyErr { | ||||||
|  |     fn from(err: EdLrrError) -> PyErr { | ||||||
|  |         match err { | ||||||
|  |             EdLrrError::PyError(e) => e, | ||||||
|  |             EdLrrError::BincodeError(..) => { | ||||||
|  |                 py_exceptions::FileFormatError::new_err(err.to_string()) | ||||||
|  |             } | ||||||
|  |             EdLrrError::RouteError { .. } => py_exceptions::RouteError::new_err(err.to_string()), | ||||||
|  |             EdLrrError::RuntimeError(msg) => py_exceptions::PyRuntimeError::new_err(msg), | ||||||
|  |             EdLrrError::ResolveError(..) => py_exceptions::PyRuntimeError::new_err(err.to_string()), | ||||||
|  |             EdLrrError::EvalError(err) => py_exceptions::PyRuntimeError::new_err(err.to_string()), | ||||||
|  |             EdLrrError::CSVError(err) => py_exceptions::PyRuntimeError::new_err(err.to_string()), | ||||||
|  |             EdLrrError::IOError(err) => py_exceptions::PyIOError::new_err(err.to_string()), | ||||||
|  |             EdLrrError::Error(err) => py_exceptions::EdLrrException::new_err(err.to_string()), | ||||||
|  |             EdLrrError::ProcessingError(buf) => { | ||||||
|  |                 py_exceptions::ProcessingError::new_err(format!("{}", buf.display())) | ||||||
|  |             } | ||||||
|  |             EdLrrError::Unknown => { | ||||||
|  |                 py_exceptions::EdLrrException::new_err("Unknown error!".to_string()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub type EdLrrResult<T> = Result<T, EdLrrError>; | ||||||
|  | 
 | ||||||
|  | /// f32 compare wrapper
 | ||||||
|  | pub fn fcmp(a: f32, b: f32) -> Ordering { | ||||||
|  |     match (a, b) { | ||||||
|  |         (x, y) if x.is_nan() && y.is_nan() => Ordering::Equal, | ||||||
|  |         (x, _) if x.is_nan() => Ordering::Greater, | ||||||
|  |         (_, y) if y.is_nan() => Ordering::Less, | ||||||
|  |         (..) => a.partial_cmp(&b).unwrap(), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// f32 warpper type implementing `Eq` and `Ord`
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct F32(pub f32); | ||||||
|  | 
 | ||||||
|  | impl PartialEq for F32 { | ||||||
|  |     fn eq(&self, other: &F32) -> bool { | ||||||
|  |         fcmp(self.0, other.0) == std::cmp::Ordering::Equal | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Eq for F32 {} | ||||||
|  | 
 | ||||||
|  | impl PartialOrd for F32 { | ||||||
|  |     fn partial_cmp(&self, other: &F32) -> Option<std::cmp::Ordering> { | ||||||
|  |         Some(fcmp(self.0, other.0)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Ord for F32 { | ||||||
|  |     fn cmp(&self, other: &F32) -> std::cmp::Ordering { | ||||||
|  |         fcmp(self.0, other.0) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Deref for F32 { | ||||||
|  |     type Target = f32; | ||||||
|  |     fn deref(&self) -> &Self::Target { | ||||||
|  |         &self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl DerefMut for F32 { | ||||||
|  |     fn deref_mut(&mut self) -> &mut Self::Target { | ||||||
|  |         &mut self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Returns additional jump range (in Ly) granted by specified class of Guardian FSD Booster
 | ||||||
| pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> { | pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> { | ||||||
|     // Data from https://elite-dangerous.fandom.com/wiki/Guardian_Frame_Shift_Drive_Booster
 |     // Data from https://elite-dangerous.fandom.com/wiki/Guardian_Frame_Shift_Drive_Booster
 | ||||||
|     let ret = match class { |     let ret = match class { | ||||||
|  | @ -22,6 +266,7 @@ pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> { | ||||||
|     return Ok(ret); |     return Ok(ret); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Returns optimal mass and maximum fuel per jump for the given FSD rating and class as a hash map
 | ||||||
| pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, String> { | pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, String> { | ||||||
|     let mut ret = HashMap::new(); |     let mut ret = HashMap::new(); | ||||||
|     // Data from https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Specifications
 |     // Data from https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Specifications
 | ||||||
|  | @ -68,6 +313,7 @@ pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, | ||||||
|     return Ok(ret); |     return Ok(ret); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Returns jump range multiplier for the specified star type (4 for neutron stars, 1.5 for white dwarfs and 1.0 otherwise)
 | ||||||
| pub fn get_mult(star_type: &str) -> f32 { | pub fn get_mult(star_type: &str) -> f32 { | ||||||
|     if star_type.contains("White Dwarf") { |     if star_type.contains("White Dwarf") { | ||||||
|         return 1.5; |         return 1.5; | ||||||
|  | @ -78,133 +324,403 @@ pub fn get_mult(star_type: &str) -> f32 { | ||||||
|     1.0 |     1.0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone, Deserialize)] | ||||||
|  | #[serde(rename_all = "lowercase")] | ||||||
|  | pub enum BeamWidth { | ||||||
|  |     Absolute(usize), | ||||||
|  |     Fraction(f32), | ||||||
|  |     Radius(f32), | ||||||
|  |     Infinite, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for BeamWidth { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         match self { | ||||||
|  |             BeamWidth::Absolute(n) => write!(f, "{}", n), | ||||||
|  |             BeamWidth::Fraction(v) => write!(f, "{}%", (*v) * 100.0), | ||||||
|  |             BeamWidth::Radius(r) => write!(f, "{} Ly", r), | ||||||
|  |             BeamWidth::Infinite => write!(f, "Infinite"), | ||||||
|  |         }?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for BeamWidth { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::Infinite | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FromPyObject<'_> for BeamWidth { | ||||||
|  |     fn extract(ob: &PyAny) -> PyResult<Self> { | ||||||
|  |         depythonize(ob).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e))) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl BeamWidth { | ||||||
|  |     pub fn is_set(&self) -> bool { | ||||||
|  |         match self { | ||||||
|  |             Self::Fraction(f) => *f > 0.0, | ||||||
|  |             Self::Absolute(n) => *n != 0, | ||||||
|  |             Self::Radius(r) => *r > 0.0, | ||||||
|  |             Self::Infinite => false, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn is_infinite(&self) -> bool { | ||||||
|  |         matches!(self, Self::Infinite) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn compute(&self, nodes: usize) -> usize { | ||||||
|  |         match self { | ||||||
|  |             Self::Fraction(f) => { | ||||||
|  |                 let w = (nodes as f32) * f.max(0.0).min(1.0); | ||||||
|  |                 return (w.ceil() as usize).max(1); | ||||||
|  |             } | ||||||
|  |             Self::Absolute(n) => *n, | ||||||
|  |             Self::Radius(_) | Self::Infinite => nodes, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | /// Represents an uresolved system to be searched for by name, id or position
 | ||||||
|  | #[derive(Debug, FromPyObject)] | ||||||
| pub enum SysEntry { | pub enum SysEntry { | ||||||
|     ID(u32), |     ID(u32), | ||||||
|     Name(String), |     Name(String), | ||||||
|  |     Pos((f32, f32, f32)), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SysEntry { | impl ToPyObject for SysEntry { | ||||||
|     pub fn parse(s: &str) -> Self { |     fn to_object(&self, py: Python) -> PyObject { | ||||||
|         if let Ok(n) = s.parse() { |         match self { | ||||||
|             SysEntry::ID(n) |             Self::ID(id) => id.to_object(py), | ||||||
|         } else { |             Self::Name(name) => name.to_object(py), | ||||||
|             SysEntry::Name(s.to_owned()) |             Self::Pos(pos) => pos.to_object(py), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn find_matches( | pub fn grid_stats( | ||||||
|     path: &PathBuf, |     path: &Path, | ||||||
|  |     grid_size: f32, | ||||||
|  | ) -> Result<BTreeMap<(i64, i64, i64), Vec<u32>>, String> { | ||||||
|  |     let mut reader = match csv::ReaderBuilder::new().has_headers(false).from_path(path) { | ||||||
|  |         Ok(rdr) => rdr, | ||||||
|  |         Err(e) => { | ||||||
|  |             return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e)); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |     let systems = reader.deserialize::<System>().map(Result::unwrap); | ||||||
|  |     let mut ret: BTreeMap<(i64, i64, i64), Vec<u32>> = BTreeMap::new(); | ||||||
|  |     for sys in systems { | ||||||
|  |         let k = ( | ||||||
|  |             ((sys.pos[0] / grid_size).round() * grid_size) as i64, | ||||||
|  |             ((sys.pos[1] / grid_size).round() * grid_size) as i64, | ||||||
|  |             ((sys.pos[2] / grid_size).round() * grid_size) as i64, | ||||||
|  |         ); | ||||||
|  |         ret.entry(k).or_default().push(sys.id); | ||||||
|  |     } | ||||||
|  |     Ok(ret) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub enum Node { | ||||||
|  |     Start, | ||||||
|  |     Goal, | ||||||
|  |     ID(u32), | ||||||
|  | } | ||||||
|  | pub enum Weight { | ||||||
|  |     Dist(Node), | ||||||
|  |     Depth, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Weight { | ||||||
|  |     fn eval(&self) -> f32 { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | struct Weights(Vec<(f32, Weight)>); | ||||||
|  | 
 | ||||||
|  | impl Weights { | ||||||
|  |     fn new() -> Self { | ||||||
|  |         Self(vec![]) | ||||||
|  |     } | ||||||
|  |     fn add(&mut self, w: f32, v: Weight) { | ||||||
|  |         self.0.push((w, v)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn eval(&mut self) -> f32 { | ||||||
|  |         self.0.iter().map(|(w, v)| w * v.eval()).sum() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[inline(always)] | ||||||
|  | pub fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||||
|  |     let dx = p1[0] - p2[0]; | ||||||
|  |     let dy = p1[1] - p2[1]; | ||||||
|  |     let dz = p1[2] - p2[2]; | ||||||
|  | 
 | ||||||
|  |     dx * dx + dy * dy + dz * dz | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[inline(always)] | ||||||
|  | pub fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||||
|  |     dist2(p1, p2).sqrt() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[inline(always)] | ||||||
|  | pub fn distm(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||||
|  |     let dx = (p1[0] - p2[0]).abs(); | ||||||
|  |     let dy = (p1[1] - p2[1]).abs(); | ||||||
|  |     let dz = (p1[2] - p2[2]).abs(); | ||||||
|  |     dx + dy + dz | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Dot product (cosine of angle) between two 3D vectors
 | ||||||
|  | #[inline(always)] | ||||||
|  | pub fn ndot(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||||
|  |     let z: [f32; 3] = [0.0; 3]; | ||||||
|  |     let lm = dist(u, &z) * dist(v, &z); | ||||||
|  |     (u[0] * v[0]) / lm + (u[1] * v[1]) / lm + (u[2] * v[2]) / lm | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Fuzzy string matcher, use to resolve star system names
 | ||||||
|  | #[cfg_attr(feature = "profiling", tracing::instrument(skip(rx)))] | ||||||
|  | fn matcher( | ||||||
|  |     rx: Receiver<ByteRecord>, | ||||||
|     names: Vec<String>, |     names: Vec<String>, | ||||||
|     exact: bool, |     exact: bool, | ||||||
| ) -> Result<HashMap<String, (f64, Option<System>)>, String> { | ) -> HashMap<String, (f64, Option<u32>)> { | ||||||
|     let mut best: HashMap<String, (f64, Option<System>)> = HashMap::new(); |     let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new(); | ||||||
|  |     for name in &names { | ||||||
|  |         best.insert(name.to_string(), (0.0, None)); | ||||||
|  |     } | ||||||
|  |     let names_u8: Vec<(String, _)> = names.iter().map(|n| (n.clone(), n.as_bytes())).collect(); | ||||||
|  |     let sdist = eddie::slice::Levenshtein::new(); | ||||||
|  |     for sys in rx.into_iter() { | ||||||
|  |         for (name, name_b) in &names_u8 { | ||||||
|  |             if let Some(ent) = best.get_mut(name) { | ||||||
|  |                 if (ent.0 - 1.0).abs() < std::f64::EPSILON { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 if exact && (&sys[1] == *name_b) { | ||||||
|  |                     let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap(); | ||||||
|  |                     *ent = (1.0, Some(id)); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 let d = sdist.similarity(&sys[1], name_b); | ||||||
|  |                 if d > ent.0 { | ||||||
|  |                     let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap(); | ||||||
|  |                     *ent = (d, Some(id)); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     best | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Scan through the csv file at `path` and return a hash map
 | ||||||
|  | /// mapping the strings from `names` to a tuple `(score, Option<system_id>)`.
 | ||||||
|  | /// Scoring matching uses the normalized Levenshtein distance where 1.0 is an exact match.
 | ||||||
|  | pub fn find_matches( | ||||||
|  |     path: &Path, | ||||||
|  |     names: Vec<String>, | ||||||
|  |     exact: bool, | ||||||
|  | ) -> Result<HashMap<String, (f64, Option<u32>)>, String> { | ||||||
|  |     let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new(); | ||||||
|     if names.is_empty() { |     if names.is_empty() { | ||||||
|         return Ok(best); |         return Ok(best); | ||||||
|     } |     } | ||||||
|     for name in &names { |     for name in &names { | ||||||
|         best.insert(name.to_string(), (0.0, None)); |         best.insert(name.to_string(), (0.0, None)); | ||||||
|     } |     } | ||||||
|     let mut reader = match csv::ReaderBuilder::new().from_path(path) { | 
 | ||||||
|  |     let mut workers = Vec::new(); | ||||||
|  |     let ncpus = num_cpus::get(); | ||||||
|  |     let (tx, rx) = bounded(4096 * ncpus); | ||||||
|  |     for _ in 0..ncpus { | ||||||
|  |         let names = names.clone(); | ||||||
|  |         let rx = rx.clone(); | ||||||
|  |         let th = thread::spawn(move || matcher(rx, names, exact)); | ||||||
|  |         workers.push(th); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let mut rdr = match csv::ReaderBuilder::new().has_headers(false).from_path(path) { | ||||||
|         Ok(rdr) => rdr, |         Ok(rdr) => rdr, | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e)); |             return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e)); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     let systems = reader.deserialize::<SystemSerde>(); |     let t_start = std::time::Instant::now(); | ||||||
|     for sys in systems { |     let mut processed: usize = 0; | ||||||
|         let sys = sys.unwrap(); |     for record in rdr.byte_records().flat_map(|v| v.ok()) { | ||||||
|         for name in &names { |         tx.send(record).unwrap(); | ||||||
|  |         processed += 1; | ||||||
|  |     } | ||||||
|  |     drop(tx); | ||||||
|  |     while let Some(th) = workers.pop() { | ||||||
|  |         for (name, (score, sys)) in th.join().unwrap().iter() { | ||||||
|             best.entry(name.clone()).and_modify(|ent| { |             best.entry(name.clone()).and_modify(|ent| { | ||||||
|                 if (exact) && (&sys.system == name) { |                 if score > &ent.0 { | ||||||
|                     *ent = (1.0, Some(sys.clone().build())) |                     *ent = (*score, *sys); | ||||||
|                 } else { |  | ||||||
|                     let d1 = strsim::normalized_levenshtein(&sys.system, &name); |  | ||||||
|                     let d2 = strsim::normalized_levenshtein(&sys.body, &name); |  | ||||||
|                     if d1 > ent.0 { |  | ||||||
|                         *ent = (d1, Some(sys.clone().build())) |  | ||||||
|                     } else if d2 > ent.0 { |  | ||||||
|                         *ent = (d2, Some(sys.clone().build())) |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     let dt = std::time::Instant::now() - t_start; | ||||||
|  |     info!( | ||||||
|  |         "Searched {} records in {:?}: {} records/second", | ||||||
|  |         processed, | ||||||
|  |         dt, | ||||||
|  |         (processed as f64) / dt.as_secs_f64() | ||||||
|  |     ); | ||||||
|     Ok(best) |     Ok(best) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Hash the contents of `path` with sha3 and return the hash as a vector of bytes
 | ||||||
|  | fn hash_file(path: &Path) -> Vec<u8> { | ||||||
|  |     let mut hash_reader = BufReader::new(File::open(path).unwrap()); | ||||||
|  |     let mut hasher = Sha3_256::new(); | ||||||
|  |     std::io::copy(&mut hash_reader, &mut hasher).unwrap(); | ||||||
|  |     hasher.finalize().iter().copied().collect() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Construct and `O(1)` lookup index for the csv file at `path`.
 | ||||||
|  | /// The structure of the index is `(sha3, Vec<usize>)`
 | ||||||
|  | /// where the first element is the sha3 hash of the file the index belongs to
 | ||||||
|  | /// followed by a deltified vector where the entry at index `i` is the file offset for line `i` of the csv file.
 | ||||||
|  | pub fn build_index(path: &Path) -> std::io::Result<()> { | ||||||
|  |     let file_hash = hash_file(path); | ||||||
|  |     let mut wtr = BufWriter::new(File::create(path.with_extension("idx"))?); | ||||||
|  |     let mut idx: Vec<u8> = Vec::new(); | ||||||
|  |     let mut records = (csv::ReaderBuilder::new() | ||||||
|  |         .has_headers(false) | ||||||
|  |         .from_path(path)?) | ||||||
|  |     .into_deserialize::<System>(); | ||||||
|  |     let mut n: usize = 0; | ||||||
|  |     let mut size; | ||||||
|  |     idx.push(0); | ||||||
|  |     loop { | ||||||
|  |         n += 1; | ||||||
|  |         if n % 100000 == 0 { | ||||||
|  |             info!("{} Bodies processed", n); | ||||||
|  |         } | ||||||
|  |         let new_pos = records.reader().position().byte(); | ||||||
|  |         if records.next().is_none() { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         size = records.reader().position().byte() - new_pos; | ||||||
|  |         idx.push(size as u8); | ||||||
|  |     } | ||||||
|  |     assert_eq!(idx.len(), n); | ||||||
|  |     bincode::serialize_into(&mut wtr, &(file_hash, idx)).unwrap(); | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Node for R*-Tree
 | ||||||
| #[derive(Debug, Clone, Copy, Serialize, Deserialize)] | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] | ||||||
| pub struct TreeNode { | pub struct TreeNode { | ||||||
|  |     /// System ID
 | ||||||
|     pub id: u32, |     pub id: u32, | ||||||
|  |     /// Position in space
 | ||||||
|     pub pos: [f32; 3], |     pub pos: [f32; 3], | ||||||
|     pub mult: f32, |     /// flags
 | ||||||
|  |     /// 00 unscoopable
 | ||||||
|  |     /// 01 scoopable
 | ||||||
|  |     /// 10 white dward
 | ||||||
|  |     /// 11 neutron star
 | ||||||
|  |     pub flags: u8, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ToPyObject for TreeNode { | ||||||
|  |     fn to_object(&self, py: Python) -> PyObject { | ||||||
|  |         pythonize::pythonize(py, self).unwrap() | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl TreeNode { | impl TreeNode { | ||||||
|     pub fn get(&self, router: &Router) -> Option<System> { |     /// Retrieve matching [System] for this tree node
 | ||||||
|         let mut cache = router.cache.as_ref().unwrap().lock().unwrap(); |     pub fn get(&self, router: &Router) -> Result<Option<System>, String> { | ||||||
|         cache.get(self.id) |         router.get(self.id) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_mult(&self) -> f32 { | ||||||
|  |         match self.flags { | ||||||
|  |             0b11 => 4.0, | ||||||
|  |             0b10 => 1.5, | ||||||
|  |             _ => 1.0 | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl PartialEq for TreeNode { | ||||||
|  |     fn eq(&self, other: &Self) -> bool { | ||||||
|  |         self.id == other.id | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | impl Eq for TreeNode {} | ||||||
|  | 
 | ||||||
|  | impl PartialOrd for TreeNode { | ||||||
|  |     fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||||||
|  |         Some(self.id.cmp(&other.id)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Ord for TreeNode { | ||||||
|  |     fn cmp(&self, other: &TreeNode) -> Ordering { | ||||||
|  |         self.id.cmp(&other.id) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Hash for TreeNode { | ||||||
|  |     fn hash<H: Hasher>(&self, state: &mut H) { | ||||||
|  |         self.id.hash(state); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Star system info read from CSV
 | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)] | #[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)] | ||||||
| pub struct SystemSerde { |  | ||||||
|     pub id: u32, |  | ||||||
|     pub star_type: String, |  | ||||||
|     pub system: String, |  | ||||||
|     pub body: String, |  | ||||||
|     pub mult: f32, |  | ||||||
|     pub distance: f32, |  | ||||||
|     pub x: f32, |  | ||||||
|     pub y: f32, |  | ||||||
|     pub z: f32, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl SystemSerde { |  | ||||||
|     pub fn build(self) -> System { |  | ||||||
|         System { |  | ||||||
|             id: self.id, |  | ||||||
|             star_type: self.star_type, |  | ||||||
|             system: self.system, |  | ||||||
|             body: self.body, |  | ||||||
|             mult: self.mult, |  | ||||||
|             distance: self.distance, |  | ||||||
|             pos: [self.x, self.y, self.z], |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn to_node(&self) -> TreeNode { |  | ||||||
|         TreeNode { |  | ||||||
|             id: self.id, |  | ||||||
|             pos: [self.x, self.y, self.z], |  | ||||||
|             mult: self.mult, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone, Deserialize, Serialize)] |  | ||||||
| pub struct System { | pub struct System { | ||||||
|  |     /// Unique System id
 | ||||||
|     pub id: u32, |     pub id: u32, | ||||||
|     pub star_type: String, |     /// Star system
 | ||||||
|     pub system: String, |     pub name: String, | ||||||
|     pub body: String, |     /// Number of bodies
 | ||||||
|  |     pub num_bodies: u8, | ||||||
|  |     /// Does the system have a scoopable star?
 | ||||||
|  |     pub has_scoopable: bool, | ||||||
|  |     /// Jump range multiplier (1.5 for white dwarfs, 4.0 for neutron stars, 1.0 otherwise)
 | ||||||
|     pub mult: f32, |     pub mult: f32, | ||||||
|     pub distance: f32, |     /// Position
 | ||||||
|     pub pos: [f32; 3], |     pub pos: [f32; 3], | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl System { | ||||||
|  |     fn get_flags(&self) -> u8 { | ||||||
|  |         let mut flags=0; | ||||||
|  |         if self.mult==4.0 { | ||||||
|  |             return 0b11 | ||||||
|  |         } | ||||||
|  |         if self.mult==1.5 { | ||||||
|  |             return 0b10 | ||||||
|  |         } | ||||||
|  |         if self.has_scoopable { | ||||||
|  |             return 0b01 | ||||||
|  |         } | ||||||
|  |         return 0b00 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl ToPyObject for System { | impl ToPyObject for System { | ||||||
|     fn to_object(&self, py: Python) -> PyObject { |     fn to_object(&self, py: Python) -> PyObject { | ||||||
|         let pos = PyTuple::new(py, self.pos.iter()); |         let d = PyDict::new(py); | ||||||
|         let elem = PyDict::new(py); |         d.set_item("id", self.id).unwrap(); | ||||||
|         elem.set_item("star_type", self.star_type.clone()).unwrap(); |         d.set_item("name", self.name.clone()).unwrap(); | ||||||
|         elem.set_item("system", self.system.clone()).unwrap(); |         d.set_item("num_bodies", self.num_bodies).unwrap(); | ||||||
|         elem.set_item("body", self.body.clone()).unwrap(); |         d.set_item("has_scoopable", self.has_scoopable).unwrap(); | ||||||
|         elem.set_item("distance", self.distance).unwrap(); |         d.set_item("mult", self.mult).unwrap(); | ||||||
|         elem.set_item("mult", self.mult).unwrap(); |         d.set_item("pos", (self.pos[0], self.pos[1], self.pos[2])) | ||||||
|         elem.set_item("id", self.id).unwrap(); |             .unwrap(); | ||||||
|         elem.set_item("pos", pos).unwrap(); |         return d.to_object(py); | ||||||
|         elem.to_object(py) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -213,7 +729,7 @@ impl System { | ||||||
|         TreeNode { |         TreeNode { | ||||||
|             id: self.id, |             id: self.id, | ||||||
|             pos: self.pos, |             pos: self.pos, | ||||||
|             mult: self.mult, |             flags: self.get_flags(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -229,3 +745,118 @@ impl PartialOrd for System { | ||||||
|         Some(self.cmp(other)) |         Some(self.cmp(other)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct DQueue<T>(Vec<VecDeque<T>>); | ||||||
|  | 
 | ||||||
|  | impl<T> DQueue<T> { | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         Self(vec![]) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn enqueue(&mut self, depth: usize, item: T) { | ||||||
|  |         self.0.resize_with(depth, VecDeque::new); | ||||||
|  |         self.0[depth].push_back(item); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn dequeue(&mut self, depth: usize) -> Option<T> { | ||||||
|  |         self.0.resize_with(depth, VecDeque::new); | ||||||
|  |         self.0[depth].pop_back() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Default for DQueue<T> { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::new() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Default, Serialize, Deserialize)] | ||||||
|  | struct BKTreeNode { | ||||||
|  |     ids: HashSet<u32,BuildHasherDefault<NoHashHasher<u32>>>, | ||||||
|  |     children: HashMap<u8,Self,BuildHasherDefault<NoHashHasher<u8>>> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl BKTreeNode { | ||||||
|  |     fn new(data: &[String], dist: &eddie::str::Levenshtein) -> Self { | ||||||
|  |         let mut tree= Self::default(); | ||||||
|  |         let mut max_depth=0; | ||||||
|  |         (0..data.len()).map(|id| { | ||||||
|  |             max_depth=max_depth.max(tree.insert(data,id as u32, dist,0)); | ||||||
|  |             if (id>0) && (id%100_000 == 0) { | ||||||
|  |                 println!("Inserting ID {}, Max Depth: {}",id,max_depth); | ||||||
|  |             } | ||||||
|  |         }).max(); | ||||||
|  |         println!("Max Depth: {}",max_depth); | ||||||
|  |         tree | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn from_id(id: u32) -> Self { | ||||||
|  |         let mut ret=Self::default(); | ||||||
|  |         ret.ids.insert(id); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn insert(&mut self, data: &[String],id: u32, dist: &eddie::str::Levenshtein, depth: usize) -> usize { | ||||||
|  |         if self.is_empty() { | ||||||
|  |             self.ids.insert(id); | ||||||
|  |             return depth; | ||||||
|  |         } | ||||||
|  |         let idx = self.get_id().unwrap() as usize; | ||||||
|  |         let self_key = data.get(idx).unwrap(); | ||||||
|  |         let ins_key = data.get(id as usize).unwrap(); | ||||||
|  |         let dist_key = dist.distance(self_key,ins_key) as u8; | ||||||
|  |         if dist_key==0 { | ||||||
|  |             self.ids.insert(id); | ||||||
|  |             return depth; | ||||||
|  |         } | ||||||
|  |         if let Some(child) = self.children.get_mut(&dist_key) { | ||||||
|  |             return child.insert(data,id,dist,depth+1); | ||||||
|  |         } else { | ||||||
|  |             self.children.insert(dist_key,Self::from_id(id)); | ||||||
|  |             return depth; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_id(&self) -> Option<u32> { | ||||||
|  |         self.ids.iter().copied().next() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn is_empty(&self) -> bool { | ||||||
|  |         return self.ids.is_empty(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct BKTree { | ||||||
|  |     base_id: u32, | ||||||
|  |     root: BKTreeNode, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | impl BKTree { | ||||||
|  |     pub fn new(data: &[String], base_id: u32) -> Self { | ||||||
|  |         let dist = eddie::str::Levenshtein::new(); | ||||||
|  |         let root = BKTreeNode::new(data, &dist); | ||||||
|  |         Self {base_id,root} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn id(&self) -> u32 { | ||||||
|  |         self.base_id | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn dump(&self, fh: &mut BufWriter<File>) -> EdLrrResult<()> { | ||||||
|  |         let options = bincode::DefaultOptions::new(); | ||||||
|  |         let amt = options.serialized_size(self)?; | ||||||
|  |         println!("Writing {}",amt); | ||||||
|  |         options.serialize_into(fh,self)?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn lookup(&self, name: &str) -> u32 { | ||||||
|  |         todo!(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										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::fs::File; | ||||||
| use std::io::{BufRead, BufReader, BufWriter}; | use std::io::{BufRead, BufReader, BufWriter, Seek}; | ||||||
|  | use std::path::Path; | ||||||
| use std::str; | use std::str; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Deserialize, Clone)] | ||||||
| pub struct SystemSerde { | struct GalaxyCoords { | ||||||
|     pub id: u32, |  | ||||||
|     pub star_type: String, |  | ||||||
|     pub system: String, |  | ||||||
|     pub body: String, |  | ||||||
|     pub mult: f32, |  | ||||||
|     pub distance: f32, |  | ||||||
|     pub x: f32, |  | ||||||
|     pub y: f32, |  | ||||||
|     pub z: f32, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn get_mult(star_type: &str) -> f32 { |  | ||||||
|     if star_type.contains("White Dwarf") { |  | ||||||
|         return 1.5; |  | ||||||
|     } |  | ||||||
|     if star_type.contains("Neutron") { |  | ||||||
|         return 4.0; |  | ||||||
|     } |  | ||||||
|     1.0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Deserialize)] |  | ||||||
| struct Coords { |  | ||||||
|     x: f32, |     x: f32, | ||||||
|     y: f32, |     y: f32, | ||||||
|     z: f32, |     z: f32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Deserialize, Clone)] | ||||||
| struct Body { | struct GalaxyBody { | ||||||
|     name: String, |     name: String, | ||||||
|     #[serde(rename = "type")] |     #[serde(rename = "type")] | ||||||
|     body_type: String, |     body_type: String, | ||||||
|  | @ -44,70 +29,73 @@ struct Body { | ||||||
|     distance: f32, |     distance: f32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Deserialize, Clone)] | ||||||
| struct System { | struct GalaxySystem { | ||||||
|     coords: Coords, |     coords: GalaxyCoords, | ||||||
|     name: String, |     name: String, | ||||||
|     bodies: Vec<Body>, |     bodies: Vec<GalaxyBody>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn process_galaxy_dump(path: &str) -> std::io::Result<()> { | /// Load compressed galaxy.json from `path` and write `stars.csv` to `out_path`
 | ||||||
|     let fh = File::create("stars.csv")?; | pub fn process_galaxy_dump(path: &Path, out_path: &Path) -> Result<()> { | ||||||
|     let mut wtr = csv::Writer::from_writer(BufWriter::new(fh)); |     let out_path = out_path.with_extension("csv"); | ||||||
|  |     let mut wtr = csv::WriterBuilder::new() | ||||||
|  |         .has_headers(false) | ||||||
|  |         .from_writer(BufWriter::new(File::create(out_path)?)); | ||||||
|     let mut buffer = String::new(); |     let mut buffer = String::new(); | ||||||
|     let mut bz2_reader = std::process::Command::new("7z") |     let rdr = BufReader::new(File::open(path)?); | ||||||
|         .args(&["x", "-so", path]) |     let mut reader = BufReader::new(GzDecoder::new(rdr)); | ||||||
|         .stdout(std::process::Stdio::piped()) |     let mut count: usize = 0; | ||||||
|         .spawn() |     let mut total: usize = 0; | ||||||
|         .unwrap_or_else(|err| { |     let mut errors: usize = 0; | ||||||
|             eprintln!("Failed to run 7z: {}", err); |     let mut bodies: usize = 0; | ||||||
|             eprintln!("Falling back to bzip2"); |     let mut systems = 0; | ||||||
|             std::process::Command::new("bzip2") |     let max_len = File::metadata(reader.get_ref().get_ref().get_ref())?.len(); | ||||||
|                 .args(&["-d", "-c", path]) |  | ||||||
|                 .stdout(std::process::Stdio::piped()) |  | ||||||
|                 .spawn() |  | ||||||
|                 .expect("Failed to execute bzip2!") |  | ||||||
|         }); |  | ||||||
|     let mut reader = BufReader::new( |  | ||||||
|         bz2_reader |  | ||||||
|             .stdout |  | ||||||
|             .as_mut() |  | ||||||
|             .expect("Failed to open stdout of child process"), |  | ||||||
|     ); |  | ||||||
|     let mut count = 0; |  | ||||||
|     while let Ok(n) = reader.read_line(&mut buffer) { |     while let Ok(n) = reader.read_line(&mut buffer) { | ||||||
|         if n == 0 { |         if n == 0 { | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         buffer = buffer |         buffer = buffer | ||||||
|             .trim() |             .trim_end_matches(|c: char| c == ',' || c.is_whitespace()) | ||||||
|             .trim_end_matches(|c| c == ',') |  | ||||||
|             .trim() |  | ||||||
|             .to_string(); |             .to_string(); | ||||||
|         if !buffer.contains("Star") { |         total += 1; | ||||||
|             continue; |         if let Ok(sys) = serde_json::from_str::<GalaxySystem>(&buffer) { | ||||||
|         }; |             let mut sys_rec = System { | ||||||
|         if let Ok(sys) = serde_json::from_str::<System>(&buffer) { |                 id: systems, | ||||||
|             for b in &sys.bodies { |                 mult: 1.0, | ||||||
|                 if b.body_type == "Star" { |                 name: sys.name, | ||||||
|                     let s = SystemSerde { |                 num_bodies: 0, | ||||||
|                         id: count, |                 pos: [sys.coords.x, sys.coords.y, sys.coords.z], | ||||||
|                         star_type: b.sub_type.clone(), |                 has_scoopable: false, | ||||||
|                         distance: b.distance, |             }; | ||||||
|                         mult: get_mult(&b.sub_type), |             for b in sys.bodies.iter().filter(|b| b.body_type == "Star").cloned() { | ||||||
|                         body: b.name.clone(), |                 sys_rec.mult = sys_rec.mult.max(get_mult(&b.sub_type)); | ||||||
|                         system: sys.name.clone(), |                 sys_rec.num_bodies += 1; | ||||||
|                         x: sys.coords.x, |                 for c in "KGBFOAM".chars() { | ||||||
|                         y: sys.coords.y, |                     if b.sub_type.starts_with(c) { | ||||||
|                         z: sys.coords.z, |                         sys_rec.has_scoopable |= true; | ||||||
|                     }; |                         break; | ||||||
|                     wtr.serialize(s)?; |                     } | ||||||
|                     count += 1; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             bodies += sys_rec.num_bodies as usize; | ||||||
|  |             systems += 1; | ||||||
|  |             count += 1; | ||||||
|  |             wtr.serialize(sys_rec)?; | ||||||
|  |             if count % 100_000 == 0 { | ||||||
|  |                 let cur_pos = reader.get_ref().get_ref().get_ref().stream_position()?; | ||||||
|  |                 let prc: f64 = ((cur_pos as f64) / (max_len as f64)) * 100.0; | ||||||
|  |                 info!("[{:.2} %] {} systems written", prc, count); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             errors += 1; | ||||||
|         } |         } | ||||||
|         buffer.clear(); |         buffer.clear(); | ||||||
|     } |     } | ||||||
|     println!("Total: {}", count); |     info!("Total: {}", total); | ||||||
|  |     info!("Bodies: {}", bodies); | ||||||
|  |     info!("Systems: {}", systems); | ||||||
|  |     info!("Processed: {}", count); | ||||||
|  |     info!("Errors: {}", errors); | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
|  | //! Elite: Dangerous Journal Loadout even parser
 | ||||||
| use crate::common::get_fsd_info; | use crate::common::get_fsd_info; | ||||||
| use crate::ship::Ship; | use crate::ship::Ship; | ||||||
| 
 | 
 | ||||||
|  | use eyre::Result; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  | @ -11,23 +13,23 @@ pub struct Event { | ||||||
|     pub event: EventData, |     pub event: EventData, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[serde(tag = "event")] |  | ||||||
| #[derive(Clone, Debug, PartialEq, Deserialize)] | #[derive(Clone, Debug, PartialEq, Deserialize)] | ||||||
|  | #[serde(tag = "event")] | ||||||
| pub enum EventData { | pub enum EventData { | ||||||
|     Loadout(Loadout), |     Loadout(Loadout), | ||||||
|     #[serde(other)] |     #[serde(other)] | ||||||
|     Unknown, |     Unknown, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[serde(rename_all = "PascalCase")] |  | ||||||
| #[derive(Clone, Debug, PartialEq, Deserialize)] | #[derive(Clone, Debug, PartialEq, Deserialize)] | ||||||
|  | #[serde(rename_all = "PascalCase")] | ||||||
| pub struct Modifier { | pub struct Modifier { | ||||||
|     label: String, |     label: String, | ||||||
|     value: f32, |     value: f32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[serde(rename_all = "PascalCase")] |  | ||||||
| #[derive(Clone, Debug, PartialEq, Deserialize)] | #[derive(Clone, Debug, PartialEq, Deserialize)] | ||||||
|  | #[serde(rename_all = "PascalCase")] | ||||||
| pub struct Engineering { | pub struct Engineering { | ||||||
|     modifiers: Vec<Modifier>, |     modifiers: Vec<Modifier>, | ||||||
| } | } | ||||||
|  | @ -72,55 +74,46 @@ impl Engineering { | ||||||
| 
 | 
 | ||||||
| impl Loadout { | impl Loadout { | ||||||
|     fn get_booster(&self) -> Option<usize> { |     fn get_booster(&self) -> Option<usize> { | ||||||
|         self.modules |         self.modules.iter().cloned().find_map(|m| { | ||||||
|             .iter() |             let Module { item, .. } = m; | ||||||
|             .cloned() |             if item.starts_with("int_guardianfsdbooster") { | ||||||
|             .filter_map(|m| { |                 return item | ||||||
|                 let Module { item, .. } = m; |                     .chars() | ||||||
|                 if item.starts_with("int_guardianfsdbooster") { |                     .last() | ||||||
|                     return item |                     .unwrap() | ||||||
|                         .chars() |                     .to_digit(10) | ||||||
|                         .last() |                     .map(|v| v as usize); | ||||||
|                         .unwrap() |             } | ||||||
|                         .to_digit(10) |             return None; | ||||||
|                         .map(|v| v as usize); |         }) | ||||||
|                 } |  | ||||||
|                 return None; |  | ||||||
|             }) |  | ||||||
|             .next() |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_fsd(&self) -> Option<(String, Option<Engineering>)> { |     fn get_fsd(&self) -> Option<(String, Option<Engineering>)> { | ||||||
|         self.modules |         self.modules.iter().cloned().find_map(|m| { | ||||||
|             .iter() |             let Module { | ||||||
|             .cloned() |                 slot, | ||||||
|             .filter_map(|m| { |                 engineering, | ||||||
|                 let Module { |                 item, | ||||||
|                     slot, |             } = m; | ||||||
|                     engineering, |             if slot == "FrameShiftDrive" { | ||||||
|                     item, |                 return Some((item, engineering)); | ||||||
|                 } = m; |             } | ||||||
|                 if slot == "FrameShiftDrive" { |             return None; | ||||||
|                     return Some((item, engineering)); |         }) | ||||||
|                 } |  | ||||||
|                 return None; |  | ||||||
|             }) |  | ||||||
|             .next() |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn try_into_ship(self) -> Result<Ship, String> { |     pub fn try_into_ship(self) -> Result<(String, Ship), String> { | ||||||
|         let fsd = self.get_fsd().ok_or("No FSD found!")?; |         let fsd = self.get_fsd().ok_or("No FSD found!")?; | ||||||
|         let booster = self.get_booster().unwrap_or(0); |         let booster = self.get_booster().unwrap_or(0); | ||||||
|         let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$") |         let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$") | ||||||
|             .unwrap() |             .unwrap() | ||||||
|             .captures(&fsd.0); |             .captures(&fsd.0); | ||||||
|         let fsd_type: (usize, usize) = fsd_type |         let fsd_type: (usize, usize) = fsd_type | ||||||
|             .map(|m| { |             .and_then(|m| { | ||||||
|                 let s = m.get(1)?.as_str().to_owned().parse().ok()?; |                 let s = m.get(1)?.as_str().to_owned().parse().ok()?; | ||||||
|                 let c = m.get(2)?.as_str().to_owned().parse().ok()?; |                 let c = m.get(2)?.as_str().to_owned().parse().ok()?; | ||||||
|                 return Some((c, s)); |                 return Some((c, s)); | ||||||
|             }) |             }) | ||||||
|             .flatten() |  | ||||||
|             .ok_or(format!("Invalid FSD found: {}", &fsd.0))?; |             .ok_or(format!("Invalid FSD found: {}", &fsd.0))?; | ||||||
|         let eng = fsd |         let eng = fsd | ||||||
|             .1 |             .1 | ||||||
|  | @ -141,18 +134,28 @@ impl Loadout { | ||||||
|         let opt_mass = fsd_info |         let opt_mass = fsd_info | ||||||
|             .get("FSDOptimalMass") |             .get("FSDOptimalMass") | ||||||
|             .ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?; |             .ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?; | ||||||
|         return Ship::new( |         let key = format!( | ||||||
|             self.ship_name, |             "[{}] {} ({})", | ||||||
|  |             if !self.ship_name.is_empty() { | ||||||
|  |                 self.ship_name | ||||||
|  |             } else { | ||||||
|  |                 "<NO NAME>".to_owned() | ||||||
|  |             }, | ||||||
|             self.ship_ident, |             self.ship_ident, | ||||||
|             self.ship, |             self.ship | ||||||
|             self.unladen_mass, |  | ||||||
|             self.fuel_capacity.main, |  | ||||||
|             self.fuel_capacity.main, |  | ||||||
|             fsd_type, |  | ||||||
|             *max_fuel, |  | ||||||
|             *opt_mass, |  | ||||||
|             booster, |  | ||||||
|         ); |         ); | ||||||
|  |         return Ok(( | ||||||
|  |             key, | ||||||
|  |             Ship::new( | ||||||
|  |                 self.unladen_mass, | ||||||
|  |                 self.fuel_capacity.main, | ||||||
|  |                 self.fuel_capacity.main, | ||||||
|  |                 fsd_type, | ||||||
|  |                 *max_fuel, | ||||||
|  |                 *opt_mass, | ||||||
|  |                 booster, | ||||||
|  |             )?, | ||||||
|  |         )); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										828
									
								
								rust/src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										828
									
								
								rust/src/lib.rs
									
										
									
									
									
								
							|  | @ -1,144 +1,379 @@ | ||||||
| // #![deny(warnings)]
 |  | ||||||
| #![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)] | #![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)] | ||||||
|  | //! # Elite: Danerous Long Range Router
 | ||||||
| pub mod common; | pub mod common; | ||||||
| pub mod edsm; |  | ||||||
| pub mod galaxy; | pub mod galaxy; | ||||||
| pub mod journal; | pub mod journal; | ||||||
|  | pub mod mmap_csv; | ||||||
|  | #[cfg(feature = "profiling")] | ||||||
|  | pub mod profiling; | ||||||
| pub mod route; | pub mod route; | ||||||
|  | pub mod search_algos; | ||||||
| pub mod ship; | pub mod ship; | ||||||
| 
 | 
 | ||||||
|  | use bincode::Options; | ||||||
|  | use csv::{Position, StringRecord}; | ||||||
|  | use eddie::Levenshtein; | ||||||
|  | // =========================
 | ||||||
|  | use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM}; | ||||||
|  | use std::alloc::System as SystemAlloc; | ||||||
|  | use std::cell::RefMut; | ||||||
|  | use std::collections::BTreeMap; | ||||||
|  | use std::io::{BufWriter, Write}; | ||||||
|  | use std::path::Path; | ||||||
|  | use std::time::Instant; | ||||||
|  | #[cfg(not(feature = "profiling"))] | ||||||
|  | #[global_allocator] | ||||||
|  | static GLOBAL: &StatsAlloc<SystemAlloc> = &INSTRUMENTED_SYSTEM; | ||||||
|  | // =========================
 | ||||||
|  | #[cfg(not(feature = "profiling"))] | ||||||
|  | mod profiling { | ||||||
|  |     pub fn init() {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| extern crate derivative; | extern crate derivative; | ||||||
| use crate::common::{find_matches, SysEntry}; | use crate::common::{find_matches, grid_stats, EdLrrError, SysEntry, System}; | ||||||
|  | #[cfg(feature = "profiling")] | ||||||
|  | use crate::profiling::*; | ||||||
| use crate::route::{Router, SearchState}; | use crate::route::{Router, SearchState}; | ||||||
| use crate::ship::Ship; | use crate::ship::Ship; | ||||||
|  | use eyre::Result; | ||||||
|  | #[cfg(not(feature = "profiling"))] | ||||||
|  | use log::*; | ||||||
| use pyo3::exceptions::*; | use pyo3::exceptions::*; | ||||||
| use pyo3::prelude::*; | use pyo3::prelude::*; | ||||||
| use pyo3::types::{PyDict, PyList, PyTuple}; | use pyo3::types::{IntoPyDict, PyDict, PyTuple}; | ||||||
| use pyo3::PyObjectProtocol; | use pyo3::{create_exception, PyObjectProtocol}; | ||||||
| use std::path::PathBuf; | use route::{LineCache, PyModeConfig}; | ||||||
|  | use std::{ | ||||||
|  |     cell::RefCell, collections::HashMap, convert::TryInto, fs::File, io::BufReader, path::PathBuf, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|  | #[cfg(feature = "profiling")] | ||||||
|  | #[global_allocator] | ||||||
|  | static GLOBAL: ProfiledAllocator<std::alloc::System> = | ||||||
|  |     ProfiledAllocator::new(std::alloc::System, 1024); | ||||||
|  | 
 | ||||||
|  | create_exception!(_ed_lrr, RoutingError, PyException); | ||||||
|  | create_exception!(_ed_lrr, ProcessingError, PyException); | ||||||
|  | create_exception!(_ed_lrr, ResolveError, PyException); | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | enum RangeOrShip { | ||||||
|  |     Range(f32), | ||||||
|  |     Ship(Ship), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FromPyObject<'_> for RangeOrShip { | ||||||
|  |     fn extract(ob: &PyAny) -> PyResult<Self> { | ||||||
|  |         if let Ok(n) = ob.extract() { | ||||||
|  |             return Ok(Self::Range(n)); | ||||||
|  |         } | ||||||
|  |         let s: PyShip = ob.extract()?; | ||||||
|  |         return Ok(Self::Ship(s.ship)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| #[pyclass(dict)] | #[pyclass(dict)] | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| #[text_signature = "(callback, /)"] | #[pyo3(text_signature = "(callback, /)")] | ||||||
| struct PyRouter { | struct PyRouter { | ||||||
|     router: Router, |     router: Router, | ||||||
|     primary_only: bool, |     stars_path: Option<String>, | ||||||
|     stars_path: String, | } | ||||||
|  | 
 | ||||||
|  | impl PyRouter { | ||||||
|  |     fn check_stars(&self) -> PyResult<PathBuf> { | ||||||
|  |         self.stars_path | ||||||
|  |             .as_ref() | ||||||
|  |             .ok_or_else(|| PyErr::from(EdLrrError::RuntimeError("no stars.csv loaded".to_owned()))) | ||||||
|  |             .map(PathBuf::from) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[pymethods] | #[pymethods] | ||||||
| impl PyRouter { | impl PyRouter { | ||||||
|     #[new] |     #[new] | ||||||
|     #[args(callback = "None")] |     #[args(callback = "None")] | ||||||
|     fn new(callback: Option<PyObject>, py: Python<'static>) -> PyResult<Self> { |     fn new(callback: Option<PyObject>) -> Self { | ||||||
|         Ok(PyRouter { |         let mut router = Router::new(); | ||||||
|             router: Router::new(Box::new( |         if callback.is_some() { | ||||||
|                 move |state: &SearchState| { |             router.set_callback(Box::new(move |state: &SearchState| { | ||||||
|                     match callback.as_ref() { |                 let gil_guard = Python::acquire_gil(); | ||||||
|                         Some(cb) => cb.call(py, (state.clone(),), None), |                 let py = gil_guard.python(); | ||||||
|                         None => Ok(py.None()), |                 match callback.as_ref() { | ||||||
|                     } |                     Some(cb) => cb.call(py, (state.clone(),), None), | ||||||
|  |                     None => Ok(py.None()), | ||||||
|                 } |                 } | ||||||
|             )), |             })) | ||||||
|             primary_only: false, |         } | ||||||
|             stars_path: String::from(""), |         PyRouter { | ||||||
|         }) |             router, | ||||||
|  |             stars_path: None, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(ship, /)"] |     #[args(primary_only = "false", immediate = "false")] | ||||||
|     fn set_ship(&mut self, py: Python, ship: &PyShip) -> PyResult<PyObject> { |     #[pyo3(text_signature = "(path, primary_only, /)")] | ||||||
|         self.router.set_ship(ship.ship.clone()); |     fn load(&mut self, path: String, py: Python, immediate: bool) -> PyResult<PyObject> { | ||||||
|  |         self.stars_path = Some(path); | ||||||
|  |         if immediate { | ||||||
|  |             let stars_path = self.check_stars()?; | ||||||
|  |             let route_res = self.router.load(&stars_path); | ||||||
|  |             if let Err(err_msg) = route_res { | ||||||
|  |                 return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|         Ok(py.None()) |         Ok(py.None()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[args(primary_only = "false")] |     #[pyo3(text_signature = "(/)")] | ||||||
|     #[text_signature = "(path, primary_only, /)"] |     fn unload(&mut self, py: Python) -> PyObject { | ||||||
|     fn load( |         self.router.unload(); | ||||||
|         &mut self, |         py.None() | ||||||
|         path: String, |     } | ||||||
|         primary_only: bool, | 
 | ||||||
|         py: Python, |     fn plot(&mut self, py: Python) -> PyResult<PyObject> { | ||||||
|     ) -> PyResult<PyObject> { |         let stars_path = self.check_stars()?; | ||||||
|         self.stars_path = path; |         let route_res = self.router.load(&stars_path); | ||||||
|         self.primary_only = primary_only; |         if let Err(err_msg) = route_res { | ||||||
|  |             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|  |         }; | ||||||
|  |         let mut max_v = [0f32, 0f32, 0f32]; | ||||||
|  |         let mut min_v = [0f32, 0f32, 0f32]; | ||||||
|  |         for node in self.router.get_tree().iter() { | ||||||
|  |             for i in 0..3 { | ||||||
|  |                 if node.pos[i] > max_v[i] { | ||||||
|  |                     max_v[i] = node.pos[i]; | ||||||
|  |                 } | ||||||
|  |                 if node.pos[i] < min_v[i] { | ||||||
|  |                     min_v[i] = node.pos[i]; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         let plot_bbox: ((f32, f32), (f32, f32)) = ((min_v[0], max_v[0]), (min_v[2], max_v[2])); | ||||||
|  |         Ok(plot_bbox.to_object(py)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn run_bfs(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||||
|  |         let stars_path = self.check_stars()?; | ||||||
|  |         let route_res = self.router.load(&stars_path); | ||||||
|  |         if let Err(err_msg) = route_res { | ||||||
|  |             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|  |         }; | ||||||
|  |         self.router | ||||||
|  |             .precomp_bfs(range) | ||||||
|  |             .map_err(PyErr::new::<RoutingError, _>) | ||||||
|  |             .map(|_| py.None()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn precompute_graph(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||||
|  |         let stars_path = self.check_stars()?; | ||||||
|  |         let route_res = self.router.load(&stars_path); | ||||||
|  |         if let Err(err_msg) = route_res { | ||||||
|  |             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|  |         }; | ||||||
|  |         self.router | ||||||
|  |             .precompute_graph(range) | ||||||
|  |             .map_err(PyErr::new::<RoutingError, _>) | ||||||
|  |             .map(|_| py.None()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn nb_perf_test(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||||
|  |         let stars_path = self.check_stars()?; | ||||||
|  |         let route_res = self.router.load(&stars_path); | ||||||
|  |         if let Err(err_msg) = route_res { | ||||||
|  |             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|  |         }; | ||||||
|  |         let mut nbmap = BTreeMap::new(); | ||||||
|  |         let tree = self.router.get_tree(); | ||||||
|  |         let total_nodes = tree.size(); | ||||||
|  |         for (n, node) in tree.iter().enumerate() { | ||||||
|  |             let nbs = self | ||||||
|  |                 .router | ||||||
|  |                 .neighbours(node, range) | ||||||
|  |                 .map(|nb| nb.id) | ||||||
|  |                 .collect::<Vec<_>>(); | ||||||
|  |             nbmap.insert(node.id, nbs); | ||||||
|  |             if n % 100_000 == 0 { | ||||||
|  |                 println!("{}/{}", n, total_nodes); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         println!("{}", nbmap.len()); | ||||||
|         Ok(py.None()) |         Ok(py.None()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[args(greedyness = "0.5", num_workers = "0", beam_width = "0")] |     fn precompute_neighbors(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||||
|     #[text_signature = "(hops, range, greedyness, beam_width, num_workers, /)"] |         let stars_path = self.check_stars()?; | ||||||
|  |         let route_res = self.router.load(&stars_path); | ||||||
|  |         if let Err(err_msg) = route_res { | ||||||
|  |             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|  |         }; | ||||||
|  |         self.router | ||||||
|  |             .precompute_all(range) | ||||||
|  |             .map_err(PyErr::new::<RoutingError, _>) | ||||||
|  |             .map(|_| py.None()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[args(
 | ||||||
|  |         greedyness = "0.5", | ||||||
|  |         max_dist = "0.0", | ||||||
|  |         num_workers = "0", | ||||||
|  |         beam_width = "BeamWidth::Absolute(0)" | ||||||
|  |     )] | ||||||
|  |     #[pyo3(text_signature = "(hops, range, mode, num_workers, /)")] | ||||||
|     fn route( |     fn route( | ||||||
|         &mut self, |         &mut self, | ||||||
|         hops: &PyList, |         hops: Vec<SysEntry>, | ||||||
|         range: Option<f32>, |         range: RangeOrShip, | ||||||
|         greedyness: f32, |         mode: Option<PyModeConfig>, | ||||||
|         beam_width: usize, |  | ||||||
|         num_workers: usize, |         num_workers: usize, | ||||||
|         py: Python, |     ) -> PyResult<Vec<common::System>> { | ||||||
|     ) -> PyResult<PyObject> { |         let stars_path = self.check_stars()?; | ||||||
|         let route_res = self |         let route_res = self.router.load(&stars_path); | ||||||
|             .router |  | ||||||
|             .load(&PathBuf::from(self.stars_path.clone()), self.primary_only); |  | ||||||
|         if let Err(err_msg) = route_res { |         if let Err(err_msg) = route_res { | ||||||
|             return Err(PyErr::new::<ValueError, _>(err_msg)); |             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|         }; |         }; | ||||||
|         let mut sys_entries: Vec<SysEntry> = Vec::new(); |         info!("Resolving systems..."); | ||||||
|         for hop in hops { |         let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) { | ||||||
|             if let Ok(id) = hop.extract() { |             Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(), | ||||||
|                 sys_entries.push(SysEntry::ID(id)); |  | ||||||
|             } else { |  | ||||||
|                 sys_entries.push(SysEntry::parse(hop.extract()?)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         println!("Resolving systems..."); |  | ||||||
|         let ids: Vec<u32> = match resolve(&sys_entries, &self.router.path) { |  | ||||||
|             Ok(ids) => ids, |  | ||||||
|             Err(err_msg) => { |             Err(err_msg) => { | ||||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); |                 return Err(EdLrrError::ResolveError(err_msg).into()); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         match self |         let mut is_default = false; | ||||||
|             .router |         let mut is_ship = false; | ||||||
|             .compute_route(&ids, range, greedyness, beam_width, num_workers) |         info!("{:?}", mode); | ||||||
|         { |         let mut mode = match mode { | ||||||
|             Ok(route) => { |             Some(mode) => mode, | ||||||
|                 let py_route: Vec<_> = route.iter().map(|hop| hop.to_object(py)).collect(); |             None => { | ||||||
|                 Ok(py_route.to_object(py)) |                 let mode = PyModeConfig::default(); | ||||||
|  |                 is_default = true; | ||||||
|  |                 mode | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         if mode.mode.is_empty() { | ||||||
|  |             if mode.ship.is_none() { | ||||||
|  |                 mode.mode = "bfs".to_string(); | ||||||
|  |             } else { | ||||||
|  |                 mode.mode = "ship".to_string(); | ||||||
|  |                 if mode.ship_mode == *"" { | ||||||
|  |                     mode.ship_mode = "jumps".to_string(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             Err(err_msg) => Err(PyErr::new::<RuntimeError, _>(err_msg)), |  | ||||||
|         } |         } | ||||||
|  |         let range = match range { | ||||||
|  |             RangeOrShip::Range(r) => Some(r), | ||||||
|  |             RangeOrShip::Ship(ship) => { | ||||||
|  |                 mode.mode = "ship".into(); | ||||||
|  |                 mode.ship = Some(ship); | ||||||
|  |                 is_ship = true; | ||||||
|  |                 None | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         info!("{:?}", mode); | ||||||
|  |         let mode = mode.try_into()?; | ||||||
|  |         if is_default && !is_ship { | ||||||
|  |             warn!("no mode specified, defaulting to {}", mode); | ||||||
|  |         } | ||||||
|  |         #[cfg(not(feature = "profiling"))] | ||||||
|  |         let reg = Region::new(GLOBAL); | ||||||
|  |         let res = match self.router.compute_route(&ids, range, mode, num_workers) { | ||||||
|  |             Ok(route) => Ok(route), | ||||||
|  |             Err(err_msg) => Err(PyErr::new::<RoutingError, _>(err_msg)), | ||||||
|  |         }; | ||||||
|  |         #[cfg(not(feature = "profiling"))] | ||||||
|  |         println!("{:?}", reg.change()); | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn perf_test(&self, callback: PyObject, py: Python) -> PyResult<PyObject> { | ||||||
|  |         use common::TreeNode; | ||||||
|  |         let node = TreeNode { | ||||||
|  |             pos: [-65.21875, 7.75, -111.03125], | ||||||
|  |             flags: 1, | ||||||
|  |             id: 0, | ||||||
|  |         }; | ||||||
|  |         let goal = TreeNode { | ||||||
|  |             pos: [-9530.5, -910.28125, 19808.125], | ||||||
|  |             flags: 1, | ||||||
|  |             id: 1, | ||||||
|  |         }; | ||||||
|  |         let kwargs = vec![("goal", goal), ("node", node)].into_py_dict(py); | ||||||
|  |         let mut n: usize = 0; | ||||||
|  |         let mut d: f64 = 0.0; | ||||||
|  |         let num_loops = 10_000_000; | ||||||
|  |         loop { | ||||||
|  |             let pool = unsafe { Python::new_pool(py) }; | ||||||
|  |             let t_start = std::time::Instant::now(); | ||||||
|  |             for _ in 0..num_loops { | ||||||
|  |                 let val: f64 = callback.call(py, (), Some(kwargs))?.extract(py)?; | ||||||
|  |             } | ||||||
|  |             d += t_start.elapsed().as_secs_f64(); | ||||||
|  |             drop(pool); | ||||||
|  |             n += num_loops; | ||||||
|  |             let dt = std::time::Duration::from_secs_f64(d / (n as f64)); | ||||||
|  |             println!("{}: {:?}", n, dt); | ||||||
|  |         } | ||||||
|  |         Ok(py.None()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[args(grid_size = "1.0")] | ||||||
|  |     #[pyo3(text_signature = "(grid_size)")] | ||||||
|  |     fn get_grid(&self, grid_size: f32, py: Python) -> PyResult<PyObject> { | ||||||
|  |         let stars_path = self.check_stars()?; | ||||||
|  |         grid_stats(&stars_path, grid_size) | ||||||
|  |             .map(|ret| ret.to_object(py)) | ||||||
|  |             .map_err(PyErr::new::<PyRuntimeError, _>) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[args(hops = "*")] |     #[args(hops = "*")] | ||||||
|     #[text_signature = "(sys_1, sys_2, ..., /)"] |     #[pyo3(text_signature = "(sys_1, sys_2, ..., /)")] | ||||||
|     fn resolve_systems(&self, hops: &PyTuple, py: Python) -> PyResult<PyObject> { |     fn resolve(&self, hops: Vec<SysEntry>, py: Python) -> PyResult<PyObject> { | ||||||
|         let mut sys_entries: Vec<SysEntry> = Vec::new(); |         info!("Resolving systems..."); | ||||||
|         for hop in hops { |         let stars_path = self.check_stars()?; | ||||||
|             if let Ok(id) = hop.extract() { |         let systems: Vec<System> = match resolve(&hops, &stars_path, false) { | ||||||
|                 sys_entries.push(SysEntry::ID(id)); |             Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(), | ||||||
|             } else { |  | ||||||
|                 sys_entries.push(SysEntry::parse(hop.extract()?)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         println!("Resolving systems..."); |  | ||||||
|         let stars_path = PathBuf::from(self.stars_path.clone()); |  | ||||||
|         let ids: Vec<u32> = match resolve(&sys_entries, &stars_path) { |  | ||||||
|             Ok(ids) => ids, |  | ||||||
|             Err(err_msg) => { |             Err(err_msg) => { | ||||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); |                 return Err(EdLrrError::ResolveError(err_msg).into()); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         let ret: Vec<(_, u32)> = hops.into_iter().zip(ids.into_iter()).collect(); |         let ret: Vec<(_, System)> = hops | ||||||
|  |             .into_iter() | ||||||
|  |             .zip(systems.iter()) | ||||||
|  |             .map(|(id, sys)| (id, sys.clone())) | ||||||
|  |             .collect(); | ||||||
|         Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py)) |         Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[staticmethod] |     fn str_tree_test(&self) -> common::EdLrrResult<()> { | ||||||
|     fn preprocess_edsm() -> PyResult<()> { |         use common::BKTree; | ||||||
|         todo!("Implement EDSM Preprocessor") |         const CHUNK_SIZE: usize = 4_000_000; | ||||||
|     } |         let path = self.check_stars()?; | ||||||
| 
 |         let reader: csv::Reader<File> = csv::ReaderBuilder::new() | ||||||
|     #[staticmethod] |             .has_headers(false) | ||||||
|     fn preprocess_galaxy() -> PyResult<()> { |             .from_path(path) | ||||||
|         todo!("Implement galaxy.json Preprocessor") |             .map_err(EdLrrError::from)?; | ||||||
|  |         let mut data: Vec<String> = Vec::with_capacity(CHUNK_SIZE); | ||||||
|  |         let t_start = Instant::now(); | ||||||
|  |         let mut base_id=0; | ||||||
|  |         let mut wr = BufWriter::new(File::create("test.bktree")?); | ||||||
|  |         for sys in reader.into_deserialize::<System>() { | ||||||
|  |             let sys = sys?; | ||||||
|  |             data.push(sys.name); | ||||||
|  |             if data.len()>CHUNK_SIZE { | ||||||
|  |                 let tree = BKTree::new(&data, base_id); | ||||||
|  |                 tree.dump(&mut wr)?; | ||||||
|  |                 base_id=sys.id; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if !data.is_empty() { | ||||||
|  |             let tree = BKTree::new(&data, base_id); | ||||||
|  |             tree.dump(&mut wr)?; | ||||||
|  |         } | ||||||
|  |         wr.flush()?; | ||||||
|  |         println!("Took: {:?}", t_start.elapsed()); | ||||||
|  |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -153,52 +388,91 @@ impl PyObjectProtocol for PyRouter { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn resolve(entries: &[SysEntry], path: &PathBuf) -> Result<Vec<u32>, String> { | enum ResolveResult { | ||||||
|  |     System(System), | ||||||
|  |     ID(u32), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ResolveResult { | ||||||
|  |     fn into_id(self) -> u32 { | ||||||
|  |         match self { | ||||||
|  |             Self::System(sys) => sys.id, | ||||||
|  |             Self::ID(id) => id, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn into_system(self) -> System { | ||||||
|  |         if let Self::System(sys) = self { | ||||||
|  |             return sys; | ||||||
|  |         } | ||||||
|  |         panic!("Tried to unwrap ID into System"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<ResolveResult>, String> { | ||||||
|     let mut names: Vec<String> = Vec::new(); |     let mut names: Vec<String> = Vec::new(); | ||||||
|     let mut ids: Vec<u32> = Vec::new(); |  | ||||||
|     let mut ret: Vec<u32> = Vec::new(); |     let mut ret: Vec<u32> = Vec::new(); | ||||||
|  |     let mut needs_rtree = false; | ||||||
|     for ent in entries { |     for ent in entries { | ||||||
|         match ent { |         match ent { | ||||||
|             SysEntry::Name(name) => names.push(name.to_owned()), |             SysEntry::Name(name) => names.push(name.to_owned()), | ||||||
|             SysEntry::ID(id) => ids.push(*id), |             SysEntry::Pos(_) => { | ||||||
|  |                 needs_rtree |= true; | ||||||
|  |             } | ||||||
|  |             _ => (), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if !path.exists() { |     if !path.exists() { | ||||||
|         return Err(format!( |         return Err(format!("Source file {:?} does not exist!", path.display())); | ||||||
|             "Source file \"{:?}\" does not exist!", |  | ||||||
|             path.display() |  | ||||||
|         )); |  | ||||||
|     } |     } | ||||||
| 
 |     let name_ids = if !names.is_empty() { | ||||||
|     let name_ids = find_matches(path, names, false)?; |         mmap_csv::mmap_csv(path, names)? | ||||||
|  |     } else { | ||||||
|  |         HashMap::new() | ||||||
|  |     }; | ||||||
|  |     let tmp_r = needs_rtree | ||||||
|  |         .then(|| { | ||||||
|  |             let mut r = Router::new(); | ||||||
|  |             r.load(path).map(|_| r) | ||||||
|  |         }) | ||||||
|  |         .transpose()?; | ||||||
|     for ent in entries { |     for ent in entries { | ||||||
|         match ent { |         match ent { | ||||||
|             SysEntry::Name(name) => { |             SysEntry::Name(name) => { | ||||||
|                 let ent_res = name_ids |                 let ent_res = name_ids | ||||||
|                     .get(&name.to_owned()) |                     .get(name) | ||||||
|                     .ok_or(format!("System {} not found", name))?; |                     .ok_or(format!("System {} not found", name))?; | ||||||
|                 let sys = ent_res |                 let sys = ent_res | ||||||
|                     .1 |  | ||||||
|                     .as_ref() |                     .as_ref() | ||||||
|                     .ok_or(format!("System {} not found", name))?; |                     .ok_or(format!("System {} not found", name))?; | ||||||
|                 if ent_res.0 < 0.75 { |                 ret.push(*sys); | ||||||
|                     println!( |  | ||||||
|                         "WARNING: {} match to {} with low confidence ({:.2}%)", |  | ||||||
|                         name, |  | ||||||
|                         sys.system, |  | ||||||
|                         ent_res.0 * 100.0 |  | ||||||
|                     ); |  | ||||||
|                 } |  | ||||||
|                 ret.push(sys.id); |  | ||||||
|             } |             } | ||||||
|             SysEntry::ID(id) => ret.push(*id), |             SysEntry::ID(id) => ret.push(*id), | ||||||
|  |             SysEntry::Pos((x, y, z)) => ret.push( | ||||||
|  |                 tmp_r | ||||||
|  |                     .as_ref() | ||||||
|  |                     .unwrap() | ||||||
|  |                     .closest(&[*x, *y, *z]) | ||||||
|  |                     .ok_or("No systems loaded!")? | ||||||
|  |                     .id, | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     Ok(ret) |     if id_only { | ||||||
|  |         return Ok(ret.iter().map(|id| ResolveResult::ID(*id)).collect()); | ||||||
|  |     } else { | ||||||
|  |         let mut lc = route::LineCache::create(path)?; | ||||||
|  |         let mut systems = vec![]; | ||||||
|  |         for id in ret { | ||||||
|  |             let sys = ResolveResult::System(lc.get(id)?.unwrap()); | ||||||
|  |             systems.push(sys) | ||||||
|  |         } | ||||||
|  |         return Ok(systems); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[pyclass(dict)] | #[pyclass(dict)] | ||||||
| #[derive(Debug)] | #[derive(Debug, Clone)] | ||||||
| struct PyShip { | struct PyShip { | ||||||
|     ship: Ship, |     ship: Ship, | ||||||
| } | } | ||||||
|  | @ -219,8 +493,8 @@ impl PyShip { | ||||||
|     #[staticmethod] |     #[staticmethod] | ||||||
|     fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> { |     fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> { | ||||||
|         match Ship::new_from_json(loadout) { |         match Ship::new_from_json(loadout) { | ||||||
|             Ok(ship) => Ok((PyShip { ship }).into_py(py)), |             Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)), | ||||||
|             Err(err_msg) => Err(PyErr::new::<ValueError, _>(err_msg)), |             Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     #[staticmethod] |     #[staticmethod] | ||||||
|  | @ -228,15 +502,15 @@ impl PyShip { | ||||||
|         let mut ship = match Ship::new_from_journal() { |         let mut ship = match Ship::new_from_journal() { | ||||||
|             Ok(ship) => ship, |             Ok(ship) => ship, | ||||||
|             Err(err_msg) => { |             Err(err_msg) => { | ||||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); |                 return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         let ships: Vec<(PyObject, PyObject)> = ship |         let ships: Vec<(PyObject, PyObject)> = ship | ||||||
|             .drain() |             .drain() | ||||||
|             .map(|(k, v)| { |             .map(|(ship_name, ship)| { | ||||||
|                 let k_py = k.to_object(py); |                 let ship_name_py = ship_name.to_object(py); | ||||||
|                 let v_py = (PyShip { ship: v }).into_py(py); |                 let ship_py = (PyShip { ship }).into_py(py); | ||||||
|                 (k_py, v_py) |                 (ship_name_py, ship_py) | ||||||
|             }) |             }) | ||||||
|             .collect(); |             .collect(); | ||||||
|         Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) |         Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) | ||||||
|  | @ -246,239 +520,141 @@ impl PyShip { | ||||||
|         self.ship.to_object(py) |         self.ship.to_object(py) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(dist, /)"] |     #[pyo3(text_signature = "(dist, /)")] | ||||||
|     fn fuel_cost(&self, _py: Python, dist: f32) -> PyResult<f32> { |     fn fuel_cost(&self, _py: Python, dist: f32) -> f32 { | ||||||
|         Ok(self.ship.fuel_cost(dist)) |         self.ship.fuel_cost(dist) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(/)"] |     #[getter] | ||||||
|     fn range(&self, _py: Python) -> PyResult<f32> { |     fn range(&self, _py: Python) -> f32 { | ||||||
|         Ok(self.ship.range()) |         self.ship.range() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(/)"] |     #[getter] | ||||||
|     fn max_range(&self, _py: Python) -> PyResult<f32> { |     fn max_range(&self, _py: Python) -> f32 { | ||||||
|         Ok(self.ship.max_range()) |         self.ship.max_range() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(dist, /)"] |     #[pyo3(text_signature = "(dist, /)")] | ||||||
|     fn make_jump(&mut self, dist: f32, _py: Python) -> PyResult<Option<f32>> { |     fn make_jump(&mut self, dist: f32, _py: Python) -> Option<f32> { | ||||||
|         Ok(self.ship.make_jump(dist)) |         self.ship.make_jump(dist) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(dist, /)"] |     #[pyo3(text_signature = "(dist, /)")] | ||||||
|     fn can_jump(&self, dist: f32, _py: Python) -> PyResult<bool> { |     fn can_jump(&self, dist: f32, _py: Python) -> bool { | ||||||
|         Ok(self.ship.can_jump(dist)) |         self.ship.can_jump(dist) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[args(fuel_amount = "None")] |     #[args(fuel_amount = "None")] | ||||||
|     #[text_signature = "(fuel_amount, /)"] |     #[pyo3(text_signature = "(fuel_amount, /)")] | ||||||
|     fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) -> PyResult<()> { |     fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) { | ||||||
|         if let Some(fuel) = fuel_amount { |         if let Some(fuel) = fuel_amount { | ||||||
|             self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity) |             self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity) | ||||||
|         } else { |         } else { | ||||||
|             self.ship.fuel_mass = self.ship.fuel_capacity; |             self.ship.fuel_mass = self.ship.fuel_capacity; | ||||||
|         } |         } | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[text_signature = "(factor, /)"] |     #[pyo3(text_signature = "(factor, /)")] | ||||||
|     fn boost(&mut self, factor: f32, _py: Python) -> PyResult<()> { |     fn boost(&mut self, factor: f32, _py: Python) { | ||||||
|         self.ship.boost(factor); |         self.ship.boost(factor); | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl PyShip { | ||||||
|  |     fn get_ship(&self) -> Ship { | ||||||
|  |         self.ship.clone() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[pyfunction] | ||||||
|  | fn preprocess_edsm( | ||||||
|  |     _bodies_path: &str, | ||||||
|  |     _systems_path: &str, | ||||||
|  |     _out_path: &str, | ||||||
|  |     _py: Python, | ||||||
|  | ) -> PyResult<()> { | ||||||
|  |     Err(pyo3::exceptions::PyNotImplementedError::new_err( | ||||||
|  |         "please use Spansh's Galaxy dump and preprocess_galaxy()", | ||||||
|  |     )) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn to_py_value(value: eval::Value, py: Python) -> PyResult<PyObject> { | ||||||
|  |     type Value = eval::Value; | ||||||
|  |     match value { | ||||||
|  |         Value::String(s) => Ok(s.to_object(py)), | ||||||
|  |         Value::Number(n) => { | ||||||
|  |             if let Some(n) = n.as_u64() { | ||||||
|  |                 return Ok(n.to_object(py)); | ||||||
|  |             } | ||||||
|  |             if let Some(n) = n.as_i64() { | ||||||
|  |                 return Ok(n.to_object(py)); | ||||||
|  |             } | ||||||
|  |             return Ok(n.as_f64().unwrap().to_object(py)); | ||||||
|  |         } | ||||||
|  |         Value::Bool(b) => Ok(b.to_object(py)), | ||||||
|  |         Value::Array(mut t) => { | ||||||
|  |             let mut res: Vec<PyObject> = vec![]; | ||||||
|  |             for v in t.drain(..) { | ||||||
|  |                 res.push(to_py_value(v, py)?); | ||||||
|  |             } | ||||||
|  |             Ok(PyTuple::new(py, &res).to_object(py)) | ||||||
|  |         } | ||||||
|  |         Value::Object(o) => { | ||||||
|  |             let res = PyDict::new(py); | ||||||
|  |             for (k, v) in o.iter() { | ||||||
|  |                 res.set_item(k, to_py_value(v.clone(), py)?)?; | ||||||
|  |             } | ||||||
|  |             Ok(res.to_object(py)) | ||||||
|  |         } | ||||||
|  |         Value::Null => Ok(py.None()), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn to_py(res: Result<eval::Value, eval::Error>, py: Python) -> PyResult<PyObject> { | ||||||
|  |     res.map_err(|e| PyErr::from(EdLrrError::EvalError(e))) | ||||||
|  |         .and_then(|r| to_py_value(r, py)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[pyfunction] | ||||||
|  | #[pyo3(text_signature = "(expr)")] | ||||||
|  | fn expr_test(expr: &str, py: Python) -> PyResult<PyObject> { | ||||||
|  |     use eval::{to_value, Expr, Value}; | ||||||
|  |     let mut res = Expr::new(expr) | ||||||
|  |         .compile() | ||||||
|  |         .map_err(|e| PyErr::from(EdLrrError::EvalError(e)))?; | ||||||
|  |     let mut hm: HashMap<&str, Value> = HashMap::new(); | ||||||
|  |     hm.insert("foo", to_value(42)); | ||||||
|  |     hm.insert("bar", to_value((-1, -2, -3))); | ||||||
|  |     res = res.value("x", vec!["Hello", "world", "!"]); | ||||||
|  |     res = res.value("y", 42); | ||||||
|  |     res = res.value("p", (2.17, 5.14, 1.62)); | ||||||
|  |     res = res.value("hw", "Hello World!"); | ||||||
|  |     res = res.value("hm", hm); | ||||||
|  |     to_py(res.exec(), py) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[pyfunction] | ||||||
|  | #[pyo3(text_signature = "(path, out_path, /)")] | ||||||
|  | fn preprocess_galaxy(path: &str, out_path: &str) -> PyResult<()> { | ||||||
|  |     use common::build_index; | ||||||
|  |     use galaxy::process_galaxy_dump; | ||||||
|  |     let path = PathBuf::from(path); | ||||||
|  |     let out_path = PathBuf::from(out_path); | ||||||
|  |     process_galaxy_dump(&path, &out_path).unwrap(); | ||||||
|  |     build_index(&out_path)?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[pymodule] | #[pymodule] | ||||||
| pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> { | pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> { | ||||||
|     better_panic::install(); |     better_panic::install(); | ||||||
| 
 |     pyo3_log::init(); | ||||||
|  |     profiling::init(); | ||||||
|     m.add_class::<PyRouter>()?; |     m.add_class::<PyRouter>()?; | ||||||
|     m.add_class::<PyShip>()?; |     m.add_class::<PyShip>()?; | ||||||
|     /* |     m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_galaxy))?; | ||||||
|     #[pyfn(m, "get_ships_from_journal")] |     m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_edsm))?; | ||||||
|     fn get_ships_from_journal(py: Python) -> PyResult<PyObject> { |     m.add_wrapped(pyo3::wrap_pyfunction!(expr_test))?; | ||||||
|         let ship = match Ship::new_from_journal() { |  | ||||||
|             Ok(ship) => ship, |  | ||||||
|             Err(err_msg) => { |  | ||||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         let ships: Vec<(_,_)> = ship.iter().map(|(k,v)| (k.to_object(py),v.to_object(py))).collect(); |  | ||||||
|         Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     #[pyfn(m, "get_ships_from_loadout")] |  | ||||||
|     fn get_ship_from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> { |  | ||||||
|         let ship = match Ship::new_from_json(loadout) { |  | ||||||
|             Ok(ship) => ship, |  | ||||||
|             Err(err_msg) => { |  | ||||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         Ok(ship.to_object(py)) |  | ||||||
|     } |  | ||||||
|     */ |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| /// Preprocess bodies.json and systemsWithCoordinates.json into stars.csv
 |  | ||||||
|     #[pyfn(m, "preprocess")] |  | ||||||
|     #[text_signature = "(infile_systems, infile_bodies, outfile, callback, /)"] |  | ||||||
|     fn ed_lrr_preprocess( |  | ||||||
|         py: Python<'static>, |  | ||||||
|         infile_systems: String, |  | ||||||
|         infile_bodies: String, |  | ||||||
|         outfile: String, |  | ||||||
|         callback: PyObject, |  | ||||||
|     ) -> PyResult<PyObject> { |  | ||||||
|         use preprocess::*; |  | ||||||
|         let state = PyDict::new(py); |  | ||||||
|         let state_dict = PyDict::new(py); |  | ||||||
|         callback.call(py, (state_dict,), None).unwrap(); |  | ||||||
|         let callback_wrapped = move |state: &PreprocessState| { |  | ||||||
|             // println!("SEND: {:?}",state);
 |  | ||||||
|             state_dict.set_item("file", state.file.clone())?; |  | ||||||
|             state_dict.set_item("total", state.total)?; |  | ||||||
|             state_dict.set_item("count", state.count)?; |  | ||||||
|             state_dict.set_item("done", state.done)?; |  | ||||||
|             state_dict.set_item("message", state.message.clone())?; |  | ||||||
|             callback.call(py, (state_dict,), None) |  | ||||||
|         }; |  | ||||||
|         preprocess_files( |  | ||||||
|             &PathBuf::from(infile_bodies), |  | ||||||
|             &PathBuf::from(infile_systems), |  | ||||||
|             &PathBuf::from(outfile), |  | ||||||
|             &callback_wrapped, |  | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
|         Ok(state.to_object(py)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Find system by name
 |  | ||||||
|     #[pyfn(m, "find_sys")] |  | ||||||
|     #[text_signature = "(sys_names, sys_list_path, /)"] |  | ||||||
|     fn find_sys(py: Python, sys_names: Vec<String>, sys_list: String) -> PyResult<PyObject> { |  | ||||||
|         let path = PathBuf::from(sys_list); |  | ||||||
|         match find_matches(&path, sys_names, false) { |  | ||||||
|             Ok(vals) => { |  | ||||||
|                 let ret = PyDict::new(py); |  | ||||||
|                 for (key, (diff, sys)) in vals { |  | ||||||
|                     let ret_dict = PyDict::new(py); |  | ||||||
|                     if let Some(val) = sys { |  | ||||||
|                         let pos = PyList::new(py, val.pos.iter()); |  | ||||||
|                         ret_dict.set_item("star_type", val.star_type.clone())?; |  | ||||||
|                         ret_dict.set_item("system", val.system.clone())?; |  | ||||||
|                         ret_dict.set_item("body", val.body.clone())?; |  | ||||||
|                         ret_dict.set_item("distance", val.distance)?; |  | ||||||
|                         ret_dict.set_item("pos", pos)?; |  | ||||||
|                         ret_dict.set_item("id", val.id)?; |  | ||||||
|                         ret.set_item(key, (diff, ret_dict).to_object(py))?; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 Ok(ret.to_object(py)) |  | ||||||
|             } |  | ||||||
|             Err(e) => Err(PyErr::new::<ValueError, _>(e)), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Compute a Route using the suplied parameters
 |  | ||||||
|     #[pyfn(m, "route")] |  | ||||||
|     #[text_signature = "(hops, range, mode, primary, permute, keep_first, keep_last, greedyness, precomp, path, num_workers, callback, /)"] |  | ||||||
|     #[allow(clippy::too_many_arguments)] |  | ||||||
|     fn py_route( |  | ||||||
|         py: Python<'static>, |  | ||||||
|         hops: Vec<&str>, |  | ||||||
|         range: f32, |  | ||||||
|         mode: String, |  | ||||||
|         primary: bool, |  | ||||||
|         permute: bool, |  | ||||||
|         keep_first: bool, |  | ||||||
|         keep_last: bool, |  | ||||||
|         greedyness: Option<f32>, |  | ||||||
|         precomp: Option<String>, |  | ||||||
|         path: String, |  | ||||||
|         num_workers: Option<usize>, |  | ||||||
|         callback: PyObject, |  | ||||||
|     ) -> PyResult<PyObject> { |  | ||||||
|         use route::*; |  | ||||||
|         let num_workers = num_workers.unwrap_or(1); |  | ||||||
|         let mode = match Mode::parse(&mode) { |  | ||||||
|             Ok(val) => val, |  | ||||||
|             Err(e) => { |  | ||||||
|                 return Err(PyErr::new::<ValueError, _>(e)); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         let state_dict = PyDict::new(py); |  | ||||||
|         { |  | ||||||
|             let cb_res = callback.call(py, (state_dict,), None); |  | ||||||
|             if cb_res.is_err() { |  | ||||||
|                 println!("Error: {:?}", cb_res); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         let callback_wrapped = move |state: &SearchState| { |  | ||||||
|             state_dict.set_item("mode", state.mode.clone())?; |  | ||||||
|             state_dict.set_item("system", state.system.clone())?; |  | ||||||
|             state_dict.set_item("body", state.body.clone())?; |  | ||||||
|             state_dict.set_item("depth", state.depth)?; |  | ||||||
|             state_dict.set_item("queue_size", state.queue_size)?; |  | ||||||
|             state_dict.set_item("d_rem", state.d_rem)?; |  | ||||||
|             state_dict.set_item("d_total", state.d_total)?; |  | ||||||
|             state_dict.set_item("prc_done", state.prc_done)?; |  | ||||||
|             state_dict.set_item("n_seen", state.n_seen)?; |  | ||||||
|             state_dict.set_item("prc_seen", state.prc_seen)?; |  | ||||||
|             state_dict.set_item("from", state.from.clone())?; |  | ||||||
|             state_dict.set_item("to", state.to.clone())?; |  | ||||||
|             let cb_res = callback.call(py, (state_dict,), None); |  | ||||||
|             if cb_res.is_err() { |  | ||||||
|                 println!("Error: {:?}", cb_res); |  | ||||||
|             } |  | ||||||
|             cb_res |  | ||||||
|         }; |  | ||||||
|         let hops: Vec<SysEntry> = (hops.iter().map(|v| SysEntry::from_str(&v)).collect::<Result<Vec<SysEntry>,_>>())?; |  | ||||||
|         println!("Resolving systems..."); |  | ||||||
|         let hops: Vec<u32> = match resolve(&hops, &PathBuf::from(&path)) { |  | ||||||
|             Ok(ids) => ids, |  | ||||||
|             Err(err_msg) => { |  | ||||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         let opts = RouteOpts { |  | ||||||
|             systems: hops, |  | ||||||
|             range: Some(range), |  | ||||||
|             file_path: PathBuf::from(path), |  | ||||||
|             precomp_file: precomp.map(PathBuf::from), |  | ||||||
|             callback: Box::new(callback_wrapped), |  | ||||||
|             mode, |  | ||||||
|             factor: greedyness, |  | ||||||
|             precompute: false, |  | ||||||
|             permute, |  | ||||||
|             keep_first, |  | ||||||
|             keep_last, |  | ||||||
|             primary, |  | ||||||
|             workers: num_workers, |  | ||||||
|         }; |  | ||||||
|         match route(opts) { |  | ||||||
|             Ok(Some(route)) => { |  | ||||||
|                 let hops = route.iter().map(|hop| { |  | ||||||
|                     let pos = PyList::new(py, hop.pos.iter()); |  | ||||||
|                     let elem = PyDict::new(py); |  | ||||||
|                     elem.set_item("star_type", hop.star_type.clone()).unwrap(); |  | ||||||
|                     elem.set_item("system", hop.system.clone()).unwrap(); |  | ||||||
|                     elem.set_item("body", hop.body.clone()).unwrap(); |  | ||||||
|                     elem.set_item("distance", hop.distance).unwrap(); |  | ||||||
|                     elem.set_item("pos", pos).unwrap(); |  | ||||||
|                     elem |  | ||||||
|                 }); |  | ||||||
|                 let lst = PyList::new(py, hops); |  | ||||||
|                 Ok(lst.to_object(py)) |  | ||||||
|             } |  | ||||||
|             Ok(None) => Ok(py.None()), |  | ||||||
|             Err(e) => Err(PyErr::new::<ValueError, _>(e)), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
|  |  | ||||||
							
								
								
									
										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::common::get_fsd_booster_info; | ||||||
| use crate::journal::*; | use crate::journal::*; | ||||||
|  | use eyre::Result; | ||||||
| use pyo3::conversion::ToPyObject; | use pyo3::conversion::ToPyObject; | ||||||
| use pyo3::prelude::*; | use pyo3::prelude::*; | ||||||
| use pyo3::types::PyDict; | use pyo3::types::PyDict; | ||||||
|  | @ -10,21 +12,25 @@ use std::fs::File; | ||||||
| use std::io::{BufRead, BufReader}; | use std::io::{BufRead, BufReader}; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| 
 | 
 | ||||||
|  | /// Frame Shift Drive information
 | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct FSD { | pub struct FSD { | ||||||
|  |     /// Rating
 | ||||||
|     pub rating_val: f32, |     pub rating_val: f32, | ||||||
|  |     /// Class
 | ||||||
|     pub class_val: f32, |     pub class_val: f32, | ||||||
|  |     /// Optimized Mass
 | ||||||
|     pub opt_mass: f32, |     pub opt_mass: f32, | ||||||
|  |     /// Max fuel per jump
 | ||||||
|     pub max_fuel: f32, |     pub max_fuel: f32, | ||||||
|  |     /// Boost factor
 | ||||||
|     pub boost: f32, |     pub boost: f32, | ||||||
|  |     /// Guardian booster bonus range
 | ||||||
|     pub guardian_booster: f32, |     pub guardian_booster: f32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct Ship { | pub struct Ship { | ||||||
|     pub name: String, |  | ||||||
|     pub ident: String, |  | ||||||
|     pub ship_type: String, |  | ||||||
|     pub base_mass: f32, |     pub base_mass: f32, | ||||||
|     pub fuel_mass: f32, |     pub fuel_mass: f32, | ||||||
|     pub fuel_capacity: f32, |     pub fuel_capacity: f32, | ||||||
|  | @ -33,9 +39,6 @@ pub struct Ship { | ||||||
| 
 | 
 | ||||||
| impl Ship { | impl Ship { | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         name: String, |  | ||||||
|         ident: String, |  | ||||||
|         ship_type: String, |  | ||||||
|         base_mass: f32, |         base_mass: f32, | ||||||
|         fuel_mass: f32, |         fuel_mass: f32, | ||||||
|         fuel_capacity: f32, |         fuel_capacity: f32, | ||||||
|  | @ -57,15 +60,12 @@ impl Ship { | ||||||
|         if fsd_type.1 < 2 || fsd_type.1 > 8 { |         if fsd_type.1 < 2 || fsd_type.1 > 8 { | ||||||
|             return Err(format!("Invalid class: {}", fsd_type.1)); |             return Err(format!("Invalid class: {}", fsd_type.1)); | ||||||
|         }; |         }; | ||||||
|         
 | 
 | ||||||
|         if guardian_booster!=0 { |         if guardian_booster != 0 { | ||||||
|             return Err("Guardian booster not yet implemented!".to_owned()) |             return Err("Guardian booster not yet implemented!".to_owned()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let ret = Self { |         let ret = Self { | ||||||
|             name, |  | ||||||
|             ident, |  | ||||||
|             ship_type, |  | ||||||
|             fuel_capacity, |             fuel_capacity, | ||||||
|             fuel_mass, |             fuel_mass, | ||||||
|             base_mass, |             base_mass, | ||||||
|  | @ -81,8 +81,8 @@ impl Ship { | ||||||
|         Ok(ret) |         Ok(ret) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn new_from_json(data: &str) -> Result<Self, String> { |     pub fn new_from_json(data: &str) -> Result<(String, Ship), String> { | ||||||
|         match serde_json::from_str::<Event>(&data) { |         match serde_json::from_str::<Event>(data) { | ||||||
|             Ok(Event { |             Ok(Event { | ||||||
|                 event: EventData::Unknown, |                 event: EventData::Unknown, | ||||||
|             }) => { |             }) => { | ||||||
|  | @ -101,7 +101,7 @@ impl Ship { | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn new_from_journal() -> Result<HashMap<String, Self>, String> { |     pub fn new_from_journal() -> Result<HashMap<String, Ship>, String> { | ||||||
|         let mut ret = HashMap::new(); |         let mut ret = HashMap::new(); | ||||||
|         let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap(); |         let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap(); | ||||||
|         let mut journals: Vec<PathBuf> = Vec::new(); |         let mut journals: Vec<PathBuf> = Vec::new(); | ||||||
|  | @ -110,12 +110,10 @@ impl Ship { | ||||||
|         userprofile.push("Frontier Developments"); |         userprofile.push("Frontier Developments"); | ||||||
|         userprofile.push("Elite Dangerous"); |         userprofile.push("Elite Dangerous"); | ||||||
|         if let Ok(iter) = userprofile.read_dir() { |         if let Ok(iter) = userprofile.read_dir() { | ||||||
|             for entry in iter { |             for entry in iter.flatten() { | ||||||
|                 if let Ok(entry) = entry { |                 if re.is_match(&entry.file_name().to_string_lossy()) { | ||||||
|                     if re.is_match(&entry.file_name().to_string_lossy()) { |                     journals.push(entry.path()); | ||||||
|                         journals.push(entry.path()); |                 }; | ||||||
|                     }; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         journals.sort(); |         journals.sort(); | ||||||
|  | @ -133,16 +131,7 @@ impl Ship { | ||||||
|                     }) => {} |                     }) => {} | ||||||
|                     Ok(ev) => { |                     Ok(ev) => { | ||||||
|                         if let Some(loadout) = ev.get_loadout() { |                         if let Some(loadout) = ev.get_loadout() { | ||||||
|                             let mut ship = loadout.try_into_ship()?; |                             let (key, ship) = loadout.try_into_ship()?; | ||||||
|                             if ship.name == "" { |  | ||||||
|                                 ship.name = "<NO NAME>".to_owned(); |  | ||||||
|                             } |  | ||||||
|                             let key = format!( |  | ||||||
|                                 "[{}] {} ({})", |  | ||||||
|                                 ship.ident, |  | ||||||
|                                 ship.name, |  | ||||||
|                                 ship.ship_type.to_ascii_lowercase() |  | ||||||
|                             ); |  | ||||||
|                             ret.insert(key, ship); |                             ret.insert(key, ship); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  | @ -179,7 +168,7 @@ impl Ship { | ||||||
|         Some(cost) |         Some(cost) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn jump_range(&self, fuel: f32, booster: bool) -> f32 { |     pub fn jump_range(&self, fuel: f32, booster: bool) -> f32 { | ||||||
|         let mass = self.base_mass + fuel; |         let mass = self.base_mass + fuel; | ||||||
|         let mut fuel = self.fsd.max_fuel.min(fuel); |         let mut fuel = self.fsd.max_fuel.min(fuel); | ||||||
|         if booster { |         if booster { | ||||||
|  | @ -198,6 +187,10 @@ impl Ship { | ||||||
|         return self.jump_range(self.fuel_mass, true); |         return self.jump_range(self.fuel_mass, true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn full_range(&self) -> f32 { | ||||||
|  |         return self.jump_range(self.fuel_capacity, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fn boost_fuel_mult(&self) -> f32 { |     fn boost_fuel_mult(&self) -> f32 { | ||||||
|         if self.fsd.guardian_booster == 0.0 { |         if self.fsd.guardian_booster == 0.0 { | ||||||
|             return 1.0; |             return 1.0; | ||||||
|  | @ -208,6 +201,21 @@ impl Ship { | ||||||
|         return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val); |         return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn fuel_cost_for_jump(&self, fuel_mass: f32, dist: f32, boost: f32) -> Option<(f32, f32)> { | ||||||
|  |         if dist == 0.0 { | ||||||
|  |             return Some((0.0, 0.0)); | ||||||
|  |         } | ||||||
|  |         let mass = self.base_mass + fuel_mass; | ||||||
|  |         let opt_mass = self.fsd.opt_mass * boost; | ||||||
|  |         let base_cost = (dist * mass) / opt_mass; | ||||||
|  |         let fuel_cost = (self.fsd.rating_val * 0.001 * base_cost.powf(self.fsd.class_val)) | ||||||
|  |             / self.boost_fuel_mult(); | ||||||
|  |         if fuel_cost > self.fsd.max_fuel || fuel_cost > fuel_mass { | ||||||
|  |             return None; | ||||||
|  |         }; | ||||||
|  |         return Some((fuel_cost, fuel_mass - fuel_cost)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn fuel_cost(&self, d: f32) -> f32 { |     pub fn fuel_cost(&self, d: f32) -> f32 { | ||||||
|         if d == 0.0 { |         if d == 0.0 { | ||||||
|             return 0.0; |             return 0.0; | ||||||
|  | @ -220,29 +228,6 @@ impl Ship { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* |  | ||||||
| #[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)] |  | ||||||
| pub struct FSD { |  | ||||||
|     pub rating_val: f32, |  | ||||||
|     pub class_val: f32, |  | ||||||
|     pub opt_mass: f32, |  | ||||||
|     pub max_fuel: f32, |  | ||||||
|     pub boost: f32, |  | ||||||
|     pub guardian_booster: f32, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)] |  | ||||||
| pub struct Ship { |  | ||||||
|     pub name: String, |  | ||||||
|     pub ident: String, |  | ||||||
|     pub ship_type: String, |  | ||||||
|     pub base_mass: f32, |  | ||||||
|     pub fuel_mass: f32, |  | ||||||
|     pub fuel_capacity: f32, |  | ||||||
|     pub fsd: FSD, |  | ||||||
| } |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| impl FSD { | impl FSD { | ||||||
|     pub fn to_object(&self, py: Python) -> PyResult<PyObject> { |     pub fn to_object(&self, py: Python) -> PyResult<PyObject> { | ||||||
|         let elem = PyDict::new(py); |         let elem = PyDict::new(py); | ||||||
|  | @ -259,9 +244,6 @@ impl FSD { | ||||||
| impl Ship { | impl Ship { | ||||||
|     pub fn to_object(&self, py: Python) -> PyResult<PyObject> { |     pub fn to_object(&self, py: Python) -> PyResult<PyObject> { | ||||||
|         let elem = PyDict::new(py); |         let elem = PyDict::new(py); | ||||||
|         elem.set_item("name", self.name.clone())?; |  | ||||||
|         elem.set_item("ident", self.ident.clone())?; |  | ||||||
|         elem.set_item("ship_type", self.ship_type.clone())?; |  | ||||||
|         elem.set_item("base_mass", self.base_mass)?; |         elem.set_item("base_mass", self.base_mass)?; | ||||||
|         elem.set_item("fuel_mass", self.fuel_mass)?; |         elem.set_item("fuel_mass", self.fuel_mass)?; | ||||||
|         elem.set_item("fuel_capacity", self.fuel_capacity)?; |         elem.set_item("fuel_capacity", self.fuel_capacity)?; | ||||||
|  |  | ||||||
							
								
								
									
										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 -*- | # -*- coding: utf-8 -*- | ||||||
| from setuptools import find_packages, setup | from setuptools import find_packages, setup | ||||||
| from setuptools_rust import Binding, RustExtension, Strip | from setuptools_rust import Binding, RustExtension, Strip | ||||||
| with open('README.md', 'r') as fh: | import os | ||||||
|  | 
 | ||||||
|  | with open("README.md", "r") as fh: | ||||||
|     long_description = fh.read() |     long_description = fh.read() | ||||||
| 
 | 
 | ||||||
| extras_require = { | extras_require = { | ||||||
|     'build': ['pyinstaller', 'pywin32'], |     "build": ["pyinstaller", "pywin32"], | ||||||
|     'test': [ |     "test": [ | ||||||
|         'pytest', |         "pytest", | ||||||
|         'pytest-cov', |         "pytest-cov", | ||||||
|         'pytest-dependency', |         "pytest-dependency", | ||||||
|         'pytest-benchmark[histogram]', |         "pytest-benchmark[histogram]", | ||||||
|         'pytest-metadata', |         "pytest-metadata", | ||||||
|         'pytest-flake8', |         "pytest-flake8", | ||||||
|         'pytest-flask', |         "pytest-flask", | ||||||
|         'pytest-mock', |         "pytest-mock", | ||||||
|         'pytest-flask-sqlalchemy', |         "pytest-flask-sqlalchemy", | ||||||
|         'pytest-steps', |         "pytest-steps", | ||||||
|         'pytest-xdist', |         "pytest-xdist", | ||||||
|         'flake8-bugbear', |         "flake8-bugbear", | ||||||
|         'flake8-comprehensions', |         "flake8-comprehensions", | ||||||
|         'cohesion', |         "cohesion", | ||||||
|         'hypothesis', |         "hypothesis", | ||||||
|         'flaky' |         "flaky", | ||||||
|     ], |     ], | ||||||
|     'dev': [ |     "dev": [ | ||||||
|         'black; python_version >= "3.6"', |         'black; python_version >= "3.6"', | ||||||
|         'jinja2', |         "jinja2", | ||||||
|         'tsp', |         "tsp", | ||||||
|         'flake8', |         "flake8", | ||||||
|         'flake8-bugbear', |         "flake8-bugbear", | ||||||
|         'flake8-comprehensions', |         "flake8-comprehensions", | ||||||
|         'cohesion', |         "cohesion", | ||||||
|         'pre-commit', |         "pre-commit", | ||||||
|         'ipython', |         "ipython", | ||||||
|         'flask-konch', |         "flask-konch", | ||||||
|         'setuptools_rust' |         "setuptools_rust", | ||||||
|     ], |     ], | ||||||
|     'gui': ['PyQt5', 'pyperclip'], |     "gui": ["PyQt5", "pyperclip"], | ||||||
|     'web': [ |     "web": [ | ||||||
|         'flask', |         "flask", | ||||||
|         'gevent', |         "gevent", | ||||||
|         'webargs', |         "webargs", | ||||||
|         'flask-executor', |         "flask-executor", | ||||||
|         'flask-wtf', |         "flask-wtf", | ||||||
|         'flask-user', |         "flask-user", | ||||||
|         'flask-debugtoolbar', |         "flask-debugtoolbar", | ||||||
|         'flask-bootstrap4', |         "flask-bootstrap4", | ||||||
|         'flask-sqlalchemy', |         "flask-sqlalchemy", | ||||||
|         'flask-nav', |         "flask-nav", | ||||||
|         'flask-admin', |         "flask-admin", | ||||||
|         'sqlalchemy_utils[password]', |         "sqlalchemy_utils[password]", | ||||||
|         'python-dotenv', |         "python-dotenv", | ||||||
|     ], |     ], | ||||||
| } | } | ||||||
| extras_require['all'] = sorted(set(sum(extras_require.values(), []))) | extras_require["all"] = sorted(set(sum(extras_require.values(), []))) | ||||||
|  | 
 | ||||||
|  | # os.environ["RUSTC_WRAPPER"]='"{}" /c echo'.format(os.environ['COMSPEC']) | ||||||
| 
 | 
 | ||||||
| setup( | setup( | ||||||
|     use_scm_version={'write_to': '__version__.py'}, |     use_scm_version={"write_to": "__version__.py"}, | ||||||
|     name='ed_lrr_gui', |     name="ed_lrr_gui", | ||||||
|     author='Daniel Seiller', |     author="Daniel Seiller", | ||||||
|     author_email='earthnuker@gmail.com', |     author_email="earthnuker@gmail.com", | ||||||
|     description='Elite: Dangerous long range route plotter', |     description="Elite: Dangerous long range route plotter", | ||||||
|     long_description=long_description, |     long_description=long_description, | ||||||
|     long_description_content_type='text/markdown', |     long_description_content_type="text/markdown", | ||||||
|     url='https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui', |     url="https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui", | ||||||
|     rust_extensions=[ |     rust_extensions=[ | ||||||
|         RustExtension( |         RustExtension( | ||||||
|             '_ed_lrr', |             "_ed_lrr", | ||||||
|             path='rust/Cargo.toml', |             path="rust/Cargo.toml", | ||||||
|             binding=Binding.PyO3, |             binding=Binding.PyO3, | ||||||
|             strip=Strip.No, |             strip=Strip.No, | ||||||
|  |             rustc_flags=["--emit=asm"], | ||||||
|  |             # features=["profiling"], | ||||||
|             debug=False, |             debug=False, | ||||||
|             native=True, |             native=True, | ||||||
|             quiet=True, |             quiet=True, | ||||||
|  | @ -78,38 +84,38 @@ setup( | ||||||
|     ], |     ], | ||||||
|     packages=find_packages(), |     packages=find_packages(), | ||||||
|     entry_points={ |     entry_points={ | ||||||
|         'console_scripts': ['ed_lrr = ed_lrr_gui.__main__:main'], |         "console_scripts": ["ed_lrr = ed_lrr_gui.__main__:main"], | ||||||
|         'gui_scripts': ['ed_lrr_gui = ed_lrr_gui.__main__:gui_main'] |         "gui_scripts": ["ed_lrr_gui = ed_lrr_gui.__main__:gui_main"], | ||||||
|     }, |     }, | ||||||
|     install_requires=[ |     install_requires=[ | ||||||
|         'appdirs', |         "appdirs", | ||||||
|         'PyYAML', |         "PyYAML", | ||||||
|         'requests', |         "requests", | ||||||
|         'python-dateutil', |         "python-dateutil", | ||||||
|         'click', |         "click", | ||||||
|         'tqdm', |         "tqdm", | ||||||
|         'click-default-group', |         "click-default-group", | ||||||
|         'profig', |         "profig", | ||||||
|         'ujson', |         "ujson", | ||||||
|         'colorama', |         "colorama", | ||||||
|         'svgwrite', |         "svgwrite", | ||||||
|  |         "coloredlogs", | ||||||
|     ], |     ], | ||||||
|     setup_requires=['setuptools', 'setuptools-rust', |     setup_requires=["setuptools", "setuptools-rust", "setuptools-scm", "wheel"], | ||||||
|                     'setuptools-scm', 'wheel'], |     dependency_links=["https://github.com/Nuitka/Nuitka/archive/develop.zip"], | ||||||
|     dependency_links=['https://github.com/Nuitka/Nuitka/archive/develop.zip'], |  | ||||||
|     extras_require=extras_require, |     extras_require=extras_require, | ||||||
|     classifiers=[ |     classifiers=[ | ||||||
|         'License :: OSI Approved :: MIT License', |         "License :: OSI Approved :: MIT License", | ||||||
|         'Programming Language :: Rust', |         "Programming Language :: Rust", | ||||||
|         'Programming Language :: Python', |         "Programming Language :: Python", | ||||||
|         'Programming Language :: Python :: 3', |         "Programming Language :: Python :: 3", | ||||||
|         'Programming Language :: Python :: 3.5', |         "Programming Language :: Python :: 3.5", | ||||||
|         'Programming Language :: Python :: 3.6', |         "Programming Language :: Python :: 3.6", | ||||||
|         'Programming Language :: Python :: 3.7', |         "Programming Language :: Python :: 3.7", | ||||||
|         'Programming Language :: Python :: 3.8', |         "Programming Language :: Python :: 3.8", | ||||||
|         'Programming Language :: Python :: Implementation :: CPython', |         "Programming Language :: Python :: Implementation :: CPython", | ||||||
|         'Operating System :: Windows', |         "Operating System :: Windows", | ||||||
|         'Operating System :: Linux', |         "Operating System :: Linux", | ||||||
|     ], |     ], | ||||||
|     include_package_data=True, |     include_package_data=True, | ||||||
|     zip_safe=False, |     zip_safe=False, | ||||||
|  |  | ||||||
							
								
								
									
										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) |             err = "Failed to resolve {}".format(name) | ||||||
|             assert name in resolved_systems, err |             assert name in resolved_systems, err | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.dependency(depends=["Test_ED_LRR::test_load_and_resolve"]) |     @pytest.mark.dependency(depends=["Test_PyRouter::test_load_and_resolve"]) | ||||||
|  |     @flaky(max_runs=10, min_passes=5) | ||||||
|     @pytest.mark.parametrize( |     @pytest.mark.parametrize( | ||||||
|         "greedyness", greedyness, ids=lambda v: "greedyness:{}".format(v) |         "greedyness", greedyness, ids=lambda v: "greedyness:{}".format(v) | ||||||
|     ) |     ) | ||||||
|     @flaky(max_runs=10, min_passes=5) |  | ||||||
|     def test_zero_range_fails(self, py_router, greedyness): |     def test_zero_range_fails(self, py_router, greedyness): | ||||||
|         r, resolved_systems = py_router |         r, resolved_systems = py_router | ||||||
|         waypoints = random.sample(list(resolved_systems.values()), k=2) |         waypoints = random.sample(list(resolved_systems.values()), k=2) | ||||||
|         err = pytest.raises(RuntimeError, r.route, waypoints, 0, greedyness) |         err = pytest.raises(RuntimeError, r.route, waypoints, 0, greedyness) | ||||||
|         err.match(r"No route from .* to .* found!") |         err.match(r"No route from .* to .* found!") | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.dependency(depends=["Test_ED_LRR::test_load_and_resolve"]) |     @pytest.mark.dependency(depends=["Test_PyRouter::test_load_and_resolve"]) | ||||||
|     @flaky(max_runs=10, min_passes=2) |     @flaky(max_runs=10, min_passes=2) | ||||||
|     @pytest.mark.parametrize("workers", n_workers, ids=idf("workers")) |     @pytest.mark.parametrize("workers", n_workers, ids=idf("workers")) | ||||||
|     @pytest.mark.parametrize("jump_range", ranges, ids=idf("range")) |     @pytest.mark.parametrize("jump_range", ranges, ids=idf("range")) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue