2022-02-23
This commit is contained in:
		
							parent
							
								
									35a0c40d14
								
							
						
					
					
						commit
						dc68cce9ed
					
				
					 80 changed files with 859345 additions and 4387 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -26,3 +26,4 @@ ed_lrr_gui/web/ed_lrr_web_ui.db | |||
| __version__.py | ||||
| .nox/ | ||||
| dist_vis.py | ||||
| img/** | ||||
							
								
								
									
										6
									
								
								.ipynb_checkpoints/Untitled-checkpoint.ipynb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.ipynb_checkpoints/Untitled-checkpoint.ipynb
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| { | ||||
|  "cells": [], | ||||
|  "metadata": {}, | ||||
|  "nbformat": 4, | ||||
|  "nbformat_minor": 4 | ||||
| } | ||||
							
								
								
									
										904
									
								
								.ipynb_checkpoints/heuristic_vis-checkpoint.ipynb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										904
									
								
								.ipynb_checkpoints/heuristic_vis-checkpoint.ipynb
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,904 @@ | |||
| { | ||||
|  "cells": [ | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": 2, | ||||
|    "metadata": {}, | ||||
|    "outputs": [ | ||||
|     { | ||||
|      "name": "stdout", | ||||
|      "output_type": "stream", | ||||
|      "text": [ | ||||
|       "Populating the interactive namespace from numpy and matplotlib\n" | ||||
|      ] | ||||
|     } | ||||
|    ], | ||||
|    "source": [ | ||||
|     "%pylab notebook" | ||||
|    ] | ||||
|   }, | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": 131, | ||||
|    "metadata": {}, | ||||
|    "outputs": [ | ||||
|     { | ||||
|      "name": "stderr", | ||||
|      "output_type": "stream", | ||||
|      "text": [ | ||||
|       "<ipython-input-131-1f27bb9e71ac>:5: RuntimeWarning: invalid value encountered in double_scalars\n", | ||||
|       "  ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n" | ||||
|      ] | ||||
|     }, | ||||
|     { | ||||
|      "data": { | ||||
|       "application/javascript": [ | ||||
|        "/* Put everything inside the global mpl namespace */\n", | ||||
|        "window.mpl = {};\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.get_websocket_type = function() {\n", | ||||
|        "    if (typeof(WebSocket) !== 'undefined') {\n", | ||||
|        "        return WebSocket;\n", | ||||
|        "    } else if (typeof(MozWebSocket) !== 'undefined') {\n", | ||||
|        "        return MozWebSocket;\n", | ||||
|        "    } else {\n", | ||||
|        "        alert('Your browser does not have WebSocket support. ' +\n", | ||||
|        "              'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", | ||||
|        "              'Firefox 4 and 5 are also supported but you ' +\n", | ||||
|        "              'have to enable WebSockets in about:config.');\n", | ||||
|        "    };\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", | ||||
|        "    this.id = figure_id;\n", | ||||
|        "\n", | ||||
|        "    this.ws = websocket;\n", | ||||
|        "\n", | ||||
|        "    this.supports_binary = (this.ws.binaryType != undefined);\n", | ||||
|        "\n", | ||||
|        "    if (!this.supports_binary) {\n", | ||||
|        "        var warnings = document.getElementById(\"mpl-warnings\");\n", | ||||
|        "        if (warnings) {\n", | ||||
|        "            warnings.style.display = 'block';\n", | ||||
|        "            warnings.textContent = (\n", | ||||
|        "                \"This browser does not support binary websocket messages. \" +\n", | ||||
|        "                    \"Performance may be slow.\");\n", | ||||
|        "        }\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    this.imageObj = new Image();\n", | ||||
|        "\n", | ||||
|        "    this.context = undefined;\n", | ||||
|        "    this.message = undefined;\n", | ||||
|        "    this.canvas = undefined;\n", | ||||
|        "    this.rubberband_canvas = undefined;\n", | ||||
|        "    this.rubberband_context = undefined;\n", | ||||
|        "    this.format_dropdown = undefined;\n", | ||||
|        "\n", | ||||
|        "    this.image_mode = 'full';\n", | ||||
|        "\n", | ||||
|        "    this.root = $('<div/>');\n", | ||||
|        "    this._root_extra_style(this.root)\n", | ||||
|        "    this.root.attr('style', 'display: inline-block');\n", | ||||
|        "\n", | ||||
|        "    $(parent_element).append(this.root);\n", | ||||
|        "\n", | ||||
|        "    this._init_header(this);\n", | ||||
|        "    this._init_canvas(this);\n", | ||||
|        "    this._init_toolbar(this);\n", | ||||
|        "\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    this.waiting = false;\n", | ||||
|        "\n", | ||||
|        "    this.ws.onopen =  function () {\n", | ||||
|        "            fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", | ||||
|        "            fig.send_message(\"send_image_mode\", {});\n", | ||||
|        "            if (mpl.ratio != 1) {\n", | ||||
|        "                fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", | ||||
|        "            }\n", | ||||
|        "            fig.send_message(\"refresh\", {});\n", | ||||
|        "        }\n", | ||||
|        "\n", | ||||
|        "    this.imageObj.onload = function() {\n", | ||||
|        "            if (fig.image_mode == 'full') {\n", | ||||
|        "                // Full images could contain transparency (where diff images\n", | ||||
|        "                // almost always do), so we need to clear the canvas so that\n", | ||||
|        "                // there is no ghosting.\n", | ||||
|        "                fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", | ||||
|        "            }\n", | ||||
|        "            fig.context.drawImage(fig.imageObj, 0, 0);\n", | ||||
|        "        };\n", | ||||
|        "\n", | ||||
|        "    this.imageObj.onunload = function() {\n", | ||||
|        "        fig.ws.close();\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    this.ws.onmessage = this._make_on_message_function(this);\n", | ||||
|        "\n", | ||||
|        "    this.ondownload = ondownload;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_header = function() {\n", | ||||
|        "    var titlebar = $(\n", | ||||
|        "        '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n", | ||||
|        "        'ui-helper-clearfix\"/>');\n", | ||||
|        "    var titletext = $(\n", | ||||
|        "        '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n", | ||||
|        "        'text-align: center; padding: 3px;\"/>');\n", | ||||
|        "    titlebar.append(titletext)\n", | ||||
|        "    this.root.append(titlebar);\n", | ||||
|        "    this.header = titletext[0];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", | ||||
|        "\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", | ||||
|        "\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_canvas = function() {\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    var canvas_div = $('<div/>');\n", | ||||
|        "\n", | ||||
|        "    canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", | ||||
|        "\n", | ||||
|        "    function canvas_keyboard_event(event) {\n", | ||||
|        "        return fig.key_event(event, event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    canvas_div.keydown('key_press', canvas_keyboard_event);\n", | ||||
|        "    canvas_div.keyup('key_release', canvas_keyboard_event);\n", | ||||
|        "    this.canvas_div = canvas_div\n", | ||||
|        "    this._canvas_extra_style(canvas_div)\n", | ||||
|        "    this.root.append(canvas_div);\n", | ||||
|        "\n", | ||||
|        "    var canvas = $('<canvas/>');\n", | ||||
|        "    canvas.addClass('mpl-canvas');\n", | ||||
|        "    canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", | ||||
|        "\n", | ||||
|        "    this.canvas = canvas[0];\n", | ||||
|        "    this.context = canvas[0].getContext(\"2d\");\n", | ||||
|        "\n", | ||||
|        "    var backingStore = this.context.backingStorePixelRatio ||\n", | ||||
|        "\tthis.context.webkitBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.mozBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.msBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.oBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.backingStorePixelRatio || 1;\n", | ||||
|        "\n", | ||||
|        "    mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", | ||||
|        "\n", | ||||
|        "    var rubberband = $('<canvas/>');\n", | ||||
|        "    rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", | ||||
|        "\n", | ||||
|        "    var pass_mouse_events = true;\n", | ||||
|        "\n", | ||||
|        "    canvas_div.resizable({\n", | ||||
|        "        start: function(event, ui) {\n", | ||||
|        "            pass_mouse_events = false;\n", | ||||
|        "        },\n", | ||||
|        "        resize: function(event, ui) {\n", | ||||
|        "            fig.request_resize(ui.size.width, ui.size.height);\n", | ||||
|        "        },\n", | ||||
|        "        stop: function(event, ui) {\n", | ||||
|        "            pass_mouse_events = true;\n", | ||||
|        "            fig.request_resize(ui.size.width, ui.size.height);\n", | ||||
|        "        },\n", | ||||
|        "    });\n", | ||||
|        "\n", | ||||
|        "    function mouse_event_fn(event) {\n", | ||||
|        "        if (pass_mouse_events)\n", | ||||
|        "            return fig.mouse_event(event, event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    rubberband.mousedown('button_press', mouse_event_fn);\n", | ||||
|        "    rubberband.mouseup('button_release', mouse_event_fn);\n", | ||||
|        "    // Throttle sequential mouse events to 1 every 20ms.\n", | ||||
|        "    rubberband.mousemove('motion_notify', mouse_event_fn);\n", | ||||
|        "\n", | ||||
|        "    rubberband.mouseenter('figure_enter', mouse_event_fn);\n", | ||||
|        "    rubberband.mouseleave('figure_leave', mouse_event_fn);\n", | ||||
|        "\n", | ||||
|        "    canvas_div.on(\"wheel\", function (event) {\n", | ||||
|        "        event = event.originalEvent;\n", | ||||
|        "        event['data'] = 'scroll'\n", | ||||
|        "        if (event.deltaY < 0) {\n", | ||||
|        "            event.step = 1;\n", | ||||
|        "        } else {\n", | ||||
|        "            event.step = -1;\n", | ||||
|        "        }\n", | ||||
|        "        mouse_event_fn(event);\n", | ||||
|        "    });\n", | ||||
|        "\n", | ||||
|        "    canvas_div.append(canvas);\n", | ||||
|        "    canvas_div.append(rubberband);\n", | ||||
|        "\n", | ||||
|        "    this.rubberband = rubberband;\n", | ||||
|        "    this.rubberband_canvas = rubberband[0];\n", | ||||
|        "    this.rubberband_context = rubberband[0].getContext(\"2d\");\n", | ||||
|        "    this.rubberband_context.strokeStyle = \"#000000\";\n", | ||||
|        "\n", | ||||
|        "    this._resize_canvas = function(width, height) {\n", | ||||
|        "        // Keep the size of the canvas, canvas container, and rubber band\n", | ||||
|        "        // canvas in synch.\n", | ||||
|        "        canvas_div.css('width', width)\n", | ||||
|        "        canvas_div.css('height', height)\n", | ||||
|        "\n", | ||||
|        "        canvas.attr('width', width * mpl.ratio);\n", | ||||
|        "        canvas.attr('height', height * mpl.ratio);\n", | ||||
|        "        canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", | ||||
|        "\n", | ||||
|        "        rubberband.attr('width', width);\n", | ||||
|        "        rubberband.attr('height', height);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    // Set the figure to an initial 600x600px, this will subsequently be updated\n", | ||||
|        "    // upon first draw.\n", | ||||
|        "    this._resize_canvas(600, 600);\n", | ||||
|        "\n", | ||||
|        "    // Disable right mouse context menu.\n", | ||||
|        "    $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", | ||||
|        "        return false;\n", | ||||
|        "    });\n", | ||||
|        "\n", | ||||
|        "    function set_focus () {\n", | ||||
|        "        canvas.focus();\n", | ||||
|        "        canvas_div.focus();\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    window.setTimeout(set_focus, 100);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_toolbar = function() {\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    var nav_element = $('<div/>');\n", | ||||
|        "    nav_element.attr('style', 'width: 100%');\n", | ||||
|        "    this.root.append(nav_element);\n", | ||||
|        "\n", | ||||
|        "    // Define a callback function for later on.\n", | ||||
|        "    function toolbar_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onclick(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "    function toolbar_mouse_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onmouseover(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    for(var toolbar_ind in mpl.toolbar_items) {\n", | ||||
|        "        var name = mpl.toolbar_items[toolbar_ind][0];\n", | ||||
|        "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", | ||||
|        "        var image = mpl.toolbar_items[toolbar_ind][2];\n", | ||||
|        "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n", | ||||
|        "\n", | ||||
|        "        if (!name) {\n", | ||||
|        "            // put a spacer in here.\n", | ||||
|        "            continue;\n", | ||||
|        "        }\n", | ||||
|        "        var button = $('<button/>');\n", | ||||
|        "        button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n", | ||||
|        "                        'ui-button-icon-only');\n", | ||||
|        "        button.attr('role', 'button');\n", | ||||
|        "        button.attr('aria-disabled', 'false');\n", | ||||
|        "        button.click(method_name, toolbar_event);\n", | ||||
|        "        button.mouseover(tooltip, toolbar_mouse_event);\n", | ||||
|        "\n", | ||||
|        "        var icon_img = $('<span/>');\n", | ||||
|        "        icon_img.addClass('ui-button-icon-primary ui-icon');\n", | ||||
|        "        icon_img.addClass(image);\n", | ||||
|        "        icon_img.addClass('ui-corner-all');\n", | ||||
|        "\n", | ||||
|        "        var tooltip_span = $('<span/>');\n", | ||||
|        "        tooltip_span.addClass('ui-button-text');\n", | ||||
|        "        tooltip_span.html(tooltip);\n", | ||||
|        "\n", | ||||
|        "        button.append(icon_img);\n", | ||||
|        "        button.append(tooltip_span);\n", | ||||
|        "\n", | ||||
|        "        nav_element.append(button);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var fmt_picker_span = $('<span/>');\n", | ||||
|        "\n", | ||||
|        "    var fmt_picker = $('<select/>');\n", | ||||
|        "    fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n", | ||||
|        "    fmt_picker_span.append(fmt_picker);\n", | ||||
|        "    nav_element.append(fmt_picker_span);\n", | ||||
|        "    this.format_dropdown = fmt_picker[0];\n", | ||||
|        "\n", | ||||
|        "    for (var ind in mpl.extensions) {\n", | ||||
|        "        var fmt = mpl.extensions[ind];\n", | ||||
|        "        var option = $(\n", | ||||
|        "            '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n", | ||||
|        "        fmt_picker.append(option);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    // Add hover states to the ui-buttons\n", | ||||
|        "    $( \".ui-button\" ).hover(\n", | ||||
|        "        function() { $(this).addClass(\"ui-state-hover\");},\n", | ||||
|        "        function() { $(this).removeClass(\"ui-state-hover\");}\n", | ||||
|        "    );\n", | ||||
|        "\n", | ||||
|        "    var status_bar = $('<span class=\"mpl-message\"/>');\n", | ||||
|        "    nav_element.append(status_bar);\n", | ||||
|        "    this.message = status_bar[0];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n", | ||||
|        "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", | ||||
|        "    // which will in turn request a refresh of the image.\n", | ||||
|        "    this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.send_message = function(type, properties) {\n", | ||||
|        "    properties['type'] = type;\n", | ||||
|        "    properties['figure_id'] = this.id;\n", | ||||
|        "    this.ws.send(JSON.stringify(properties));\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.send_draw_message = function() {\n", | ||||
|        "    if (!this.waiting) {\n", | ||||
|        "        this.waiting = true;\n", | ||||
|        "        this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n", | ||||
|        "    }\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_save = function(fig, msg) {\n", | ||||
|        "    var format_dropdown = fig.format_dropdown;\n", | ||||
|        "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", | ||||
|        "    fig.ondownload(fig, format);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_resize = function(fig, msg) {\n", | ||||
|        "    var size = msg['size'];\n", | ||||
|        "    if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n", | ||||
|        "        fig._resize_canvas(size[0], size[1]);\n", | ||||
|        "        fig.send_message(\"refresh\", {});\n", | ||||
|        "    };\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n", | ||||
|        "    var x0 = msg['x0'] / mpl.ratio;\n", | ||||
|        "    var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", | ||||
|        "    var x1 = msg['x1'] / mpl.ratio;\n", | ||||
|        "    var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", | ||||
|        "    x0 = Math.floor(x0) + 0.5;\n", | ||||
|        "    y0 = Math.floor(y0) + 0.5;\n", | ||||
|        "    x1 = Math.floor(x1) + 0.5;\n", | ||||
|        "    y1 = Math.floor(y1) + 0.5;\n", | ||||
|        "    var min_x = Math.min(x0, x1);\n", | ||||
|        "    var min_y = Math.min(y0, y1);\n", | ||||
|        "    var width = Math.abs(x1 - x0);\n", | ||||
|        "    var height = Math.abs(y1 - y0);\n", | ||||
|        "\n", | ||||
|        "    fig.rubberband_context.clearRect(\n", | ||||
|        "        0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n", | ||||
|        "\n", | ||||
|        "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n", | ||||
|        "    // Updates the figure title.\n", | ||||
|        "    fig.header.textContent = msg['label'];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n", | ||||
|        "    var cursor = msg['cursor'];\n", | ||||
|        "    switch(cursor)\n", | ||||
|        "    {\n", | ||||
|        "    case 0:\n", | ||||
|        "        cursor = 'pointer';\n", | ||||
|        "        break;\n", | ||||
|        "    case 1:\n", | ||||
|        "        cursor = 'default';\n", | ||||
|        "        break;\n", | ||||
|        "    case 2:\n", | ||||
|        "        cursor = 'crosshair';\n", | ||||
|        "        break;\n", | ||||
|        "    case 3:\n", | ||||
|        "        cursor = 'move';\n", | ||||
|        "        break;\n", | ||||
|        "    }\n", | ||||
|        "    fig.rubberband_canvas.style.cursor = cursor;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_message = function(fig, msg) {\n", | ||||
|        "    fig.message.textContent = msg['message'];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_draw = function(fig, msg) {\n", | ||||
|        "    // Request the server to send over a new figure.\n", | ||||
|        "    fig.send_draw_message();\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n", | ||||
|        "    fig.image_mode = msg['mode'];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.updated_canvas_event = function() {\n", | ||||
|        "    // Called whenever the canvas gets updated.\n", | ||||
|        "    this.send_message(\"ack\", {});\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "// A function to construct a web socket function for onmessage handling.\n", | ||||
|        "// Called in the figure constructor.\n", | ||||
|        "mpl.figure.prototype._make_on_message_function = function(fig) {\n", | ||||
|        "    return function socket_on_message(evt) {\n", | ||||
|        "        if (evt.data instanceof Blob) {\n", | ||||
|        "            /* FIXME: We get \"Resource interpreted as Image but\n", | ||||
|        "             * transferred with MIME type text/plain:\" errors on\n", | ||||
|        "             * Chrome.  But how to set the MIME type?  It doesn't seem\n", | ||||
|        "             * to be part of the websocket stream */\n", | ||||
|        "            evt.data.type = \"image/png\";\n", | ||||
|        "\n", | ||||
|        "            /* Free the memory for the previous frames */\n", | ||||
|        "            if (fig.imageObj.src) {\n", | ||||
|        "                (window.URL || window.webkitURL).revokeObjectURL(\n", | ||||
|        "                    fig.imageObj.src);\n", | ||||
|        "            }\n", | ||||
|        "\n", | ||||
|        "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", | ||||
|        "                evt.data);\n", | ||||
|        "            fig.updated_canvas_event();\n", | ||||
|        "            fig.waiting = false;\n", | ||||
|        "            return;\n", | ||||
|        "        }\n", | ||||
|        "        else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", | ||||
|        "            fig.imageObj.src = evt.data;\n", | ||||
|        "            fig.updated_canvas_event();\n", | ||||
|        "            fig.waiting = false;\n", | ||||
|        "            return;\n", | ||||
|        "        }\n", | ||||
|        "\n", | ||||
|        "        var msg = JSON.parse(evt.data);\n", | ||||
|        "        var msg_type = msg['type'];\n", | ||||
|        "\n", | ||||
|        "        // Call the  \"handle_{type}\" callback, which takes\n", | ||||
|        "        // the figure and JSON message as its only arguments.\n", | ||||
|        "        try {\n", | ||||
|        "            var callback = fig[\"handle_\" + msg_type];\n", | ||||
|        "        } catch (e) {\n", | ||||
|        "            console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n", | ||||
|        "            return;\n", | ||||
|        "        }\n", | ||||
|        "\n", | ||||
|        "        if (callback) {\n", | ||||
|        "            try {\n", | ||||
|        "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", | ||||
|        "                callback(fig, msg);\n", | ||||
|        "            } catch (e) {\n", | ||||
|        "                console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n", | ||||
|        "            }\n", | ||||
|        "        }\n", | ||||
|        "    };\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", | ||||
|        "mpl.findpos = function(e) {\n", | ||||
|        "    //this section is from http://www.quirksmode.org/js/events_properties.html\n", | ||||
|        "    var targ;\n", | ||||
|        "    if (!e)\n", | ||||
|        "        e = window.event;\n", | ||||
|        "    if (e.target)\n", | ||||
|        "        targ = e.target;\n", | ||||
|        "    else if (e.srcElement)\n", | ||||
|        "        targ = e.srcElement;\n", | ||||
|        "    if (targ.nodeType == 3) // defeat Safari bug\n", | ||||
|        "        targ = targ.parentNode;\n", | ||||
|        "\n", | ||||
|        "    // jQuery normalizes the pageX and pageY\n", | ||||
|        "    // pageX,Y are the mouse positions relative to the document\n", | ||||
|        "    // offset() returns the position of the element relative to the document\n", | ||||
|        "    var x = e.pageX - $(targ).offset().left;\n", | ||||
|        "    var y = e.pageY - $(targ).offset().top;\n", | ||||
|        "\n", | ||||
|        "    return {\"x\": x, \"y\": y};\n", | ||||
|        "};\n", | ||||
|        "\n", | ||||
|        "/*\n", | ||||
|        " * return a copy of an object with only non-object keys\n", | ||||
|        " * we need this to avoid circular references\n", | ||||
|        " * http://stackoverflow.com/a/24161582/3208463\n", | ||||
|        " */\n", | ||||
|        "function simpleKeys (original) {\n", | ||||
|        "  return Object.keys(original).reduce(function (obj, key) {\n", | ||||
|        "    if (typeof original[key] !== 'object')\n", | ||||
|        "        obj[key] = original[key]\n", | ||||
|        "    return obj;\n", | ||||
|        "  }, {});\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.mouse_event = function(event, name) {\n", | ||||
|        "    var canvas_pos = mpl.findpos(event)\n", | ||||
|        "\n", | ||||
|        "    if (name === 'button_press')\n", | ||||
|        "    {\n", | ||||
|        "        this.canvas.focus();\n", | ||||
|        "        this.canvas_div.focus();\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var x = canvas_pos.x * mpl.ratio;\n", | ||||
|        "    var y = canvas_pos.y * mpl.ratio;\n", | ||||
|        "\n", | ||||
|        "    this.send_message(name, {x: x, y: y, button: event.button,\n", | ||||
|        "                             step: event.step,\n", | ||||
|        "                             guiEvent: simpleKeys(event)});\n", | ||||
|        "\n", | ||||
|        "    /* This prevents the web browser from automatically changing to\n", | ||||
|        "     * the text insertion cursor when the button is pressed.  We want\n", | ||||
|        "     * to control all of the cursor setting manually through the\n", | ||||
|        "     * 'cursor' event from matplotlib */\n", | ||||
|        "    event.preventDefault();\n", | ||||
|        "    return false;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._key_event_extra = function(event, name) {\n", | ||||
|        "    // Handle any extra behaviour associated with a key event\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.key_event = function(event, name) {\n", | ||||
|        "\n", | ||||
|        "    // Prevent repeat events\n", | ||||
|        "    if (name == 'key_press')\n", | ||||
|        "    {\n", | ||||
|        "        if (event.which === this._key)\n", | ||||
|        "            return;\n", | ||||
|        "        else\n", | ||||
|        "            this._key = event.which;\n", | ||||
|        "    }\n", | ||||
|        "    if (name == 'key_release')\n", | ||||
|        "        this._key = null;\n", | ||||
|        "\n", | ||||
|        "    var value = '';\n", | ||||
|        "    if (event.ctrlKey && event.which != 17)\n", | ||||
|        "        value += \"ctrl+\";\n", | ||||
|        "    if (event.altKey && event.which != 18)\n", | ||||
|        "        value += \"alt+\";\n", | ||||
|        "    if (event.shiftKey && event.which != 16)\n", | ||||
|        "        value += \"shift+\";\n", | ||||
|        "\n", | ||||
|        "    value += 'k';\n", | ||||
|        "    value += event.which.toString();\n", | ||||
|        "\n", | ||||
|        "    this._key_event_extra(event, name);\n", | ||||
|        "\n", | ||||
|        "    this.send_message(name, {key: value,\n", | ||||
|        "                             guiEvent: simpleKeys(event)});\n", | ||||
|        "    return false;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n", | ||||
|        "    if (name == 'download') {\n", | ||||
|        "        this.handle_save(this, null);\n", | ||||
|        "    } else {\n", | ||||
|        "        this.send_message(\"toolbar_button\", {name: name});\n", | ||||
|        "    }\n", | ||||
|        "};\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n", | ||||
|        "    this.message.textContent = tooltip;\n", | ||||
|        "};\n", | ||||
|        "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", | ||||
|        "\n", | ||||
|        "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", | ||||
|        "\n", | ||||
|        "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", | ||||
|        "    // Create a \"websocket\"-like object which calls the given IPython comm\n", | ||||
|        "    // object with the appropriate methods. Currently this is a non binary\n", | ||||
|        "    // socket, so there is still some room for performance tuning.\n", | ||||
|        "    var ws = {};\n", | ||||
|        "\n", | ||||
|        "    ws.close = function() {\n", | ||||
|        "        comm.close()\n", | ||||
|        "    };\n", | ||||
|        "    ws.send = function(m) {\n", | ||||
|        "        //console.log('sending', m);\n", | ||||
|        "        comm.send(m);\n", | ||||
|        "    };\n", | ||||
|        "    // Register the callback with on_msg.\n", | ||||
|        "    comm.on_msg(function(msg) {\n", | ||||
|        "        //console.log('receiving', msg['content']['data'], msg);\n", | ||||
|        "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n", | ||||
|        "        ws.onmessage(msg['content']['data'])\n", | ||||
|        "    });\n", | ||||
|        "    return ws;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.mpl_figure_comm = function(comm, msg) {\n", | ||||
|        "    // This is the function which gets called when the mpl process\n", | ||||
|        "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n", | ||||
|        "\n", | ||||
|        "    var id = msg.content.data.id;\n", | ||||
|        "    // Get hold of the div created by the display call when the Comm\n", | ||||
|        "    // socket was opened in Python.\n", | ||||
|        "    var element = $(\"#\" + id);\n", | ||||
|        "    var ws_proxy = comm_websocket_adapter(comm)\n", | ||||
|        "\n", | ||||
|        "    function ondownload(figure, format) {\n", | ||||
|        "        window.open(figure.imageObj.src);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var fig = new mpl.figure(id, ws_proxy,\n", | ||||
|        "                           ondownload,\n", | ||||
|        "                           element.get(0));\n", | ||||
|        "\n", | ||||
|        "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", | ||||
|        "    // web socket which is closed, not our websocket->open comm proxy.\n", | ||||
|        "    ws_proxy.onopen();\n", | ||||
|        "\n", | ||||
|        "    fig.parent_element = element.get(0);\n", | ||||
|        "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n", | ||||
|        "    if (!fig.cell_info) {\n", | ||||
|        "        console.error(\"Failed to find cell for figure\", id, fig);\n", | ||||
|        "        return;\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var output_index = fig.cell_info[2]\n", | ||||
|        "    var cell = fig.cell_info[0];\n", | ||||
|        "\n", | ||||
|        "};\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_close = function(fig, msg) {\n", | ||||
|        "    var width = fig.canvas.width/mpl.ratio\n", | ||||
|        "    fig.root.unbind('remove')\n", | ||||
|        "\n", | ||||
|        "    // Update the output cell to use the data from the current canvas.\n", | ||||
|        "    fig.push_to_output();\n", | ||||
|        "    var dataURL = fig.canvas.toDataURL();\n", | ||||
|        "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n", | ||||
|        "    // the notebook keyboard shortcuts fail.\n", | ||||
|        "    IPython.keyboard_manager.enable()\n", | ||||
|        "    $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n", | ||||
|        "    fig.close_ws(fig, msg);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.close_ws = function(fig, msg){\n", | ||||
|        "    fig.send_message('closing', msg);\n", | ||||
|        "    // fig.ws.close()\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", | ||||
|        "    // Turn the data on the canvas into data in the output cell.\n", | ||||
|        "    var width = this.canvas.width/mpl.ratio\n", | ||||
|        "    var dataURL = this.canvas.toDataURL();\n", | ||||
|        "    this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.updated_canvas_event = function() {\n", | ||||
|        "    // Tell IPython that the notebook contents must change.\n", | ||||
|        "    IPython.notebook.set_dirty(true);\n", | ||||
|        "    this.send_message(\"ack\", {});\n", | ||||
|        "    var fig = this;\n", | ||||
|        "    // Wait a second, then push the new image to the DOM so\n", | ||||
|        "    // that it is saved nicely (might be nice to debounce this).\n", | ||||
|        "    setTimeout(function () { fig.push_to_output() }, 1000);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_toolbar = function() {\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    var nav_element = $('<div/>');\n", | ||||
|        "    nav_element.attr('style', 'width: 100%');\n", | ||||
|        "    this.root.append(nav_element);\n", | ||||
|        "\n", | ||||
|        "    // Define a callback function for later on.\n", | ||||
|        "    function toolbar_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onclick(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "    function toolbar_mouse_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onmouseover(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    for(var toolbar_ind in mpl.toolbar_items){\n", | ||||
|        "        var name = mpl.toolbar_items[toolbar_ind][0];\n", | ||||
|        "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", | ||||
|        "        var image = mpl.toolbar_items[toolbar_ind][2];\n", | ||||
|        "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n", | ||||
|        "\n", | ||||
|        "        if (!name) { continue; };\n", | ||||
|        "\n", | ||||
|        "        var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n", | ||||
|        "        button.click(method_name, toolbar_event);\n", | ||||
|        "        button.mouseover(tooltip, toolbar_mouse_event);\n", | ||||
|        "        nav_element.append(button);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    // Add the status bar.\n", | ||||
|        "    var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n", | ||||
|        "    nav_element.append(status_bar);\n", | ||||
|        "    this.message = status_bar[0];\n", | ||||
|        "\n", | ||||
|        "    // Add the close button to the window.\n", | ||||
|        "    var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n", | ||||
|        "    var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n", | ||||
|        "    button.click(function (evt) { fig.handle_close(fig, {}); } );\n", | ||||
|        "    button.mouseover('Stop Interaction', toolbar_mouse_event);\n", | ||||
|        "    buttongrp.append(button);\n", | ||||
|        "    var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", | ||||
|        "    titlebar.prepend(buttongrp);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._root_extra_style = function(el){\n", | ||||
|        "    var fig = this\n", | ||||
|        "    el.on(\"remove\", function(){\n", | ||||
|        "\tfig.close_ws(fig, {});\n", | ||||
|        "    });\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._canvas_extra_style = function(el){\n", | ||||
|        "    // this is important to make the div 'focusable\n", | ||||
|        "    el.attr('tabindex', 0)\n", | ||||
|        "    // reach out to IPython and tell the keyboard manager to turn it's self\n", | ||||
|        "    // off when our div gets focus\n", | ||||
|        "\n", | ||||
|        "    // location in version 3\n", | ||||
|        "    if (IPython.notebook.keyboard_manager) {\n", | ||||
|        "        IPython.notebook.keyboard_manager.register_events(el);\n", | ||||
|        "    }\n", | ||||
|        "    else {\n", | ||||
|        "        // location in version 2\n", | ||||
|        "        IPython.keyboard_manager.register_events(el);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._key_event_extra = function(event, name) {\n", | ||||
|        "    var manager = IPython.notebook.keyboard_manager;\n", | ||||
|        "    if (!manager)\n", | ||||
|        "        manager = IPython.keyboard_manager;\n", | ||||
|        "\n", | ||||
|        "    // Check for shift+enter\n", | ||||
|        "    if (event.shiftKey && event.which == 13) {\n", | ||||
|        "        this.canvas_div.blur();\n", | ||||
|        "        // select the cell after this one\n", | ||||
|        "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", | ||||
|        "        IPython.notebook.select(index + 1);\n", | ||||
|        "    }\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_save = function(fig, msg) {\n", | ||||
|        "    fig.ondownload(fig, null);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.find_output_cell = function(html_output) {\n", | ||||
|        "    // Return the cell and output element which can be found *uniquely* in the notebook.\n", | ||||
|        "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", | ||||
|        "    // IPython event is triggered only after the cells have been serialised, which for\n", | ||||
|        "    // our purposes (turning an active figure into a static one), is too late.\n", | ||||
|        "    var cells = IPython.notebook.get_cells();\n", | ||||
|        "    var ncells = cells.length;\n", | ||||
|        "    for (var i=0; i<ncells; i++) {\n", | ||||
|        "        var cell = cells[i];\n", | ||||
|        "        if (cell.cell_type === 'code'){\n", | ||||
|        "            for (var j=0; j<cell.output_area.outputs.length; j++) {\n", | ||||
|        "                var data = cell.output_area.outputs[j];\n", | ||||
|        "                if (data.data) {\n", | ||||
|        "                    // IPython >= 3 moved mimebundle to data attribute of output\n", | ||||
|        "                    data = data.data;\n", | ||||
|        "                }\n", | ||||
|        "                if (data['text/html'] == html_output) {\n", | ||||
|        "                    return [cell, data, j];\n", | ||||
|        "                }\n", | ||||
|        "            }\n", | ||||
|        "        }\n", | ||||
|        "    }\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "// Register the function which deals with the matplotlib target/channel.\n", | ||||
|        "// The kernel may be null if the page has been refreshed.\n", | ||||
|        "if (IPython.notebook.kernel != null) {\n", | ||||
|        "    IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", | ||||
|        "}\n" | ||||
|       ], | ||||
|       "text/plain": [ | ||||
|        "<IPython.core.display.Javascript object>" | ||||
|       ] | ||||
|      }, | ||||
|      "metadata": {}, | ||||
|      "output_type": "display_data" | ||||
|     }, | ||||
|     { | ||||
|      "data": { | ||||
|       "text/html": [ | ||||
|        "<div id='215b26ff-80fe-45a3-bb52-9351ea1b2ebb'></div>" | ||||
|       ], | ||||
|       "text/plain": [ | ||||
|        "<IPython.core.display.HTML object>" | ||||
|       ] | ||||
|      }, | ||||
|      "metadata": {}, | ||||
|      "output_type": "display_data" | ||||
|     }, | ||||
|     { | ||||
|      "data": { | ||||
|       "text/plain": [ | ||||
|        "(0, 100)" | ||||
|       ] | ||||
|      }, | ||||
|      "execution_count": 131, | ||||
|      "metadata": {}, | ||||
|      "output_type": "execute_result" | ||||
|     } | ||||
|    ], | ||||
|    "source": [ | ||||
|     "def h(p,src,dest):\n", | ||||
|     "    c = (((src-dest)**2).sum())**0.5\n", | ||||
|     "    a = (((dest-p)**2).sum())**0.5\n", | ||||
|     "    b = (((src-p)**2).sum())**0.5\n", | ||||
|     "    ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n", | ||||
|     "    return a-b\n", | ||||
|     "S=np.array([10,10])\n", | ||||
|     "D=np.array([20,25])\n", | ||||
|     "\n", | ||||
|     "Rx=range(100)\n", | ||||
|     "Ry=range(100)\n", | ||||
|     "grid=np.zeros((len(Rx),len(Ry)))\n", | ||||
|     "for px,x in enumerate(Rx):\n", | ||||
|     "    for py,y in enumerate(Ry):\n", | ||||
|     "        grid[px,py]=h(np.array([x,y]),S,D)\n", | ||||
|     "imshow(grid,cmap='coolwarm_r',origin='lower')\n", | ||||
|     "colorbar()\n", | ||||
|     "\n", | ||||
|     "scatter(*S,color='green')\n", | ||||
|     "scatter(*D,color='red')\n", | ||||
|     "plt.xlim(0,100)\n", | ||||
|     "plt.ylim(0,100)" | ||||
|    ] | ||||
|   }, | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": 38, | ||||
|    "metadata": {}, | ||||
|    "outputs": [ | ||||
|     { | ||||
|      "name": "stdout", | ||||
|      "output_type": "stream", | ||||
|      "text": [ | ||||
|       "Object `np.magnitude` not found.\n" | ||||
|      ] | ||||
|     } | ||||
|    ], | ||||
|    "source": [ | ||||
|     "np." | ||||
|    ] | ||||
|   }, | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": null, | ||||
|    "metadata": {}, | ||||
|    "outputs": [], | ||||
|    "source": [] | ||||
|   } | ||||
|  ], | ||||
|  "metadata": { | ||||
|   "kernelspec": { | ||||
|    "display_name": "Python 3.8.1 64-bit ('anaconda': conda)", | ||||
|    "language": "python", | ||||
|    "name": "python38164bitanacondaconda2a51168e890d45bd836f654eb2ae46f7" | ||||
|   }, | ||||
|   "language_info": { | ||||
|    "codemirror_mode": { | ||||
|     "name": "ipython", | ||||
|     "version": 3 | ||||
|    }, | ||||
|    "file_extension": ".py", | ||||
|    "mimetype": "text/x-python", | ||||
|    "name": "python", | ||||
|    "nbconvert_exporter": "python", | ||||
|    "pygments_lexer": "ipython3", | ||||
|    "version": "3.8.1" | ||||
|   } | ||||
|  }, | ||||
|  "nbformat": 4, | ||||
|  "nbformat_minor": 4 | ||||
| } | ||||
							
								
								
									
										505
									
								
								.qt_for_python/uic/ed_lrr.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										505
									
								
								.qt_for_python/uic/ed_lrr.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,505 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| 
 | ||||
| ################################################################################ | ||||
| ## Form generated from reading UI file 'ed_lrr.ui' | ||||
| ## | ||||
| ## Created by: Qt User Interface Compiler version 5.15.2 | ||||
| ## | ||||
| ## WARNING! All changes made in this file will be lost when recompiling UI file! | ||||
| ################################################################################ | ||||
| 
 | ||||
| from PySide2.QtCore import * | ||||
| from PySide2.QtGui import * | ||||
| from PySide2.QtWidgets import * | ||||
| 
 | ||||
| 
 | ||||
| class Ui_ED_LRR(object): | ||||
|     def setupUi(self, ED_LRR): | ||||
|         if not ED_LRR.objectName(): | ||||
|             ED_LRR.setObjectName(u"ED_LRR") | ||||
|         ED_LRR.setEnabled(True) | ||||
|         ED_LRR.resize(577, 500) | ||||
|         sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(ED_LRR.sizePolicy().hasHeightForWidth()) | ||||
|         ED_LRR.setSizePolicy(sizePolicy) | ||||
|         ED_LRR.setMinimumSize(QSize(577, 500)) | ||||
|         ED_LRR.setMaximumSize(QSize(577, 500)) | ||||
|         ED_LRR.setStyleSheet(u"") | ||||
|         ED_LRR.setDocumentMode(False) | ||||
|         ED_LRR.setTabShape(QTabWidget.Rounded) | ||||
|         self.menu_act_quit = QAction(ED_LRR) | ||||
|         self.menu_act_quit.setObjectName(u"menu_act_quit") | ||||
|         self.actionA = QAction(ED_LRR) | ||||
|         self.actionA.setObjectName(u"actionA") | ||||
|         self.actionB = QAction(ED_LRR) | ||||
|         self.actionB.setObjectName(u"actionB") | ||||
|         self.centralwidget = QWidget(ED_LRR) | ||||
|         self.centralwidget.setObjectName(u"centralwidget") | ||||
|         sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) | ||||
|         sizePolicy1.setHorizontalStretch(0) | ||||
|         sizePolicy1.setVerticalStretch(0) | ||||
|         sizePolicy1.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) | ||||
|         self.centralwidget.setSizePolicy(sizePolicy1) | ||||
|         self.verticalLayout = QVBoxLayout(self.centralwidget) | ||||
|         self.verticalLayout.setObjectName(u"verticalLayout") | ||||
|         self.tabs = QTabWidget(self.centralwidget) | ||||
|         self.tabs.setObjectName(u"tabs") | ||||
|         self.tabs.setEnabled(True) | ||||
|         self.tabs.setAutoFillBackground(False) | ||||
|         self.tabs.setTabPosition(QTabWidget.North) | ||||
|         self.tabs.setTabShape(QTabWidget.Rounded) | ||||
|         self.tabs.setElideMode(Qt.ElideNone) | ||||
|         self.tabs.setTabsClosable(False) | ||||
|         self.tabs.setTabBarAutoHide(False) | ||||
|         self.tab_download = QWidget() | ||||
|         self.tab_download.setObjectName(u"tab_download") | ||||
|         self.formLayout = QFormLayout(self.tab_download) | ||||
|         self.formLayout.setObjectName(u"formLayout") | ||||
|         self.lbl_bodies_dl = QLabel(self.tab_download) | ||||
|         self.lbl_bodies_dl.setObjectName(u"lbl_bodies_dl") | ||||
| 
 | ||||
|         self.formLayout.setWidget(1, QFormLayout.LabelRole, self.lbl_bodies_dl) | ||||
| 
 | ||||
|         self.lbl_systems_dl = QLabel(self.tab_download) | ||||
|         self.lbl_systems_dl.setObjectName(u"lbl_systems_dl") | ||||
| 
 | ||||
|         self.formLayout.setWidget(3, QFormLayout.LabelRole, self.lbl_systems_dl) | ||||
| 
 | ||||
|         self.inp_bodies_dl = QComboBox(self.tab_download) | ||||
|         self.inp_bodies_dl.setObjectName(u"inp_bodies_dl") | ||||
|         self.inp_bodies_dl.setEditable(True) | ||||
|         self.inp_bodies_dl.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.formLayout.setWidget(1, QFormLayout.FieldRole, self.inp_bodies_dl) | ||||
| 
 | ||||
|         self.inp_systems_dl = QComboBox(self.tab_download) | ||||
|         self.inp_systems_dl.setObjectName(u"inp_systems_dl") | ||||
|         self.inp_systems_dl.setEditable(True) | ||||
|         self.inp_systems_dl.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.formLayout.setWidget(3, QFormLayout.FieldRole, self.inp_systems_dl) | ||||
| 
 | ||||
|         self.gridLayout = QGridLayout() | ||||
|         self.gridLayout.setObjectName(u"gridLayout") | ||||
|         self.inp_bodies_dest_dl = QComboBox(self.tab_download) | ||||
|         self.inp_bodies_dest_dl.setObjectName(u"inp_bodies_dest_dl") | ||||
|         sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) | ||||
|         sizePolicy2.setHorizontalStretch(0) | ||||
|         sizePolicy2.setVerticalStretch(0) | ||||
|         sizePolicy2.setHeightForWidth(self.inp_bodies_dest_dl.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_bodies_dest_dl.setSizePolicy(sizePolicy2) | ||||
|         self.inp_bodies_dest_dl.setEditable(False) | ||||
|         self.inp_bodies_dest_dl.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.gridLayout.addWidget(self.inp_bodies_dest_dl, 0, 0, 1, 1) | ||||
| 
 | ||||
|         self.btn_bodies_dest_browse_dl = QPushButton(self.tab_download) | ||||
|         self.btn_bodies_dest_browse_dl.setObjectName(u"btn_bodies_dest_browse_dl") | ||||
| 
 | ||||
|         self.gridLayout.addWidget(self.btn_bodies_dest_browse_dl, 0, 1, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout.setLayout(2, QFormLayout.FieldRole, self.gridLayout) | ||||
| 
 | ||||
|         self.gridLayout_2 = QGridLayout() | ||||
|         self.gridLayout_2.setObjectName(u"gridLayout_2") | ||||
|         self.btn_systems_dest_browse_dl = QPushButton(self.tab_download) | ||||
|         self.btn_systems_dest_browse_dl.setObjectName(u"btn_systems_dest_browse_dl") | ||||
| 
 | ||||
|         self.gridLayout_2.addWidget(self.btn_systems_dest_browse_dl, 0, 1, 1, 1) | ||||
| 
 | ||||
|         self.inp_systems_dest_dl = QComboBox(self.tab_download) | ||||
|         self.inp_systems_dest_dl.setObjectName(u"inp_systems_dest_dl") | ||||
|         sizePolicy2.setHeightForWidth(self.inp_systems_dest_dl.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_systems_dest_dl.setSizePolicy(sizePolicy2) | ||||
|         self.inp_systems_dest_dl.setEditable(False) | ||||
|         self.inp_systems_dest_dl.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.gridLayout_2.addWidget(self.inp_systems_dest_dl, 0, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout.setLayout(4, QFormLayout.FieldRole, self.gridLayout_2) | ||||
| 
 | ||||
|         self.btn_download = QPushButton(self.tab_download) | ||||
|         self.btn_download.setObjectName(u"btn_download") | ||||
| 
 | ||||
|         self.formLayout.setWidget(5, QFormLayout.LabelRole, self.btn_download) | ||||
| 
 | ||||
|         self.label = QLabel(self.tab_download) | ||||
|         self.label.setObjectName(u"label") | ||||
| 
 | ||||
|         self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label) | ||||
| 
 | ||||
|         self.label_2 = QLabel(self.tab_download) | ||||
|         self.label_2.setObjectName(u"label_2") | ||||
| 
 | ||||
|         self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_2) | ||||
| 
 | ||||
|         self.tabs.addTab(self.tab_download, "") | ||||
|         self.tab_preprocess = QWidget() | ||||
|         self.tab_preprocess.setObjectName(u"tab_preprocess") | ||||
|         self.formLayout_3 = QFormLayout(self.tab_preprocess) | ||||
|         self.formLayout_3.setObjectName(u"formLayout_3") | ||||
|         self.lbl_bodies_pp = QLabel(self.tab_preprocess) | ||||
|         self.lbl_bodies_pp.setObjectName(u"lbl_bodies_pp") | ||||
| 
 | ||||
|         self.formLayout_3.setWidget(0, QFormLayout.LabelRole, self.lbl_bodies_pp) | ||||
| 
 | ||||
|         self.gr_bodies_pp = QGridLayout() | ||||
|         self.gr_bodies_pp.setObjectName(u"gr_bodies_pp") | ||||
|         self.btn_bodies_browse_pp = QPushButton(self.tab_preprocess) | ||||
|         self.btn_bodies_browse_pp.setObjectName(u"btn_bodies_browse_pp") | ||||
| 
 | ||||
|         self.gr_bodies_pp.addWidget(self.btn_bodies_browse_pp, 0, 1, 1, 1) | ||||
| 
 | ||||
|         self.inp_bodies_pp = QComboBox(self.tab_preprocess) | ||||
|         self.inp_bodies_pp.setObjectName(u"inp_bodies_pp") | ||||
|         sizePolicy2.setHeightForWidth(self.inp_bodies_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_bodies_pp.setSizePolicy(sizePolicy2) | ||||
|         self.inp_bodies_pp.setEditable(False) | ||||
|         self.inp_bodies_pp.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.gr_bodies_pp.addWidget(self.inp_bodies_pp, 0, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_3.setLayout(0, QFormLayout.FieldRole, self.gr_bodies_pp) | ||||
| 
 | ||||
|         self.lbl_systems_pp = QLabel(self.tab_preprocess) | ||||
|         self.lbl_systems_pp.setObjectName(u"lbl_systems_pp") | ||||
| 
 | ||||
|         self.formLayout_3.setWidget(1, QFormLayout.LabelRole, self.lbl_systems_pp) | ||||
| 
 | ||||
|         self.gr_systems_pp = QGridLayout() | ||||
|         self.gr_systems_pp.setObjectName(u"gr_systems_pp") | ||||
|         self.btn_systems_browse_pp = QPushButton(self.tab_preprocess) | ||||
|         self.btn_systems_browse_pp.setObjectName(u"btn_systems_browse_pp") | ||||
| 
 | ||||
|         self.gr_systems_pp.addWidget(self.btn_systems_browse_pp, 0, 1, 1, 1) | ||||
| 
 | ||||
|         self.inp_systems_pp = QComboBox(self.tab_preprocess) | ||||
|         self.inp_systems_pp.setObjectName(u"inp_systems_pp") | ||||
|         sizePolicy2.setHeightForWidth(self.inp_systems_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_systems_pp.setSizePolicy(sizePolicy2) | ||||
|         self.inp_systems_pp.setEditable(False) | ||||
|         self.inp_systems_pp.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.gr_systems_pp.addWidget(self.inp_systems_pp, 0, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_3.setLayout(1, QFormLayout.FieldRole, self.gr_systems_pp) | ||||
| 
 | ||||
|         self.lbl_out_pp = QLabel(self.tab_preprocess) | ||||
|         self.lbl_out_pp.setObjectName(u"lbl_out_pp") | ||||
| 
 | ||||
|         self.formLayout_3.setWidget(2, QFormLayout.LabelRole, self.lbl_out_pp) | ||||
| 
 | ||||
|         self.gr_out_grid_pp = QGridLayout() | ||||
|         self.gr_out_grid_pp.setObjectName(u"gr_out_grid_pp") | ||||
|         self.btn_out_browse_pp = QPushButton(self.tab_preprocess) | ||||
|         self.btn_out_browse_pp.setObjectName(u"btn_out_browse_pp") | ||||
|         sizePolicy3 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) | ||||
|         sizePolicy3.setHorizontalStretch(0) | ||||
|         sizePolicy3.setVerticalStretch(0) | ||||
|         sizePolicy3.setHeightForWidth(self.btn_out_browse_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.btn_out_browse_pp.setSizePolicy(sizePolicy3) | ||||
| 
 | ||||
|         self.gr_out_grid_pp.addWidget(self.btn_out_browse_pp, 0, 1, 1, 1) | ||||
| 
 | ||||
|         self.inp_out_pp = QComboBox(self.tab_preprocess) | ||||
|         self.inp_out_pp.setObjectName(u"inp_out_pp") | ||||
|         sizePolicy2.setHeightForWidth(self.inp_out_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_out_pp.setSizePolicy(sizePolicy2) | ||||
|         self.inp_out_pp.setEditable(False) | ||||
|         self.inp_out_pp.setInsertPolicy(QComboBox.InsertAtTop) | ||||
| 
 | ||||
|         self.gr_out_grid_pp.addWidget(self.inp_out_pp, 0, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_3.setLayout(2, QFormLayout.FieldRole, self.gr_out_grid_pp) | ||||
| 
 | ||||
|         self.btn_preprocess = QPushButton(self.tab_preprocess) | ||||
|         self.btn_preprocess.setObjectName(u"btn_preprocess") | ||||
| 
 | ||||
|         self.formLayout_3.setWidget(3, QFormLayout.LabelRole, self.btn_preprocess) | ||||
| 
 | ||||
|         self.tabs.addTab(self.tab_preprocess, "") | ||||
|         self.tab_route = QWidget() | ||||
|         self.tab_route.setObjectName(u"tab_route") | ||||
|         self.formLayout_2 = QFormLayout(self.tab_route) | ||||
|         self.formLayout_2.setObjectName(u"formLayout_2") | ||||
|         self.lbl_sys_lst = QLabel(self.tab_route) | ||||
|         self.lbl_sys_lst.setObjectName(u"lbl_sys_lst") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.lbl_sys_lst) | ||||
| 
 | ||||
|         self.gr_sys = QGridLayout() | ||||
|         self.gr_sys.setObjectName(u"gr_sys") | ||||
|         self.btn_sys_lst_browse = QPushButton(self.tab_route) | ||||
|         self.btn_sys_lst_browse.setObjectName(u"btn_sys_lst_browse") | ||||
| 
 | ||||
|         self.gr_sys.addWidget(self.btn_sys_lst_browse, 0, 1, 1, 1) | ||||
| 
 | ||||
|         self.inp_sys_lst = QComboBox(self.tab_route) | ||||
|         self.inp_sys_lst.setObjectName(u"inp_sys_lst") | ||||
|         sizePolicy2.setHeightForWidth(self.inp_sys_lst.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_sys_lst.setSizePolicy(sizePolicy2) | ||||
|         self.inp_sys_lst.setEditable(False) | ||||
|         self.inp_sys_lst.setInsertPolicy(QComboBox.InsertAtTop) | ||||
|         self.inp_sys_lst.setFrame(True) | ||||
|         self.inp_sys_lst.setModelColumn(0) | ||||
| 
 | ||||
|         self.gr_sys.addWidget(self.inp_sys_lst, 0, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_2.setLayout(0, QFormLayout.FieldRole, self.gr_sys) | ||||
| 
 | ||||
|         self.btn_add = QPushButton(self.tab_route) | ||||
|         self.btn_add.setObjectName(u"btn_add") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.btn_add) | ||||
| 
 | ||||
|         self.inp_sys = QLineEdit(self.tab_route) | ||||
|         self.inp_sys.setObjectName(u"inp_sys") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(1, QFormLayout.FieldRole, self.inp_sys) | ||||
| 
 | ||||
|         self.btn_rm = QPushButton(self.tab_route) | ||||
|         self.btn_rm.setObjectName(u"btn_rm") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(3, QFormLayout.LabelRole, self.btn_rm) | ||||
| 
 | ||||
|         self.gr_mode = QGridLayout() | ||||
|         self.gr_mode.setObjectName(u"gr_mode") | ||||
|         self.rd_comp = QRadioButton(self.tab_route) | ||||
|         self.rd_comp.setObjectName(u"rd_comp") | ||||
|         self.rd_comp.setChecked(True) | ||||
| 
 | ||||
|         self.gr_mode.addWidget(self.rd_comp, 0, 1, 1, 1) | ||||
| 
 | ||||
|         self.rd_precomp = QRadioButton(self.tab_route) | ||||
|         self.rd_precomp.setObjectName(u"rd_precomp") | ||||
| 
 | ||||
|         self.gr_mode.addWidget(self.rd_precomp, 0, 2, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_2.setLayout(3, QFormLayout.FieldRole, self.gr_mode) | ||||
| 
 | ||||
|         self.chk_permute = QCheckBox(self.tab_route) | ||||
|         self.chk_permute.setObjectName(u"chk_permute") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(4, QFormLayout.LabelRole, self.chk_permute) | ||||
| 
 | ||||
|         self.gridLayout_4 = QGridLayout() | ||||
|         self.gridLayout_4.setObjectName(u"gridLayout_4") | ||||
|         self.chk_permute_keep_last = QCheckBox(self.tab_route) | ||||
|         self.chk_permute_keep_last.setObjectName(u"chk_permute_keep_last") | ||||
| 
 | ||||
|         self.gridLayout_4.addWidget(self.chk_permute_keep_last, 0, 3, 1, 1) | ||||
| 
 | ||||
|         self.chk_permute_keep_first = QCheckBox(self.tab_route) | ||||
|         self.chk_permute_keep_first.setObjectName(u"chk_permute_keep_first") | ||||
|         sizePolicy3.setHeightForWidth(self.chk_permute_keep_first.sizePolicy().hasHeightForWidth()) | ||||
|         self.chk_permute_keep_first.setSizePolicy(sizePolicy3) | ||||
|         self.chk_permute_keep_first.setTristate(False) | ||||
| 
 | ||||
|         self.gridLayout_4.addWidget(self.chk_permute_keep_first, 0, 2, 1, 1) | ||||
| 
 | ||||
|         self.lbl_keep = QLabel(self.tab_route) | ||||
|         self.lbl_keep.setObjectName(u"lbl_keep") | ||||
|         sizePolicy1.setHeightForWidth(self.lbl_keep.sizePolicy().hasHeightForWidth()) | ||||
|         self.lbl_keep.setSizePolicy(sizePolicy1) | ||||
| 
 | ||||
|         self.gridLayout_4.addWidget(self.lbl_keep, 0, 1, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_2.setLayout(4, QFormLayout.FieldRole, self.gridLayout_4) | ||||
| 
 | ||||
|         self.lst_sys = QTreeWidget(self.tab_route) | ||||
|         __qtreewidgetitem = QTreeWidgetItem() | ||||
|         __qtreewidgetitem.setText(0, u"Name"); | ||||
|         self.lst_sys.setHeaderItem(__qtreewidgetitem) | ||||
|         self.lst_sys.setObjectName(u"lst_sys") | ||||
|         sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) | ||||
|         sizePolicy4.setHorizontalStretch(0) | ||||
|         sizePolicy4.setVerticalStretch(0) | ||||
|         sizePolicy4.setHeightForWidth(self.lst_sys.sizePolicy().hasHeightForWidth()) | ||||
|         self.lst_sys.setSizePolicy(sizePolicy4) | ||||
|         self.lst_sys.setMinimumSize(QSize(0, 0)) | ||||
|         self.lst_sys.setDragEnabled(True) | ||||
|         self.lst_sys.setDragDropOverwriteMode(False) | ||||
|         self.lst_sys.setDragDropMode(QAbstractItemView.InternalMove) | ||||
|         self.lst_sys.setDefaultDropAction(Qt.MoveAction) | ||||
|         self.lst_sys.setAlternatingRowColors(True) | ||||
|         self.lst_sys.setSelectionMode(QAbstractItemView.ExtendedSelection) | ||||
|         self.lst_sys.setSelectionBehavior(QAbstractItemView.SelectRows) | ||||
|         self.lst_sys.setHeaderHidden(True) | ||||
|         self.lst_sys.header().setVisible(False) | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(7, QFormLayout.SpanningRole, self.lst_sys) | ||||
| 
 | ||||
|         self.lbl_range = QLabel(self.tab_route) | ||||
|         self.lbl_range.setObjectName(u"lbl_range") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(8, QFormLayout.LabelRole, self.lbl_range) | ||||
| 
 | ||||
|         self.sb_range = QDoubleSpinBox(self.tab_route) | ||||
|         self.sb_range.setObjectName(u"sb_range") | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(8, QFormLayout.FieldRole, self.sb_range) | ||||
| 
 | ||||
|         self.gr_opts = QGridLayout() | ||||
|         self.gr_opts.setObjectName(u"gr_opts") | ||||
|         self.cmb_mode = QComboBox(self.tab_route) | ||||
|         self.cmb_mode.addItem("") | ||||
|         self.cmb_mode.addItem("") | ||||
|         self.cmb_mode.addItem("") | ||||
|         self.cmb_mode.setObjectName(u"cmb_mode") | ||||
| 
 | ||||
|         self.gr_opts.addWidget(self.cmb_mode, 0, 2, 1, 1) | ||||
| 
 | ||||
|         self.lbl_greedyness = QLabel(self.tab_route) | ||||
|         self.lbl_greedyness.setObjectName(u"lbl_greedyness") | ||||
|         self.lbl_greedyness.setEnabled(True) | ||||
| 
 | ||||
|         self.gr_opts.addWidget(self.lbl_greedyness, 1, 1, 1, 1) | ||||
| 
 | ||||
|         self.chk_primary = QCheckBox(self.tab_route) | ||||
|         self.chk_primary.setObjectName(u"chk_primary") | ||||
| 
 | ||||
|         self.gr_opts.addWidget(self.chk_primary, 0, 3, 1, 1) | ||||
| 
 | ||||
|         self.sld_greedyness = QSlider(self.tab_route) | ||||
|         self.sld_greedyness.setObjectName(u"sld_greedyness") | ||||
|         self.sld_greedyness.setMaximum(100) | ||||
|         self.sld_greedyness.setPageStep(10) | ||||
|         self.sld_greedyness.setValue(50) | ||||
|         self.sld_greedyness.setOrientation(Qt.Horizontal) | ||||
|         self.sld_greedyness.setTickPosition(QSlider.TicksBelow) | ||||
|         self.sld_greedyness.setTickInterval(10) | ||||
| 
 | ||||
|         self.gr_opts.addWidget(self.sld_greedyness, 1, 2, 1, 2) | ||||
| 
 | ||||
|         self.lbl_mode = QLabel(self.tab_route) | ||||
|         self.lbl_mode.setObjectName(u"lbl_mode") | ||||
| 
 | ||||
|         self.gr_opts.addWidget(self.lbl_mode, 0, 1, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.formLayout_2.setLayout(9, QFormLayout.SpanningRole, self.gr_opts) | ||||
| 
 | ||||
|         self.btn_go = QPushButton(self.tab_route) | ||||
|         self.btn_go.setObjectName(u"btn_go") | ||||
|         self.btn_go.setFlat(False) | ||||
| 
 | ||||
|         self.formLayout_2.setWidget(10, QFormLayout.LabelRole, self.btn_go) | ||||
| 
 | ||||
|         self.tabs.addTab(self.tab_route, "") | ||||
|         self.tab_log = QWidget() | ||||
|         self.tab_log.setObjectName(u"tab_log") | ||||
|         self.gridLayout_3 = QGridLayout(self.tab_log) | ||||
|         self.gridLayout_3.setObjectName(u"gridLayout_3") | ||||
|         self.txt_log = QTextEdit(self.tab_log) | ||||
|         self.txt_log.setObjectName(u"txt_log") | ||||
|         self.txt_log.setEnabled(True) | ||||
|         self.txt_log.setFrameShadow(QFrame.Sunken) | ||||
|         self.txt_log.setLineWidth(1) | ||||
|         self.txt_log.setReadOnly(True) | ||||
|         self.txt_log.setAcceptRichText(False) | ||||
| 
 | ||||
|         self.gridLayout_3.addWidget(self.txt_log, 0, 0, 1, 1) | ||||
| 
 | ||||
|         self.tabs.addTab(self.tab_log, "") | ||||
| 
 | ||||
|         self.verticalLayout.addWidget(self.tabs) | ||||
| 
 | ||||
|         ED_LRR.setCentralWidget(self.centralwidget) | ||||
|         self.menu = QMenuBar(ED_LRR) | ||||
|         self.menu.setObjectName(u"menu") | ||||
|         self.menu.setGeometry(QRect(0, 0, 577, 21)) | ||||
|         self.menu_file = QMenu(self.menu) | ||||
|         self.menu_file.setObjectName(u"menu_file") | ||||
|         self.menuWindow = QMenu(self.menu) | ||||
|         self.menuWindow.setObjectName(u"menuWindow") | ||||
|         self.menuStyle = QMenu(self.menuWindow) | ||||
|         self.menuStyle.setObjectName(u"menuStyle") | ||||
|         ED_LRR.setMenuBar(self.menu) | ||||
|         self.bar_status = QStatusBar(ED_LRR) | ||||
|         self.bar_status.setObjectName(u"bar_status") | ||||
|         ED_LRR.setStatusBar(self.bar_status) | ||||
|         QWidget.setTabOrder(self.rd_comp, self.cmb_mode) | ||||
|         QWidget.setTabOrder(self.cmb_mode, self.chk_primary) | ||||
|         QWidget.setTabOrder(self.chk_primary, self.sld_greedyness) | ||||
|         QWidget.setTabOrder(self.sld_greedyness, self.rd_precomp) | ||||
| 
 | ||||
|         self.menu.addAction(self.menu_file.menuAction()) | ||||
|         self.menu.addAction(self.menuWindow.menuAction()) | ||||
|         self.menu_file.addAction(self.menu_act_quit) | ||||
|         self.menuWindow.addAction(self.menuStyle.menuAction()) | ||||
| 
 | ||||
|         self.retranslateUi(ED_LRR) | ||||
|         self.menu_act_quit.triggered.connect(ED_LRR.close) | ||||
| 
 | ||||
|         self.tabs.setCurrentIndex(2) | ||||
| 
 | ||||
| 
 | ||||
|         QMetaObject.connectSlotsByName(ED_LRR) | ||||
|     # setupUi | ||||
| 
 | ||||
|     def retranslateUi(self, ED_LRR): | ||||
|         ED_LRR.setWindowTitle(QCoreApplication.translate("ED_LRR", u"Elite: Dangerous Long Range Route Plotter", None)) | ||||
|         self.menu_act_quit.setText(QCoreApplication.translate("ED_LRR", u"Quit", None)) | ||||
| #if QT_CONFIG(shortcut) | ||||
|         self.menu_act_quit.setShortcut(QCoreApplication.translate("ED_LRR", u"Ctrl+Q", None)) | ||||
| #endif // QT_CONFIG(shortcut) | ||||
|         self.actionA.setText(QCoreApplication.translate("ED_LRR", u"A", None)) | ||||
|         self.actionB.setText(QCoreApplication.translate("ED_LRR", u"B", None)) | ||||
|         self.lbl_bodies_dl.setText(QCoreApplication.translate("ED_LRR", u"bodies.json", None)) | ||||
|         self.lbl_systems_dl.setText(QCoreApplication.translate("ED_LRR", u"systemsWithCoordinates.json", None)) | ||||
|         self.inp_bodies_dl.setCurrentText(QCoreApplication.translate("ED_LRR", u"https://www.edsm.net/dump/bodies.json", None)) | ||||
|         self.inp_systems_dl.setCurrentText(QCoreApplication.translate("ED_LRR", u"https://www.edsm.net/dump/systemsWithCoordinates.json", None)) | ||||
|         self.btn_bodies_dest_browse_dl.setText(QCoreApplication.translate("ED_LRR", u"...", None)) | ||||
|         self.btn_systems_dest_browse_dl.setText(QCoreApplication.translate("ED_LRR", u"...", None)) | ||||
|         self.btn_download.setText(QCoreApplication.translate("ED_LRR", u"Download", None)) | ||||
|         self.label.setText(QCoreApplication.translate("ED_LRR", u"Download path", None)) | ||||
|         self.label_2.setText(QCoreApplication.translate("ED_LRR", u"Download path", None)) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_download), QCoreApplication.translate("ED_LRR", u"Download", None)) | ||||
|         self.lbl_bodies_pp.setText(QCoreApplication.translate("ED_LRR", u"bodies.json", None)) | ||||
|         self.btn_bodies_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None)) | ||||
|         self.lbl_systems_pp.setText(QCoreApplication.translate("ED_LRR", u"systemsWithCoordinates.json", None)) | ||||
|         self.btn_systems_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None)) | ||||
|         self.lbl_out_pp.setText(QCoreApplication.translate("ED_LRR", u"Output", None)) | ||||
|         self.btn_out_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None)) | ||||
|         self.btn_preprocess.setText(QCoreApplication.translate("ED_LRR", u"Preprocess", None)) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_preprocess), QCoreApplication.translate("ED_LRR", u"Preprocess", None)) | ||||
|         self.lbl_sys_lst.setText(QCoreApplication.translate("ED_LRR", u"System List", None)) | ||||
|         self.btn_sys_lst_browse.setText(QCoreApplication.translate("ED_LRR", u"...", None)) | ||||
|         self.btn_add.setText(QCoreApplication.translate("ED_LRR", u"Add", None)) | ||||
|         self.inp_sys.setPlaceholderText(QCoreApplication.translate("ED_LRR", u"System Name", None)) | ||||
|         self.btn_rm.setText(QCoreApplication.translate("ED_LRR", u"Remove", None)) | ||||
|         self.rd_comp.setText(QCoreApplication.translate("ED_LRR", u"Compute Route", None)) | ||||
|         self.rd_precomp.setText(QCoreApplication.translate("ED_LRR", u"Precompute Graph", None)) | ||||
|         self.chk_permute.setText(QCoreApplication.translate("ED_LRR", u"Permute", None)) | ||||
|         self.chk_permute_keep_last.setText(QCoreApplication.translate("ED_LRR", u"Last", None)) | ||||
|         self.chk_permute_keep_first.setText(QCoreApplication.translate("ED_LRR", u"First", None)) | ||||
|         self.lbl_keep.setText(QCoreApplication.translate("ED_LRR", u"Keep Endpoints:", None)) | ||||
|         ___qtreewidgetitem = self.lst_sys.headerItem() | ||||
|         ___qtreewidgetitem.setText(1, QCoreApplication.translate("ED_LRR", u"Type", None)); | ||||
|         self.lbl_range.setText(QCoreApplication.translate("ED_LRR", u"Jump Range (Ly)", None)) | ||||
|         self.cmb_mode.setItemText(0, QCoreApplication.translate("ED_LRR", u"Breadth-First Search", None)) | ||||
|         self.cmb_mode.setItemText(1, QCoreApplication.translate("ED_LRR", u"Greedy-Search", None)) | ||||
|         self.cmb_mode.setItemText(2, QCoreApplication.translate("ED_LRR", u"A*-Search", None)) | ||||
| 
 | ||||
|         self.cmb_mode.setCurrentText(QCoreApplication.translate("ED_LRR", u"Breadth-First Search", None)) | ||||
|         self.lbl_greedyness.setText(QCoreApplication.translate("ED_LRR", u"Greedyness Factor", None)) | ||||
|         self.chk_primary.setText(QCoreApplication.translate("ED_LRR", u"Primary Stars Only", None)) | ||||
|         self.lbl_mode.setText(QCoreApplication.translate("ED_LRR", u"Mode", None)) | ||||
|         self.btn_go.setText(QCoreApplication.translate("ED_LRR", u"GO!", None)) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_route), QCoreApplication.translate("ED_LRR", u"Route", None)) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_log), QCoreApplication.translate("ED_LRR", u"Log", None)) | ||||
|         self.menu_file.setTitle(QCoreApplication.translate("ED_LRR", u"File", None)) | ||||
|         self.menuWindow.setTitle(QCoreApplication.translate("ED_LRR", u"Window", None)) | ||||
|         self.menuStyle.setTitle(QCoreApplication.translate("ED_LRR", u"Style", None)) | ||||
|     # retranslateUi | ||||
| 
 | ||||
							
								
								
									
										79
									
								
								.qt_for_python/uic/widget_route.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								.qt_for_python/uic/widget_route.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| 
 | ||||
| ################################################################################ | ||||
| ## Form generated from reading UI file 'widget_route.ui' | ||||
| ## | ||||
| ## Created by: Qt User Interface Compiler version 5.15.2 | ||||
| ## | ||||
| ## WARNING! All changes made in this file will be lost when recompiling UI file! | ||||
| ################################################################################ | ||||
| 
 | ||||
| from PySide2.QtCore import * | ||||
| from PySide2.QtGui import * | ||||
| from PySide2.QtWidgets import * | ||||
| 
 | ||||
| 
 | ||||
| class Ui_diag_route(object): | ||||
|     def setupUi(self, diag_route): | ||||
|         if not diag_route.objectName(): | ||||
|             diag_route.setObjectName(u"diag_route") | ||||
|         diag_route.setWindowModality(Qt.WindowModal) | ||||
|         diag_route.resize(430, 300) | ||||
|         self.layout_main = QGridLayout(diag_route) | ||||
|         self.layout_main.setObjectName(u"layout_main") | ||||
|         self.layout_top = QGridLayout() | ||||
|         self.layout_top.setObjectName(u"layout_top") | ||||
|         self.lst_route = QTreeWidget(diag_route) | ||||
|         self.lst_route.setObjectName(u"lst_route") | ||||
|         self.lst_route.setProperty("showDropIndicator", False) | ||||
|         self.lst_route.setDefaultDropAction(Qt.IgnoreAction) | ||||
|         self.lst_route.setAlternatingRowColors(True) | ||||
|         self.lst_route.setSelectionMode(QAbstractItemView.NoSelection) | ||||
|         self.lst_route.setVerticalScrollMode(QAbstractItemView.ScrollPerItem) | ||||
|         self.lst_route.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) | ||||
|         self.lst_route.setItemsExpandable(False) | ||||
|         self.lst_route.setAllColumnsShowFocus(False) | ||||
| 
 | ||||
|         self.layout_top.addWidget(self.lst_route, 0, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.layout_main.addLayout(self.layout_top, 0, 0, 1, 1) | ||||
| 
 | ||||
|         self.layout_bottom = QGridLayout() | ||||
|         self.layout_bottom.setObjectName(u"layout_bottom") | ||||
|         self.chk_copy = QCheckBox(diag_route) | ||||
|         self.chk_copy.setObjectName(u"chk_copy") | ||||
| 
 | ||||
|         self.layout_bottom.addWidget(self.chk_copy, 1, 0, 1, 1) | ||||
| 
 | ||||
|         self.btn_close = QPushButton(diag_route) | ||||
|         self.btn_close.setObjectName(u"btn_close") | ||||
| 
 | ||||
|         self.layout_bottom.addWidget(self.btn_close, 1, 2, 1, 1) | ||||
| 
 | ||||
|         self.btn_export = QPushButton(diag_route) | ||||
|         self.btn_export.setObjectName(u"btn_export") | ||||
| 
 | ||||
|         self.layout_bottom.addWidget(self.btn_export, 1, 1, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.layout_main.addLayout(self.layout_bottom, 1, 0, 1, 1) | ||||
| 
 | ||||
| 
 | ||||
|         self.retranslateUi(diag_route) | ||||
| 
 | ||||
|         QMetaObject.connectSlotsByName(diag_route) | ||||
|     # setupUi | ||||
| 
 | ||||
|     def retranslateUi(self, diag_route): | ||||
|         diag_route.setWindowTitle(QCoreApplication.translate("diag_route", u"Route", None)) | ||||
|         ___qtreewidgetitem = self.lst_route.headerItem() | ||||
|         ___qtreewidgetitem.setText(3, QCoreApplication.translate("diag_route", u"Distance (Ls)", None)); | ||||
|         ___qtreewidgetitem.setText(2, QCoreApplication.translate("diag_route", u"Body", None)); | ||||
|         ___qtreewidgetitem.setText(1, QCoreApplication.translate("diag_route", u"System", None)); | ||||
|         ___qtreewidgetitem.setText(0, QCoreApplication.translate("diag_route", u"Num", None)); | ||||
|         self.chk_copy.setText(QCoreApplication.translate("diag_route", u"Auto-copy next hop to clipboard", None)) | ||||
|         self.btn_close.setText(QCoreApplication.translate("diag_route", u"Close", None)) | ||||
|         self.btn_export.setText(QCoreApplication.translate("diag_route", u"Export", None)) | ||||
|     # retranslateUi | ||||
| 
 | ||||
							
								
								
									
										43
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
										
									
									
									
								
							|  | @ -108,46 +108,3 @@ $$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \ | |||
| $$dist = \frac{- B_{g} \cdot m_{fuel} - B_{g} \cdot m_{ship} + boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$ | ||||
| 
 | ||||
| $$e_{fuel} = \frac{l \cdot \left(\frac{10.0^{- \frac{3.0}{p}} \cdot \left(B_{g} + dist\right) \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}}{\min\left(f_{max}, m_{fuel}\right)}$$ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| # TODO | ||||
| 
 | ||||
| ## Routing | ||||
| 
 | ||||
| - Implement Neutron Mode | ||||
|   - Filter for neutron stars, plot route, then plot "fine" router between waypoints | ||||
|     - What Jump-Range to use for neutron route? `max_range*4`? | ||||
| - Implement Bidir BFS | ||||
| - Optimized All-Pairs BFS for graph precomputation | ||||
| - Take fuel consumption into account (WIP) | ||||
|   - Guardian Booster support (Done?) | ||||
|   - Economic routing | ||||
| - Custom weights and filtering for routing | ||||
| 
 | ||||
| ## GUI | ||||
| 
 | ||||
| - Implement estimate time to completion display for route computation and preprocessing (Done?) | ||||
| - Export route as: | ||||
|   - JSON | ||||
|   - HTML (WIP) | ||||
|   - CSV | ||||
|   - SVG | ||||
| 
 | ||||
| ## Installer | ||||
| 
 | ||||
| - Update PATH from installer | ||||
| 
 | ||||
| ## Preprocessing | ||||
| 
 | ||||
| - Build index over `systemsWithCoordinates.json` instead of loading it into RAM (reuse modified `LineCache` from `router.rs`) | ||||
| - Finish `galaxy.jsonl` preprocessor | ||||
| - Implement Python interface to preprocessor | ||||
| 
 | ||||
| ## Misc | ||||
| 
 | ||||
| - Luigi based Task queue for distributed routing | ||||
| - Full route tree computation | ||||
|   - overlap elimination | ||||
|  |  | |||
							
								
								
									
										49
									
								
								TODO.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								TODO.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| 
 | ||||
| # TODO | ||||
| 
 | ||||
| ## API | ||||
| 
 | ||||
| - expose multiroute TSP optimizer | ||||
| 
 | ||||
| ## Routing | ||||
| 
 | ||||
| - beam stack search, queue for each depth level, ordered by distance to goal node (min first) | ||||
| - GraphSearch trait | ||||
| - Implement Neutron Mode | ||||
|   - Filter for neutron stars, plot coarse route, then plot exact router between waypoints | ||||
|     - What Jump-Range to use for neutron route? `max_range*4`? | ||||
|     - furthest inside jump range, otherwise closest? | ||||
| - Implement Bidirectional BFS | ||||
| - Optimized All-Pairs BFS for graph precomputation | ||||
| - Take fuel consumption into account (WIP) partially implemented | ||||
|   - Guardian Booster support (Done?) | ||||
|   - Economic routing (minimal fuel, dijkstra) | ||||
|     - implemented, needs to be properly exposed | ||||
| - Custom weights and filtering for routing [evalexpr](https://docs.rs/evalexpr/) | ||||
|   - pathfinding weighted by $(goal-pos)\cdot(goal-start)$ | ||||
| - use vecmat crate for vector distance etc | ||||
| 
 | ||||
| 
 | ||||
| ## GUI | ||||
| 
 | ||||
| - Imgui? | ||||
| - Implement estimate time to completion display for route computation and preprocessing (Done?) | ||||
| - Export route as: | ||||
|   - JSON | ||||
|   - HTML (WIP) | ||||
|   - CSV | ||||
|   - SVG | ||||
| 
 | ||||
| ## Installer | ||||
| 
 | ||||
| - Update PATH from installer | ||||
| 
 | ||||
| ## Preprocessing | ||||
| 
 | ||||
| - Build index over `systemsWithCoordinates.json` instead of loading it into RAM (reuse modified `LineCache` from `router.rs`) | ||||
| - Finish `galaxy.jsonl` preprocessor (Done?) | ||||
| - Implement Python interface to preprocessor | ||||
| 
 | ||||
| ## Misc | ||||
| 
 | ||||
| - Luigi/Celery/Dask based Task queue for distributed routing | ||||
							
								
								
									
										6
									
								
								beam_stack_impl.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								beam_stack_impl.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| def pruntLayer(l,w): | ||||
|     Keep=sorted(Open[l])[:w] # best `w` node from `Open[l]` | ||||
|     Prune = [n for n in Open[l] if n not in Keep] | ||||
|     beam_stack.top().f_max=min([f(n) for n in Prune]) | ||||
|     for n in Prune: | ||||
|         Open[l].remove(n) | ||||
							
								
								
									
										84
									
								
								benchmark_sweep.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								benchmark_sweep.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | |||
| from tqdm import tqdm | ||||
| import time | ||||
| import json | ||||
| import os | ||||
| import statistics | ||||
| 
 | ||||
| def stats(values): | ||||
|     ret={ | ||||
|         "max":max(values), | ||||
|         "min":min(values), | ||||
|         "mean":statistics.mean(values), | ||||
|         "range":abs(max(values)-min(values)), | ||||
|         "sum":sum(values) | ||||
|     } | ||||
|     if len(values)>1: | ||||
|         ret["stdev"]=statistics.stdev(values) | ||||
|     else: | ||||
|         ret["stdev"]=0 | ||||
|     return ret | ||||
| 
 | ||||
| last_seen=float('inf') | ||||
| pbar=None | ||||
| def callback(state): | ||||
|     try: | ||||
|         global pbar,last_seen | ||||
|         if state['n_seen']<last_seen: | ||||
|             if pbar: | ||||
|                 pbar.close() | ||||
|             pbar=tqdm(total=state['d_total'],unit="Ly",unit_scale=True) | ||||
|         last_seen=state['n_seen'] | ||||
|         pbar.n=round(state['d_total']-state['d_rem'],2) | ||||
|         pbar.set_description("[J:{depth} | Q:{queue_size} | D:{d_rem:.2f} Ly | S:{n_seen} ({prc_seen:.2f}%)] {system}".format(**state)) | ||||
|         pbar.update(0) | ||||
|     except Exception as e: | ||||
|         print(e) | ||||
| 
 | ||||
| if not 'callback' in vars(): | ||||
|     callback=None | ||||
| import _ed_lrr | ||||
| callback=None | ||||
| r=_ed_lrr.PyRouter(callback) | ||||
| r.load("stars.csv") | ||||
| res=[] | ||||
| done=set() | ||||
| if os.path.isfile("res.json"): | ||||
|     with open("res.json","r") as fh: | ||||
|         try: | ||||
|             res=json.load(fh) | ||||
|         except: | ||||
|             pass | ||||
| for v in res: | ||||
|     done.add((v[0],v[1])) | ||||
| systems=r.resolve_systems("Sol","Colonia") | ||||
| min_dt_total=60*10 | ||||
| print("Warming up...") | ||||
| r.route([systems['Sol'],systems['Sol']],48.0,0.0,0.0,0) # warmup | ||||
| print("Done") | ||||
| 
 | ||||
| for g in [0.0, 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]: | ||||
|     for w in [0.0, 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]: | ||||
|         if (g,w) in done: | ||||
|             continue | ||||
|         dt=[] | ||||
|         lr=[] | ||||
|         l=0 | ||||
|         while sum(dt)<min_dt_total: | ||||
|             l+=1 | ||||
|             print({'g':g,'w':w,'l':l}) | ||||
|             t=time.time() | ||||
|             route=r.route([systems['Sol'],systems['Colonia']],48.0,g,w,0) | ||||
|             dt.append(time.time()-t) | ||||
|             lr.append(len(route)) | ||||
|             print(stats(dt)) | ||||
|         dt=sum(dt)/len(dt) | ||||
|         res.append((g,w,lr,dt)) | ||||
|         with open("res.json","w") as of: | ||||
|             json.dump(res,of) | ||||
| 
 | ||||
| # for n in range(21): | ||||
| #     grid=r.get_grid(1<<n) | ||||
| #     max_v=max(grid.items(),key=lambda v:v[1]) | ||||
| #     mean_v=sum(grid.values())/len(grid) | ||||
| #     print(1<<n,len(grid),mean_v,max_v) | ||||
| 
 | ||||
|  | @ -3,9 +3,8 @@ from celery import Celery | |||
| import _ed_lrr | ||||
| import os | ||||
| 
 | ||||
| os.environ.setdefault("CELERY_CONFIG_MODULE", "celeryconfig") | ||||
| app = Celery("ed_lrr") | ||||
| app.config_from_envvar("CELERY_CONFIG_MODULE") | ||||
| app.config_from_object(__import__("celeryconfig")) | ||||
| 
 | ||||
| 
 | ||||
| @app.task(bind=True) | ||||
|  | @ -1,48 +0,0 @@ | |||
| MD = $(wildcard src/*.md) | ||||
| DOTS = $(wildcard src/*.dot) | ||||
| ASYS = $(wildcard src/*.asy) | ||||
| PYS = $(wildcard src/img_*.py) | ||||
| PDFS = $(MD:src/%.md=out/%.pdf) | ||||
| 
 | ||||
| IMG_PDFS = $(ASYS:src/%.asy=img/%.pdf) $(PYS:src/img_%.py=img/%.pdf) $(DOTS:src/%.dot=img/%.pdf) | ||||
| 
 | ||||
| IMGS = $(IMG_PDFS) | ||||
| 
 | ||||
| TEMPLATE = eisvogel | ||||
| PDF_ENGINE = xelatex | ||||
| PANDOC = pandoc | ||||
| PANDOC_OPTIONS = -F panflute -F pandoc-citeproc --pdf-engine=$(PDF_ENGINE) --template $(TEMPLATE) -N --standalone --listings | ||||
| 
 | ||||
| GRAPHVIZ = dot | ||||
| GRAPHVIZ_OPTIONS = -Tpdf | ||||
| 
 | ||||
| ASY = asy | ||||
| ASY_OPTIONS = -noV -f pdf | ||||
| 
 | ||||
| PYTHON = python | ||||
| PYTHON_OPTIONS = | ||||
| 
 | ||||
| mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) | ||||
| current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) | ||||
| 
 | ||||
| .PHONY: clean all default | ||||
| all: $(PDFS) | ||||
| default: all | ||||
| 
 | ||||
| out/%.pdf: src/%.md $(IMGS) Makefile | ||||
| 	$(PANDOC) $(PANDOC_OPTIONS) -o $@ $< | ||||
| 
 | ||||
| img/%.pdf: src/%.dot | ||||
| 	$(GRAPHVIZ) $(GRAPHVIZ_OPTIONS) -o $@ $< | ||||
| 
 | ||||
| img/%.pdf: src/img_%.py | ||||
| 	$(PYTHON) $(PYTHON_OPTIONS) $< $@ | ||||
| 
 | ||||
| img/%.pdf: src/%.asy | ||||
| 	$(ASY) $(ASY_OPTIONS) -o $@ $< | ||||
| 
 | ||||
| watch: | ||||
| 	watchexec -w src -w data -w filters -w Makefile make all | ||||
| 
 | ||||
| clean: | ||||
| 	-rm $(PDFS) $(IMGS) | ||||
|  | @ -1,158 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| import contextlib | ||||
| import csv | ||||
| import datetime | ||||
| import hashlib | ||||
| import io | ||||
| import os | ||||
| import re | ||||
| import subprocess as SP | ||||
| import sys | ||||
| import tempfile | ||||
| from functools import partial | ||||
| 
 | ||||
| import panflute as pf | ||||
| from dateutil.parser import parse as dateparse | ||||
| from jinja2 import Environment, PackageLoader, Template, select_autoescape | ||||
| from panflute import * | ||||
| 
 | ||||
| 
 | ||||
| def remove_pound(elem, doc): | ||||
|     if type(elem) == Str: | ||||
|         return Str(elem.text.lstrip("#")) | ||||
| 
 | ||||
| 
 | ||||
| def fix_color(elem, doc): | ||||
|     if type(elem) == MetaMap: | ||||
|         for k in elem.content: | ||||
|             if k.endswith("-color"): | ||||
|                 elem[k] = elem[k].walk(remove_pound) | ||||
| 
 | ||||
| 
 | ||||
| def update_date(elem, doc): | ||||
|     if type(elem) == MetaMap: | ||||
|         datefmt = doc.get_metadata("datefmt", "%Y-%m-%d") | ||||
|         today = datetime.date.today().strftime(datefmt) | ||||
|         date = dateparse(doc.get_metadata("date", today)).date() | ||||
|         elem["date"] = MetaInlines(Str(date.strftime(datefmt))) | ||||
|         return elem | ||||
| 
 | ||||
| 
 | ||||
| def csv_table(elem, doc): | ||||
|     if type(elem) == Para and len(elem.content) == 1 and type(elem.content[0]) == Image: | ||||
|         elem = elem.content[0] | ||||
|         ext = os.path.splitext(elem.url)[1][1:] | ||||
|         if ext == "csv": | ||||
|             caption = elem.content | ||||
|             has_header = elem.attributes.get("has-header", "false").lower() == "true" | ||||
|             with open(elem.url) as f: | ||||
|                 reader = csv.reader(f) | ||||
|                 body = [] | ||||
|                 for row in reader: | ||||
|                     cells = [TableCell(Plain(Str(x))) for x in row] | ||||
|                     body.append(TableRow(*cells)) | ||||
|             header = body.pop(0) if has_header else None | ||||
|             ret = Table(*body, header=header, caption=caption) | ||||
|             return ret | ||||
| 
 | ||||
| 
 | ||||
| def code_refs(elem, doc): | ||||
|     if type(elem) == Cite: | ||||
|         label = elem.content[0] | ||||
|         if type(label) == Str: | ||||
|             label = label.text | ||||
|             filename = re.findall(r"^\[@lst:(.*)\]$", label) or [None] | ||||
|             if filename[0] in doc.inc_files: | ||||
|                 return [ | ||||
|                     RawInline( | ||||
|                         "\\hyperref[{}]{{{}}}".format(filename[0], filename[0]), | ||||
|                         format="tex", | ||||
|                     ) | ||||
|                 ] | ||||
| 
 | ||||
| 
 | ||||
| def include_code(elem, doc): | ||||
|     if type(elem) == CodeBlock: | ||||
|         if "include" in elem.attributes: | ||||
|             filepath = elem.attributes.pop("include") | ||||
|             filename = os.path.split(filepath)[-1] | ||||
|             try: | ||||
|                 elem.text += elem.text + open(filepath, encoding="utf-8").read() | ||||
|                 elem.attributes["caption"] = filename | ||||
|                 doc.inc_files.append(filename) | ||||
|             except Exception as e: | ||||
|                 elem.text += "Error: {}".format(e) | ||||
|             return [RawBlock("\\label{{{}}}".format(filename), format="tex"), elem] | ||||
| 
 | ||||
| 
 | ||||
| def py_eval(options, data, element, doc): | ||||
|     out_buffer = io.StringIO() | ||||
|     with contextlib.redirect_stdout(out_buffer): | ||||
|         exec(data, doc.pyenv) | ||||
|     out_buffer.seek(0) | ||||
|     return convert_text(out_buffer.read()) | ||||
| 
 | ||||
| 
 | ||||
| def jinja_py_filt(doc, file): | ||||
|     env = {} | ||||
|     code = open(file, encoding="utf-8").read() | ||||
|     exec(code, env) | ||||
|     return env["main"](doc) | ||||
| 
 | ||||
| 
 | ||||
| def prepare(doc): | ||||
|     doc.inc_files = [] | ||||
|     doc.env = Environment() | ||||
|     doc.pyenv = {} | ||||
|     filters = {"py": partial(jinja_py_filt, doc)} | ||||
|     doc.env.filters.update(filters) | ||||
| 
 | ||||
| 
 | ||||
| def process_templates(elem, doc): | ||||
|     if type(elem) == CodeBlock: | ||||
|         if elem.classes == ["@"]: | ||||
|             args = {"meta": doc.get_metadata()} | ||||
|             return convert_text(doc.env.from_string(elem.text).render(args)) | ||||
| 
 | ||||
| 
 | ||||
| def yaml_filt(elem, doc): | ||||
|     tags = {"eval": py_eval} | ||||
|     return yaml_filter(elem, doc, tags=tags, strict_yaml=True) | ||||
| 
 | ||||
| 
 | ||||
| def checkboxes(elem, doc): | ||||
|     if type(elem) in [Para, Plain]: | ||||
|         val = re.findall(r"^\[([xX]|\ )\] (.*)$", stringify(elem)) | ||||
|         if val: | ||||
|             val = val[0][0].lower() == "x" | ||||
|         else: | ||||
|             return elem | ||||
|         marker = { | ||||
|             True: RawInline("$\\boxtimes$", format="latex"), | ||||
|             False: RawInline("$\\square$", format="latex"), | ||||
|         }[val] | ||||
|         cont = [] | ||||
|         if val: | ||||
|             cont += elem.content[2:] | ||||
|         else: | ||||
|             cont += elem.content[4:] | ||||
|         return Plain(marker, Space, *cont) | ||||
|     return elem | ||||
| 
 | ||||
| 
 | ||||
| def main(doc=None): | ||||
|     f = [ | ||||
|         process_templates, | ||||
|         update_date, | ||||
|         csv_table, | ||||
|         include_code, | ||||
|         fix_color, | ||||
|         code_refs, | ||||
|         yaml_filt, | ||||
|         checkboxes, | ||||
|     ] | ||||
|     return run_filters(f, prepare=prepare, doc=doc) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
|  | @ -1,92 +0,0 @@ | |||
| --- | ||||
| # Metadata | ||||
| title: ED_LRR | ||||
| author: | ||||
| - Daniel Seiller <earthnuker@gmail.com> | ||||
| subtitle: 'Elite Dangerous: Long-Range Router' | ||||
| 
 | ||||
| # Formating | ||||
| toc: true | ||||
| lang: en | ||||
| colorlinks: true | ||||
| papersize: a4 | ||||
| numbersections: true | ||||
| 
 | ||||
| #Panflute options | ||||
| panflute-filters: [multifilter] | ||||
| panflute-path: 'filters' | ||||
| 
 | ||||
| #Template options | ||||
| titlepage: true | ||||
| toc-own-page: false | ||||
| --- | ||||
| 
 | ||||
| \pagebreak | ||||
| 
 | ||||
| # Implementation | ||||
| 
 | ||||
| ## `stars.csv` format | ||||
| 
 | ||||
| ### Columns | ||||
| 
 | ||||
| | Name      | Content                                                      | | ||||
| | --------- | ------------------------------------------------------------ | | ||||
| | id        | unique ID-Number (not equal to id or id64, just a sequential number) | | ||||
| | star_type | Type of Star                                                 | | ||||
| | system    | Name of System                                               | | ||||
| | body      | Name of Star                                                 | | ||||
| | mult      | Jump Range Multiplier (1.5 for White Dwarfs, 4.0 for Neutron Stars) | | ||||
| | distance  | Distance from arrival in Ls                                  | | ||||
| | x,y,z     | Position in Galactic Coordinates with Sol at (0,0,0)         | | ||||
| 
 | ||||
| ## `stars.idx` format | ||||
| 
 | ||||
| `bincode` serialized data: | ||||
| 
 | ||||
| - **[u64]**: List of byte offset for records (entry 0=first recod, entry 1= second record, etc) | ||||
| 
 | ||||
| ## Routing Algorithms | ||||
| 
 | ||||
| ### Breadth-First Search (BFS) | ||||
| 
 | ||||
| Standard Breadth-First Search, always finds the shortest route | ||||
| 
 | ||||
| ### A*-Search | ||||
| 
 | ||||
| Modified A*-Search with adjustable "greediness". Priority Queue weighted by $\text{number of jumps from start system} + (\text{estimated number of jumps to target system} * \text{greediness})$ | ||||
| 
 | ||||
| A greediness of 0 is equivalent to BFS and a greediness of $\infty$ is equivalent to Greedy-Search | ||||
| 
 | ||||
| ### Greedy-Search | ||||
| 
 | ||||
| Priority Queue weighted by minimum distance to target, prefers systems with high multiplier (Neutron Stars and White Dwarfs) | ||||
| 
 | ||||
| ## Optimizations | ||||
| 
 | ||||
| ### Ellipse elimination | ||||
| 
 | ||||
| Only consider systems within an ellipsoid with source and destination as the foci, the width of the ellipsoid is adjustable | ||||
| 
 | ||||
| ## Routing Graphs | ||||
| 
 | ||||
| ### File format | ||||
| 
 | ||||
| `bincode` serialized data: | ||||
| 
 | ||||
| - *bool* **primary**: flag to indicate that graph only includes primary stars | ||||
| - *f32* **range**: jump range for routing graph | ||||
| - *[u8]* **file_hash**: sha3 hash of `stars.csv` from which graph was generated | ||||
| - *String* **path**: path to `stars.csv` from which graph was generated | ||||
| - *FnvHashMap* **graph**: Hashmap mapping systems to the systems from which they can be rached, traversed from destination system backwards to source to reconstruct route | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| <!-- | ||||
| TODO: Add screenshots | ||||
| --> | ||||
| 
 | ||||
| ## Preprocessing Data | ||||
| 
 | ||||
| ## Plotting a Route | ||||
| 
 | ||||
| # [Changelog](https://gitlab.com/Earthnuker/ed_lrr/blob/pyqt_gui/CHANGELOG.md) | ||||
|  | @ -1,83 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| import heapq | ||||
| import sys | ||||
| 
 | ||||
| import numpy as np | ||||
| import pylab as PL | ||||
| from scipy.spatial.ckdtree import cKDTree | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| 
 | ||||
| def vec(a, b): | ||||
|     return b - a | ||||
| 
 | ||||
| 
 | ||||
| def bfs(points): | ||||
|     return | ||||
| 
 | ||||
| 
 | ||||
| def in_ellipse(p, f1, f2, r, offset=0): | ||||
|     df = ((f1 - f2) ** 2).sum(0) ** 0.5 | ||||
|     d_f1 = ((p - f1) ** 2).sum(1) ** 0.5 | ||||
|     d_f2 = ((p - f2) ** 2).sum(1) ** 0.5 | ||||
|     return (d_f1 + d_f2) < (df * (1 + r)) | ||||
| 
 | ||||
| 
 | ||||
| num_points = 100000 | ||||
| 
 | ||||
| p_orig = np.random.normal(0, 10, size=(num_points, 2)) | ||||
| tree = cKDTree(p_orig) | ||||
| f1 = np.array([0, -30]) | ||||
| f2 = -f1  # np.random.normal(0, 20, (3,)) | ||||
| # r = 2 ** ((n / cnt) - cnt) | ||||
| 
 | ||||
| mask = in_ellipse(p_orig, f1, f2, 0.1) | ||||
| 
 | ||||
| p = p_orig[mask] | ||||
| p_orig = p_orig[~mask] | ||||
| 
 | ||||
| colors = np.random.random(p.shape[0]) | ||||
| fig = PL.gcf() | ||||
| PL.scatter( | ||||
|     p_orig[:, 0], | ||||
|     p_orig[:, 1], | ||||
|     marker=".", | ||||
|     s=0.2, | ||||
|     edgecolor="None", | ||||
|     c=[(0.0, 0.0, 0.0)], | ||||
|     alpha=0.75, | ||||
|     rasterized=True, | ||||
| ) | ||||
| PL.scatter( | ||||
|     p[:, 0], | ||||
|     p[:, 1], | ||||
|     marker="s", | ||||
|     s=0.2, | ||||
|     edgecolor="None", | ||||
|     c=colors, | ||||
|     rasterized=True | ||||
| ) | ||||
| PL.plot(f1[0], f1[1], "r.", label="Source") | ||||
| PL.plot(f2[0], f2[1], "g.", label="Destination") | ||||
| 
 | ||||
| max_v = max( | ||||
|     p_orig[:, 0].max(), | ||||
|     p_orig[:, 1].max(), | ||||
|     f1[0], f1[1], | ||||
|     f2[0], f2[1] | ||||
| ) + 2 | ||||
| 
 | ||||
| min_v = min( | ||||
|     p_orig[:, 0].min(), | ||||
|     p_orig[:, 1].min(), | ||||
|     f1[0], f1[1], | ||||
|     f2[0], f2[1] | ||||
| ) - 2 | ||||
| 
 | ||||
| 
 | ||||
| PL.xlim(min_v, max_v) | ||||
| PL.ylim(min_v, max_v) | ||||
| 
 | ||||
| PL.legend() | ||||
| PL.savefig(sys.argv[1], dpi=1200) | ||||
							
								
								
									
										1
									
								
								docs_mdbook/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs_mdbook/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| book | ||||
|  | @ -4,8 +4,8 @@ | |||
|         "en" | ||||
|     ], | ||||
|     "spellright.documentTypes": [ | ||||
|         "markdown", | ||||
|         "latex", | ||||
|         "plaintext" | ||||
|         "plaintext", | ||||
|         "git-commit" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										6
									
								
								docs_mdbook/.vscode/vscode-kanban.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docs_mdbook/.vscode/vscode-kanban.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| { | ||||
|   "todo": [], | ||||
|   "in-progress": [], | ||||
|   "testing": [], | ||||
|   "done": [] | ||||
| } | ||||
							
								
								
									
										22
									
								
								docs_mdbook/book.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								docs_mdbook/book.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| [book] | ||||
| authors = ["Daniel Seiller"] | ||||
| language = "en" | ||||
| multilingual = false | ||||
| src = "src" | ||||
| title = "Elite: Dangerous Long Range Router" | ||||
| 
 | ||||
| [output.html] | ||||
| preferred-dark-theme = "ayu" | ||||
| mathjax-support = true | ||||
| 
 | ||||
| [preprocessor.svgbob] | ||||
| text_width = 8.0 | ||||
| text_height = 16.0 | ||||
| class = "bob" | ||||
| font_family = "arial" | ||||
| font_size = 14.0 | ||||
| stroke_width = 2.0 | ||||
| # there's using css-variables from theme: | ||||
| stroke_color = "var(--fg)" # see default theme / variables.css | ||||
| background_color = "transparent" # also useful `var(--bg)` | ||||
| # all properties are optional. | ||||
							
								
								
									
										12
									
								
								docs_mdbook/src/SUMMARY.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docs_mdbook/src/SUMMARY.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| # Summary | ||||
| 
 | ||||
| - [Intro](./intro/_index.md) | ||||
|   - [Galactic Travel in Elite: Dangerous](./intro/ed_travel.md) | ||||
|     - [FSD Fuel consumption and jump range](./intro/fsd_fuel.md) | ||||
|   - [Graph Search algorithms](./intro/graph_algos.md) | ||||
| 
 | ||||
| - [Elite Dangerous Long Range Router](./ed_lrr/_index.md) | ||||
|   - [Graph representation](./ed_lrr/graph.md) | ||||
|   - [Search modes](./ed_lrr/modes.md) | ||||
|   - [Graph precomputation](./ed_lrr/precomp.md) | ||||
|   - [Python API](./ed_lrr/py_api.md) | ||||
							
								
								
									
										1
									
								
								docs_mdbook/src/ed_lrr/_index.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs_mdbook/src/ed_lrr/_index.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| # Elite Dangerous Long Range Router | ||||
							
								
								
									
										4
									
								
								docs_mdbook/src/ed_lrr/graph.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs_mdbook/src/ed_lrr/graph.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| # Graph representation | ||||
| 
 | ||||
| ED_LRR uses an implicit graph built on top of an R*-Tree for its route computation. | ||||
| Every node (star system) has edges towards all systems within jump range, edge weights (the distance between star systems) can be computed on the fly when necessary | ||||
							
								
								
									
										31
									
								
								docs_mdbook/src/ed_lrr/modes.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								docs_mdbook/src/ed_lrr/modes.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| # Search modes | ||||
| 
 | ||||
| ## Heuristic | ||||
| 
 | ||||
| A*, Greedy and Beam search all use the following heuristic to select candidates to expand on the next layer of the graph | ||||
| 
 | ||||
| $$mult(n) =  | ||||
| \begin{cases} | ||||
|     4 &\text{if  $n$ is a neutron star} \\\\ | ||||
|     1.5 &\text{if $n$ is a white dwarf star} \\\\ | ||||
|     1 &\text{otherwise} | ||||
| \end{cases} | ||||
| $$ | ||||
| 
 | ||||
| $$d(a,b) = \sqrt{(a_x-b_x)^2+(a_y-b_y)^2+(a_z-b_z)^2}$$ | ||||
| 
 | ||||
| $$ | ||||
| h(\text{node},\text{goal})= | ||||
|     \max( | ||||
|         d(\text{node},\text{goal})- | ||||
|         ( | ||||
|             \text{range}*mult(\text{node}) | ||||
|         ),0) | ||||
| $$ | ||||
| 
 | ||||
| potential new heuristic: | ||||
| 
 | ||||
| $$ | ||||
| h(\text{node},\text{next},\text{goal})= | ||||
| 1 - {\cos^{-1}(|(\text{next}-\text{node})| \cdot |(\text{goal}-\text{node})|)\over\pi} | ||||
| $$ | ||||
							
								
								
									
										2
									
								
								docs_mdbook/src/ed_lrr/precomp.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs_mdbook/src/ed_lrr/precomp.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| # Graph precomputation | ||||
| 
 | ||||
							
								
								
									
										58
									
								
								docs_mdbook/src/ed_lrr/py_api.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								docs_mdbook/src/ed_lrr/py_api.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| # Python API | ||||
| 
 | ||||
| First the module needs to be imported | ||||
| 
 | ||||
| ```python | ||||
| from _ed_lrr import PyRouter, PyShip | ||||
| ``` | ||||
| 
 | ||||
| Then we need to instantiate a route plotter | ||||
| 
 | ||||
| ```python | ||||
| # callback is passed a dict describing the current search state and progress | ||||
| def callback(state): | ||||
|     print(state) | ||||
| 
 | ||||
| r=PyRouter(callback) | ||||
| ``` | ||||
| 
 | ||||
| Optionally ship loadouts can be loaded from the Elite: Dangerous journal files | ||||
| 
 | ||||
| ```python | ||||
| ships = PyShip.from_journal() | ||||
| ``` | ||||
| 
 | ||||
| To plot a route we need to load a list of star systems with coordinates | ||||
| 
 | ||||
| ```python | ||||
| r.load("./stars.csv") | ||||
| ``` | ||||
| 
 | ||||
| After a list has been loaded we can resolve star systems to their IDs | ||||
| 
 | ||||
| ```python | ||||
| systems = [ | ||||
|     # resolve by coordinates, needs to build an R*-Tree so uses a few GB of RAM | ||||
|     (0,0,0), | ||||
|     # resolve by name, does fuzzy search, has to scan the whole list of system names | ||||
|     "Colonia", | ||||
|     # resolve by ID, fairly fast, but the IDs are ed_lrr specific | ||||
|     3553323 | ||||
| ] | ||||
| systems = r.resolve_systems(*query) # this will return a dict mapping the input key to a dict | ||||
| assert sorted(systems.keys())==sorted(query) | ||||
| sys_ids = {k: v["id"] for k, v in systems.items()} | ||||
| ``` | ||||
| 
 | ||||
| Once the system IDs are known we can compute a route | ||||
| 
 | ||||
| ```python | ||||
| route = r.route( | ||||
|     [sys_ids["Sol"], sys_ids["Colonia"]] # route hops, can be any number of systems (at least 2) | ||||
|     48.0, # jump range | ||||
|     beam_width=1<<12, # beam width to limit exploration (4096) | ||||
|     greedyness=0, # greedyness for A* (0=BFS,0.5 = A*, 1=Greedy) | ||||
|     max_dist=500, # maximum deviation from straight line (not yet implemented) | ||||
|     num_workers=0 # number of workers to distribute the search accross (0 -> serial mode, >=1 -> spawn worker pool) | ||||
| ) | ||||
| ``` | ||||
							
								
								
									
										17
									
								
								docs_mdbook/src/fsd.asy
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docs_mdbook/src/fsd.asy
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| import graph; | ||||
| size(500,0); | ||||
| real fsd(real m_fuel) { | ||||
|     // 4A drive | ||||
|     real boost = 1.0; | ||||
|     real f_max = 3.0; | ||||
|     real m_opt = 525.0; | ||||
|     real m_ship = 347.0; | ||||
|     real l = 12.0; | ||||
|     real p = 2.30; | ||||
|     return ((boost*m_opt*(1000.0*min(f_max,m_fuel)/l)^(1/p)))/(m_ship+m_fuel); | ||||
| } | ||||
| 
 | ||||
| draw(graph(fsd,-100,100,n=500,Hermite),red); | ||||
| 
 | ||||
| xaxis("$m_{fuel}$"); | ||||
| yaxis("$range (Ly)$",0); | ||||
							
								
								
									
										1
									
								
								docs_mdbook/src/intro/_index.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs_mdbook/src/intro/_index.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| # Intro | ||||
							
								
								
									
										19
									
								
								docs_mdbook/src/intro/ed_travel.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs_mdbook/src/intro/ed_travel.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| # Galactic Travel in Elite: Dangerous | ||||
| 
 | ||||
| All ships in Elite: Dangerous (E:D) are equipped with a Frame Shift Drive (FSD) which allows them to jumpst vast distances (multiple light years) from one star system to another. | ||||
| 
 | ||||
| The maximum range you can traverse in a single jump is limited by the maximum fuel consuption per jump of the specific drive (depends on class and rating) and influenced by the following factors: | ||||
| 
 | ||||
| - Rating of the FSD | ||||
| - Class of the FSD | ||||
| - Mass of your ship (Base mass+Cargo mass+Fuel mass) | ||||
| - Amount of fuel available in the tank | ||||
| 
 | ||||
| For details see [the chapter detailing FSD fuel consumption](./fsd_fuel.html) | ||||
| 
 | ||||
| If the ship is equipped with a Fuel Scoop it can: | ||||
| 
 | ||||
| - Scoop hydrogen from the corona of a star to refill its fuel tank (only applies to stars of class K, G, B, F, O, A or M) | ||||
| - Supercharge its FSD to increase the maximum jump range by a factor of: | ||||
|   - 4 if supercharging in the jets of a neutron star | ||||
|   - 1.5 if supercharging in the jets of a white dwarf star | ||||
							
								
								
									
										36
									
								
								docs_mdbook/src/intro/fsd_fuel.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs_mdbook/src/intro/fsd_fuel.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| # Notes on FSD Fuel consumption and jump range | ||||
| 
 | ||||
| FSD Fuel consumption ([Elite: Dangerous Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)): | ||||
| $$Fuel = 0.001 \cdot l \cdot \left(\frac{dist \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}$$ | ||||
| 
 | ||||
| Solving for \\(dist\\) gives the jump range (in Ly) for a given amount of fuel (in tons) as: | ||||
| $$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$ | ||||
| 
 | ||||
| Assuming \\(f_{max}\\) tons of available fuel gives us the maximum jump range for a single jump as: | ||||
| $$dist_{max} = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot f_{max}}{l}\right)^{\frac{1}{p}}}{f_{max} + m_{ship}}$$ | ||||
| 
 | ||||
| Since the guardian FSD booster increases the maximum jump range by \\(B_g\\) light years we can calculate a correction factor for the fuel consumption as: | ||||
| $$ e_{fuel} = 0.001 \cdot l \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p}$$ | ||||
| 
 | ||||
| Incorporating \\(e_{fuel}\\) into the distance equation yields | ||||
| $$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$ | ||||
| 
 | ||||
| Expanding \\(e_{fuel}\\) yields | ||||
| $$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p} \cdot \min\left(f_{max}, m_{fuel}\right)\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$ | ||||
| 
 | ||||
| Finally, Expanding \\(dist_{max}\\) yields the full equation as | ||||
| $$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000000.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{- p} \cdot \min\left(f_{max}, m_{fuel}\right)}{l^{2}}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$ | ||||
| 
 | ||||
| Where: | ||||
| - \\(Fuel\\) is the fuel needed to jump (in tons) | ||||
| - \\(l\\) is the linear constant of your FSD (depends on the rating) | ||||
| - \\(p\\) is the power constant of your FSD (depends on the class) | ||||
| - \\(m_{ship}\\) is the mass of your ship (including cargo) | ||||
| - \\(m_{fuel}\\) is the amount of fuel in tons currently stored in your tanks | ||||
| - \\(m_{opt}\\) is the optimized mass of your FSD (in tons) | ||||
| - \\(f_{max}\\) is the maximum amount of fuel your FSD can use per jump | ||||
| - \\(boost\\) is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc) | ||||
| - \\(dist\\) is the distance you can jump with a given fuel amount | ||||
| - \\(dist_{max}\\) is the maximum distance you can jump (when \\(m_{fuel}=f_{max}\\)) | ||||
| - \\(B_{g}\\) is the amount of Ly added by your Guardian FSD Booster | ||||
| - \\(e_{fuel}\\) is the efficiency increase added by the Guardian FSD Booster | ||||
							
								
								
									
										25
									
								
								docs_mdbook/src/intro/graph_algos.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								docs_mdbook/src/intro/graph_algos.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| # Graph Search algorithms | ||||
| 
 | ||||
| ## Breadth-first search (BFS) | ||||
| 
 | ||||
| BFS expand node in breadth first order while keeping track of the parent node of each expanded node | ||||
| 
 | ||||
| ## Beam search | ||||
| 
 | ||||
| Beam search is similar to BFS but limits the number of expanded nodes based on a heuristic | ||||
| 
 | ||||
| ## Greedy search | ||||
| 
 | ||||
| Greedy search is essentially Beam search with a beam width of 1 | ||||
| 
 | ||||
| ## Dijkstra | ||||
| 
 | ||||
| Dijkstra's algorithm finds the shortest path across a graph based on some edge weight | ||||
| 
 | ||||
| ## A* | ||||
| 
 | ||||
| A* is similar to Dijkstra but uses a heuristic to speed up the search | ||||
| 
 | ||||
| ## Beam-Stack search (BSS) | ||||
| 
 | ||||
| Beam-Stack search is a variation of beam search which keeps a separate priority queue for each layer of the graph to allow backtracking and expand previously unexpanded nodes | ||||
							
								
								
									
										47
									
								
								docs_mdbook/src/range.asy
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								docs_mdbook/src/range.asy
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| 
 | ||||
| import graph; | ||||
| import stats; | ||||
| size(500); | ||||
| srand(10); | ||||
| 
 | ||||
| struct Star { | ||||
|     pair pos; | ||||
|     real mult; | ||||
| } | ||||
| 
 | ||||
| real range = 48.0; | ||||
| 
 | ||||
| int n_stars=1000; | ||||
| Star[] stars=new Star[n_stars]; | ||||
| for(int i=0; i < n_stars; ++i) { | ||||
|     Star s=new Star; | ||||
|     s.pos=(Gaussrand()*range*2,Gaussrand()*range*2); | ||||
|     s.mult=1.0; | ||||
|     if (unitrand()<0.2) { | ||||
|         s.mult=1.5; | ||||
|     } else { | ||||
|         if (unitrand()<0.1) { | ||||
|             s.mult=4.0; | ||||
|         } | ||||
|     } | ||||
|     stars[i]=s; | ||||
| } | ||||
| 
 | ||||
| Star origin=new Star; | ||||
| origin.pos=(0,0); | ||||
| origin.mult=1.0; | ||||
| 
 | ||||
| draw(circle(origin.pos,range*origin.mult),white); | ||||
| draw(circle(origin.pos,range),white+dashed); | ||||
| draw(circle(origin.pos,range*2),white+dashed); | ||||
| draw(circle(origin.pos,range*4),white+dashed); | ||||
| fill(circle(origin.pos,2),red); | ||||
| 
 | ||||
| for (Star s: stars) { | ||||
|     if (length(s.pos-origin.pos)<(range*origin.mult)) { | ||||
|         draw(s.pos--origin.pos,white+dashed); | ||||
|         fill(circle(s.pos,s.mult),green); | ||||
|     } else { | ||||
|         fill(circle(s.pos,s.mult),white); | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										24
									
								
								ed_lrr_gui.code-workspace
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								ed_lrr_gui.code-workspace
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| { | ||||
| 	"folders": [ | ||||
| 		{ | ||||
| 			"path": "." | ||||
| 		}, | ||||
| 		{ | ||||
| 			"path": "rust" | ||||
| 		} | ||||
| 	], | ||||
| 	"settings": { | ||||
| 		"discord.enabled": true, | ||||
| 		"files.exclude": { | ||||
| 			"**/.git": true, | ||||
| 			"**/.svn": true, | ||||
| 			"**/.hg": true, | ||||
| 			"**/CVS": true, | ||||
| 			"**/.DS_Store": true, | ||||
| 			"**/Thumbs.db": true, | ||||
| 			"**/.history": true, | ||||
| 			".history": true | ||||
| 		}, | ||||
| 		"explorerExclude.backup": null | ||||
| 	} | ||||
| } | ||||
|  | @ -304,15 +304,7 @@ def preprocess(systems, bodies, output): | |||
|     "-g", | ||||
|     metavar="<float>", | ||||
|     default=cfg["route.greediness"], | ||||
|     help="Greedyness factor for A-Star", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--mode", | ||||
|     "-m", | ||||
|     default=cfg["route.mode"], | ||||
|     help="Search mode", | ||||
|     type=click.Choice(["bfs", "bfs_old", "a-star", "greedy"]), | ||||
|     help="Greedyness factor (0.0=BFS, 1.0=Greedy)", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|  |  | |||
							
								
								
									
										108
									
								
								fsd_eq.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								fsd_eq.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| from sympy import * | ||||
| from sympy.utilities.codegen import RustCodeGen | ||||
| 
 | ||||
| 
 | ||||
| def to_latex(eq, inline=False): | ||||
|     mode = "equation" | ||||
|     itex = True | ||||
|     if inline: | ||||
|         mode = "inline" | ||||
|         itex = False | ||||
|     return latex(eq, mul_symbol=" \\cdot ", mode=mode, itex=itex) | ||||
| 
 | ||||
| 
 | ||||
| def solve_for(eq, sym): | ||||
|     return Eq(sym, solve(eq, sym)[0]) | ||||
| 
 | ||||
| 
 | ||||
| init_printing(use_latex=True, latex_mode="equation", mul_symbol="\\cdot") | ||||
| 
 | ||||
| var("m_ship m_fuel m_opt f_useable f_max l p boost dist dist_max Fuel B_g e_fuel") | ||||
| 
 | ||||
| mass = m_ship + m_fuel  # total mass of ship+fuel | ||||
| m_opt = m_opt * boost  # supercharging increases optimized mass | ||||
| 
 | ||||
| available_fuel = Min( | ||||
|     f_max, m_fuel | ||||
| )  # limit maximum fuel consumption to FSD max fuel limit | ||||
| 
 | ||||
| eq_fuel = Eq(Fuel, l * 0.001 * (((dist * mass) / m_opt) ** p))  # FSD Fuel equation | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| eq_fuel_boost = eq_fuel.subs({"dist":dist+B_g,"Fuel":available_fuel*e_fuel}) # FSD Booster boosts maximum distance by B_g | ||||
| eq_d_boost = solve_for(eq_fuel_boost, dist)  # solve for distance | ||||
| # eq_d_boost = eq_d_boost.subs({"Fuel":f_max,"m_fuel":f_max})  # Assume maximum jump range | ||||
| 
 | ||||
| print(to_latex(eq_d_boost)) | ||||
| print(to_latex(solve_for(eq_d_boost,e_fuel))) | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| max_range = eq_d.subs( | ||||
|     {m_fuel: f_max, dist: dist_max} | ||||
| )  # Compute maximum jump range by assuming f_max tons of fuel in tank | ||||
| 
 | ||||
| full_eq = eq_d.subs( | ||||
|     Min(f_max, m_fuel), Min(f_max, m_fuel) * fuel_mult | ||||
| ).subs( | ||||
|     max_range.lhs, max_range.rhs | ||||
| )  # substitute everything in | ||||
| 
 | ||||
| docs = [ | ||||
|     ( | ||||
|         eq_fuel, | ||||
|         "FSD Fuel consumption ([E:D Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)):", | ||||
|     ), | ||||
|     ( | ||||
|         eq_d, | ||||
|         "Solving for $dist$ gives the jump range (in Ly) for a given amount of fuel (in tons) as:", | ||||
|     ), | ||||
|     ( | ||||
|         max_range, | ||||
|         "Assuming $f_{max}$ tons of available fuel gives us the maximum jump range for a single jump as:", | ||||
|     ), | ||||
|     ( | ||||
|         fuel_mult, | ||||
|         "Since the guardian FSD booster increases the maximum jump range by $B_g$ Ly we can calculate a correction factor for the fuel consumption as:", | ||||
|     ), | ||||
|     ( | ||||
|         eq_d.subs(Min(f_max, m_fuel), Min(f_max, m_fuel) * e_fuel), | ||||
|         "Incorporating $e_{fuel}$ into the distance equation yields", | ||||
|     ), | ||||
|     ( | ||||
|         eq_d.subs(Min(f_max, m_fuel), Min(f_max, m_fuel) * fuel_mult), | ||||
|         "Expanding $e_{fuel}$ yields", | ||||
|     ), | ||||
|     (full_eq, "Finally, Expanding $dist_{max}$ yields the full equation as"), | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| var_defs = [ | ||||
|     ("Fuel", "is the fuel needed to jump (in tons)"), | ||||
|     ("l", "is the linear constant of your FSD (depends on the rating)"), | ||||
|     ("p", "is the power constant of your FSD (depends on the class)"), | ||||
|     ("m_ship", "is the mass of your ship (including cargo)"), | ||||
|     ("m_fuel", "is the amount of fuel in tons currently stored in your tanks"), | ||||
|     ("m_opt", "is the optimized mass of your FSD (in tons)"), | ||||
|     ("f_max", "is the maximum amount of fuel your FSD can use per jump"), | ||||
|     ( | ||||
|         "boost", | ||||
|         'is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc)', | ||||
|     ), | ||||
|     ("dist", "is the distance you can jump with a given fuel amount"), | ||||
|     ("dist_max", "is the maximum distance you can jump (when $m_{fuel}=f_{max}$)"), | ||||
|     ("B_g", "is the amount of Ly added by your Guardian FSD Booster"), | ||||
|     ("e_fuel", "is the efficiency increase added by the Guardian FSD Booster"), | ||||
| ] | ||||
| for eq, doc in docs: | ||||
|     eq=simplify(eq) | ||||
|     if doc: | ||||
|         print(doc, to_latex(eq)) | ||||
|     else: | ||||
|         print(to_latex(eq)) | ||||
|     print() | ||||
| 
 | ||||
| print("Where:") | ||||
| for name, desc in var_defs: | ||||
|     print("- {} {}".format(to_latex(symbols(name), True), desc)) | ||||
							
								
								
									
										904
									
								
								heuristic_vis.ipynb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										904
									
								
								heuristic_vis.ipynb
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,904 @@ | |||
| { | ||||
|  "cells": [ | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": 2, | ||||
|    "metadata": {}, | ||||
|    "outputs": [ | ||||
|     { | ||||
|      "name": "stdout", | ||||
|      "output_type": "stream", | ||||
|      "text": [ | ||||
|       "Populating the interactive namespace from numpy and matplotlib\n" | ||||
|      ] | ||||
|     } | ||||
|    ], | ||||
|    "source": [ | ||||
|     "%pylab notebook" | ||||
|    ] | ||||
|   }, | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": 131, | ||||
|    "metadata": {}, | ||||
|    "outputs": [ | ||||
|     { | ||||
|      "name": "stderr", | ||||
|      "output_type": "stream", | ||||
|      "text": [ | ||||
|       "<ipython-input-131-1f27bb9e71ac>:5: RuntimeWarning: invalid value encountered in double_scalars\n", | ||||
|       "  ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n" | ||||
|      ] | ||||
|     }, | ||||
|     { | ||||
|      "data": { | ||||
|       "application/javascript": [ | ||||
|        "/* Put everything inside the global mpl namespace */\n", | ||||
|        "window.mpl = {};\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.get_websocket_type = function() {\n", | ||||
|        "    if (typeof(WebSocket) !== 'undefined') {\n", | ||||
|        "        return WebSocket;\n", | ||||
|        "    } else if (typeof(MozWebSocket) !== 'undefined') {\n", | ||||
|        "        return MozWebSocket;\n", | ||||
|        "    } else {\n", | ||||
|        "        alert('Your browser does not have WebSocket support. ' +\n", | ||||
|        "              'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", | ||||
|        "              'Firefox 4 and 5 are also supported but you ' +\n", | ||||
|        "              'have to enable WebSockets in about:config.');\n", | ||||
|        "    };\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", | ||||
|        "    this.id = figure_id;\n", | ||||
|        "\n", | ||||
|        "    this.ws = websocket;\n", | ||||
|        "\n", | ||||
|        "    this.supports_binary = (this.ws.binaryType != undefined);\n", | ||||
|        "\n", | ||||
|        "    if (!this.supports_binary) {\n", | ||||
|        "        var warnings = document.getElementById(\"mpl-warnings\");\n", | ||||
|        "        if (warnings) {\n", | ||||
|        "            warnings.style.display = 'block';\n", | ||||
|        "            warnings.textContent = (\n", | ||||
|        "                \"This browser does not support binary websocket messages. \" +\n", | ||||
|        "                    \"Performance may be slow.\");\n", | ||||
|        "        }\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    this.imageObj = new Image();\n", | ||||
|        "\n", | ||||
|        "    this.context = undefined;\n", | ||||
|        "    this.message = undefined;\n", | ||||
|        "    this.canvas = undefined;\n", | ||||
|        "    this.rubberband_canvas = undefined;\n", | ||||
|        "    this.rubberband_context = undefined;\n", | ||||
|        "    this.format_dropdown = undefined;\n", | ||||
|        "\n", | ||||
|        "    this.image_mode = 'full';\n", | ||||
|        "\n", | ||||
|        "    this.root = $('<div/>');\n", | ||||
|        "    this._root_extra_style(this.root)\n", | ||||
|        "    this.root.attr('style', 'display: inline-block');\n", | ||||
|        "\n", | ||||
|        "    $(parent_element).append(this.root);\n", | ||||
|        "\n", | ||||
|        "    this._init_header(this);\n", | ||||
|        "    this._init_canvas(this);\n", | ||||
|        "    this._init_toolbar(this);\n", | ||||
|        "\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    this.waiting = false;\n", | ||||
|        "\n", | ||||
|        "    this.ws.onopen =  function () {\n", | ||||
|        "            fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", | ||||
|        "            fig.send_message(\"send_image_mode\", {});\n", | ||||
|        "            if (mpl.ratio != 1) {\n", | ||||
|        "                fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", | ||||
|        "            }\n", | ||||
|        "            fig.send_message(\"refresh\", {});\n", | ||||
|        "        }\n", | ||||
|        "\n", | ||||
|        "    this.imageObj.onload = function() {\n", | ||||
|        "            if (fig.image_mode == 'full') {\n", | ||||
|        "                // Full images could contain transparency (where diff images\n", | ||||
|        "                // almost always do), so we need to clear the canvas so that\n", | ||||
|        "                // there is no ghosting.\n", | ||||
|        "                fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", | ||||
|        "            }\n", | ||||
|        "            fig.context.drawImage(fig.imageObj, 0, 0);\n", | ||||
|        "        };\n", | ||||
|        "\n", | ||||
|        "    this.imageObj.onunload = function() {\n", | ||||
|        "        fig.ws.close();\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    this.ws.onmessage = this._make_on_message_function(this);\n", | ||||
|        "\n", | ||||
|        "    this.ondownload = ondownload;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_header = function() {\n", | ||||
|        "    var titlebar = $(\n", | ||||
|        "        '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n", | ||||
|        "        'ui-helper-clearfix\"/>');\n", | ||||
|        "    var titletext = $(\n", | ||||
|        "        '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n", | ||||
|        "        'text-align: center; padding: 3px;\"/>');\n", | ||||
|        "    titlebar.append(titletext)\n", | ||||
|        "    this.root.append(titlebar);\n", | ||||
|        "    this.header = titletext[0];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", | ||||
|        "\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", | ||||
|        "\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_canvas = function() {\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    var canvas_div = $('<div/>');\n", | ||||
|        "\n", | ||||
|        "    canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", | ||||
|        "\n", | ||||
|        "    function canvas_keyboard_event(event) {\n", | ||||
|        "        return fig.key_event(event, event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    canvas_div.keydown('key_press', canvas_keyboard_event);\n", | ||||
|        "    canvas_div.keyup('key_release', canvas_keyboard_event);\n", | ||||
|        "    this.canvas_div = canvas_div\n", | ||||
|        "    this._canvas_extra_style(canvas_div)\n", | ||||
|        "    this.root.append(canvas_div);\n", | ||||
|        "\n", | ||||
|        "    var canvas = $('<canvas/>');\n", | ||||
|        "    canvas.addClass('mpl-canvas');\n", | ||||
|        "    canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", | ||||
|        "\n", | ||||
|        "    this.canvas = canvas[0];\n", | ||||
|        "    this.context = canvas[0].getContext(\"2d\");\n", | ||||
|        "\n", | ||||
|        "    var backingStore = this.context.backingStorePixelRatio ||\n", | ||||
|        "\tthis.context.webkitBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.mozBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.msBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.oBackingStorePixelRatio ||\n", | ||||
|        "\tthis.context.backingStorePixelRatio || 1;\n", | ||||
|        "\n", | ||||
|        "    mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", | ||||
|        "\n", | ||||
|        "    var rubberband = $('<canvas/>');\n", | ||||
|        "    rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", | ||||
|        "\n", | ||||
|        "    var pass_mouse_events = true;\n", | ||||
|        "\n", | ||||
|        "    canvas_div.resizable({\n", | ||||
|        "        start: function(event, ui) {\n", | ||||
|        "            pass_mouse_events = false;\n", | ||||
|        "        },\n", | ||||
|        "        resize: function(event, ui) {\n", | ||||
|        "            fig.request_resize(ui.size.width, ui.size.height);\n", | ||||
|        "        },\n", | ||||
|        "        stop: function(event, ui) {\n", | ||||
|        "            pass_mouse_events = true;\n", | ||||
|        "            fig.request_resize(ui.size.width, ui.size.height);\n", | ||||
|        "        },\n", | ||||
|        "    });\n", | ||||
|        "\n", | ||||
|        "    function mouse_event_fn(event) {\n", | ||||
|        "        if (pass_mouse_events)\n", | ||||
|        "            return fig.mouse_event(event, event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    rubberband.mousedown('button_press', mouse_event_fn);\n", | ||||
|        "    rubberband.mouseup('button_release', mouse_event_fn);\n", | ||||
|        "    // Throttle sequential mouse events to 1 every 20ms.\n", | ||||
|        "    rubberband.mousemove('motion_notify', mouse_event_fn);\n", | ||||
|        "\n", | ||||
|        "    rubberband.mouseenter('figure_enter', mouse_event_fn);\n", | ||||
|        "    rubberband.mouseleave('figure_leave', mouse_event_fn);\n", | ||||
|        "\n", | ||||
|        "    canvas_div.on(\"wheel\", function (event) {\n", | ||||
|        "        event = event.originalEvent;\n", | ||||
|        "        event['data'] = 'scroll'\n", | ||||
|        "        if (event.deltaY < 0) {\n", | ||||
|        "            event.step = 1;\n", | ||||
|        "        } else {\n", | ||||
|        "            event.step = -1;\n", | ||||
|        "        }\n", | ||||
|        "        mouse_event_fn(event);\n", | ||||
|        "    });\n", | ||||
|        "\n", | ||||
|        "    canvas_div.append(canvas);\n", | ||||
|        "    canvas_div.append(rubberband);\n", | ||||
|        "\n", | ||||
|        "    this.rubberband = rubberband;\n", | ||||
|        "    this.rubberband_canvas = rubberband[0];\n", | ||||
|        "    this.rubberband_context = rubberband[0].getContext(\"2d\");\n", | ||||
|        "    this.rubberband_context.strokeStyle = \"#000000\";\n", | ||||
|        "\n", | ||||
|        "    this._resize_canvas = function(width, height) {\n", | ||||
|        "        // Keep the size of the canvas, canvas container, and rubber band\n", | ||||
|        "        // canvas in synch.\n", | ||||
|        "        canvas_div.css('width', width)\n", | ||||
|        "        canvas_div.css('height', height)\n", | ||||
|        "\n", | ||||
|        "        canvas.attr('width', width * mpl.ratio);\n", | ||||
|        "        canvas.attr('height', height * mpl.ratio);\n", | ||||
|        "        canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", | ||||
|        "\n", | ||||
|        "        rubberband.attr('width', width);\n", | ||||
|        "        rubberband.attr('height', height);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    // Set the figure to an initial 600x600px, this will subsequently be updated\n", | ||||
|        "    // upon first draw.\n", | ||||
|        "    this._resize_canvas(600, 600);\n", | ||||
|        "\n", | ||||
|        "    // Disable right mouse context menu.\n", | ||||
|        "    $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", | ||||
|        "        return false;\n", | ||||
|        "    });\n", | ||||
|        "\n", | ||||
|        "    function set_focus () {\n", | ||||
|        "        canvas.focus();\n", | ||||
|        "        canvas_div.focus();\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    window.setTimeout(set_focus, 100);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_toolbar = function() {\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    var nav_element = $('<div/>');\n", | ||||
|        "    nav_element.attr('style', 'width: 100%');\n", | ||||
|        "    this.root.append(nav_element);\n", | ||||
|        "\n", | ||||
|        "    // Define a callback function for later on.\n", | ||||
|        "    function toolbar_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onclick(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "    function toolbar_mouse_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onmouseover(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    for(var toolbar_ind in mpl.toolbar_items) {\n", | ||||
|        "        var name = mpl.toolbar_items[toolbar_ind][0];\n", | ||||
|        "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", | ||||
|        "        var image = mpl.toolbar_items[toolbar_ind][2];\n", | ||||
|        "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n", | ||||
|        "\n", | ||||
|        "        if (!name) {\n", | ||||
|        "            // put a spacer in here.\n", | ||||
|        "            continue;\n", | ||||
|        "        }\n", | ||||
|        "        var button = $('<button/>');\n", | ||||
|        "        button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n", | ||||
|        "                        'ui-button-icon-only');\n", | ||||
|        "        button.attr('role', 'button');\n", | ||||
|        "        button.attr('aria-disabled', 'false');\n", | ||||
|        "        button.click(method_name, toolbar_event);\n", | ||||
|        "        button.mouseover(tooltip, toolbar_mouse_event);\n", | ||||
|        "\n", | ||||
|        "        var icon_img = $('<span/>');\n", | ||||
|        "        icon_img.addClass('ui-button-icon-primary ui-icon');\n", | ||||
|        "        icon_img.addClass(image);\n", | ||||
|        "        icon_img.addClass('ui-corner-all');\n", | ||||
|        "\n", | ||||
|        "        var tooltip_span = $('<span/>');\n", | ||||
|        "        tooltip_span.addClass('ui-button-text');\n", | ||||
|        "        tooltip_span.html(tooltip);\n", | ||||
|        "\n", | ||||
|        "        button.append(icon_img);\n", | ||||
|        "        button.append(tooltip_span);\n", | ||||
|        "\n", | ||||
|        "        nav_element.append(button);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var fmt_picker_span = $('<span/>');\n", | ||||
|        "\n", | ||||
|        "    var fmt_picker = $('<select/>');\n", | ||||
|        "    fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n", | ||||
|        "    fmt_picker_span.append(fmt_picker);\n", | ||||
|        "    nav_element.append(fmt_picker_span);\n", | ||||
|        "    this.format_dropdown = fmt_picker[0];\n", | ||||
|        "\n", | ||||
|        "    for (var ind in mpl.extensions) {\n", | ||||
|        "        var fmt = mpl.extensions[ind];\n", | ||||
|        "        var option = $(\n", | ||||
|        "            '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n", | ||||
|        "        fmt_picker.append(option);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    // Add hover states to the ui-buttons\n", | ||||
|        "    $( \".ui-button\" ).hover(\n", | ||||
|        "        function() { $(this).addClass(\"ui-state-hover\");},\n", | ||||
|        "        function() { $(this).removeClass(\"ui-state-hover\");}\n", | ||||
|        "    );\n", | ||||
|        "\n", | ||||
|        "    var status_bar = $('<span class=\"mpl-message\"/>');\n", | ||||
|        "    nav_element.append(status_bar);\n", | ||||
|        "    this.message = status_bar[0];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n", | ||||
|        "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", | ||||
|        "    // which will in turn request a refresh of the image.\n", | ||||
|        "    this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.send_message = function(type, properties) {\n", | ||||
|        "    properties['type'] = type;\n", | ||||
|        "    properties['figure_id'] = this.id;\n", | ||||
|        "    this.ws.send(JSON.stringify(properties));\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.send_draw_message = function() {\n", | ||||
|        "    if (!this.waiting) {\n", | ||||
|        "        this.waiting = true;\n", | ||||
|        "        this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n", | ||||
|        "    }\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_save = function(fig, msg) {\n", | ||||
|        "    var format_dropdown = fig.format_dropdown;\n", | ||||
|        "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", | ||||
|        "    fig.ondownload(fig, format);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_resize = function(fig, msg) {\n", | ||||
|        "    var size = msg['size'];\n", | ||||
|        "    if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n", | ||||
|        "        fig._resize_canvas(size[0], size[1]);\n", | ||||
|        "        fig.send_message(\"refresh\", {});\n", | ||||
|        "    };\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n", | ||||
|        "    var x0 = msg['x0'] / mpl.ratio;\n", | ||||
|        "    var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", | ||||
|        "    var x1 = msg['x1'] / mpl.ratio;\n", | ||||
|        "    var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", | ||||
|        "    x0 = Math.floor(x0) + 0.5;\n", | ||||
|        "    y0 = Math.floor(y0) + 0.5;\n", | ||||
|        "    x1 = Math.floor(x1) + 0.5;\n", | ||||
|        "    y1 = Math.floor(y1) + 0.5;\n", | ||||
|        "    var min_x = Math.min(x0, x1);\n", | ||||
|        "    var min_y = Math.min(y0, y1);\n", | ||||
|        "    var width = Math.abs(x1 - x0);\n", | ||||
|        "    var height = Math.abs(y1 - y0);\n", | ||||
|        "\n", | ||||
|        "    fig.rubberband_context.clearRect(\n", | ||||
|        "        0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n", | ||||
|        "\n", | ||||
|        "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n", | ||||
|        "    // Updates the figure title.\n", | ||||
|        "    fig.header.textContent = msg['label'];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n", | ||||
|        "    var cursor = msg['cursor'];\n", | ||||
|        "    switch(cursor)\n", | ||||
|        "    {\n", | ||||
|        "    case 0:\n", | ||||
|        "        cursor = 'pointer';\n", | ||||
|        "        break;\n", | ||||
|        "    case 1:\n", | ||||
|        "        cursor = 'default';\n", | ||||
|        "        break;\n", | ||||
|        "    case 2:\n", | ||||
|        "        cursor = 'crosshair';\n", | ||||
|        "        break;\n", | ||||
|        "    case 3:\n", | ||||
|        "        cursor = 'move';\n", | ||||
|        "        break;\n", | ||||
|        "    }\n", | ||||
|        "    fig.rubberband_canvas.style.cursor = cursor;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_message = function(fig, msg) {\n", | ||||
|        "    fig.message.textContent = msg['message'];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_draw = function(fig, msg) {\n", | ||||
|        "    // Request the server to send over a new figure.\n", | ||||
|        "    fig.send_draw_message();\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n", | ||||
|        "    fig.image_mode = msg['mode'];\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.updated_canvas_event = function() {\n", | ||||
|        "    // Called whenever the canvas gets updated.\n", | ||||
|        "    this.send_message(\"ack\", {});\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "// A function to construct a web socket function for onmessage handling.\n", | ||||
|        "// Called in the figure constructor.\n", | ||||
|        "mpl.figure.prototype._make_on_message_function = function(fig) {\n", | ||||
|        "    return function socket_on_message(evt) {\n", | ||||
|        "        if (evt.data instanceof Blob) {\n", | ||||
|        "            /* FIXME: We get \"Resource interpreted as Image but\n", | ||||
|        "             * transferred with MIME type text/plain:\" errors on\n", | ||||
|        "             * Chrome.  But how to set the MIME type?  It doesn't seem\n", | ||||
|        "             * to be part of the websocket stream */\n", | ||||
|        "            evt.data.type = \"image/png\";\n", | ||||
|        "\n", | ||||
|        "            /* Free the memory for the previous frames */\n", | ||||
|        "            if (fig.imageObj.src) {\n", | ||||
|        "                (window.URL || window.webkitURL).revokeObjectURL(\n", | ||||
|        "                    fig.imageObj.src);\n", | ||||
|        "            }\n", | ||||
|        "\n", | ||||
|        "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", | ||||
|        "                evt.data);\n", | ||||
|        "            fig.updated_canvas_event();\n", | ||||
|        "            fig.waiting = false;\n", | ||||
|        "            return;\n", | ||||
|        "        }\n", | ||||
|        "        else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n", | ||||
|        "            fig.imageObj.src = evt.data;\n", | ||||
|        "            fig.updated_canvas_event();\n", | ||||
|        "            fig.waiting = false;\n", | ||||
|        "            return;\n", | ||||
|        "        }\n", | ||||
|        "\n", | ||||
|        "        var msg = JSON.parse(evt.data);\n", | ||||
|        "        var msg_type = msg['type'];\n", | ||||
|        "\n", | ||||
|        "        // Call the  \"handle_{type}\" callback, which takes\n", | ||||
|        "        // the figure and JSON message as its only arguments.\n", | ||||
|        "        try {\n", | ||||
|        "            var callback = fig[\"handle_\" + msg_type];\n", | ||||
|        "        } catch (e) {\n", | ||||
|        "            console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n", | ||||
|        "            return;\n", | ||||
|        "        }\n", | ||||
|        "\n", | ||||
|        "        if (callback) {\n", | ||||
|        "            try {\n", | ||||
|        "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", | ||||
|        "                callback(fig, msg);\n", | ||||
|        "            } catch (e) {\n", | ||||
|        "                console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n", | ||||
|        "            }\n", | ||||
|        "        }\n", | ||||
|        "    };\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", | ||||
|        "mpl.findpos = function(e) {\n", | ||||
|        "    //this section is from http://www.quirksmode.org/js/events_properties.html\n", | ||||
|        "    var targ;\n", | ||||
|        "    if (!e)\n", | ||||
|        "        e = window.event;\n", | ||||
|        "    if (e.target)\n", | ||||
|        "        targ = e.target;\n", | ||||
|        "    else if (e.srcElement)\n", | ||||
|        "        targ = e.srcElement;\n", | ||||
|        "    if (targ.nodeType == 3) // defeat Safari bug\n", | ||||
|        "        targ = targ.parentNode;\n", | ||||
|        "\n", | ||||
|        "    // jQuery normalizes the pageX and pageY\n", | ||||
|        "    // pageX,Y are the mouse positions relative to the document\n", | ||||
|        "    // offset() returns the position of the element relative to the document\n", | ||||
|        "    var x = e.pageX - $(targ).offset().left;\n", | ||||
|        "    var y = e.pageY - $(targ).offset().top;\n", | ||||
|        "\n", | ||||
|        "    return {\"x\": x, \"y\": y};\n", | ||||
|        "};\n", | ||||
|        "\n", | ||||
|        "/*\n", | ||||
|        " * return a copy of an object with only non-object keys\n", | ||||
|        " * we need this to avoid circular references\n", | ||||
|        " * http://stackoverflow.com/a/24161582/3208463\n", | ||||
|        " */\n", | ||||
|        "function simpleKeys (original) {\n", | ||||
|        "  return Object.keys(original).reduce(function (obj, key) {\n", | ||||
|        "    if (typeof original[key] !== 'object')\n", | ||||
|        "        obj[key] = original[key]\n", | ||||
|        "    return obj;\n", | ||||
|        "  }, {});\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.mouse_event = function(event, name) {\n", | ||||
|        "    var canvas_pos = mpl.findpos(event)\n", | ||||
|        "\n", | ||||
|        "    if (name === 'button_press')\n", | ||||
|        "    {\n", | ||||
|        "        this.canvas.focus();\n", | ||||
|        "        this.canvas_div.focus();\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var x = canvas_pos.x * mpl.ratio;\n", | ||||
|        "    var y = canvas_pos.y * mpl.ratio;\n", | ||||
|        "\n", | ||||
|        "    this.send_message(name, {x: x, y: y, button: event.button,\n", | ||||
|        "                             step: event.step,\n", | ||||
|        "                             guiEvent: simpleKeys(event)});\n", | ||||
|        "\n", | ||||
|        "    /* This prevents the web browser from automatically changing to\n", | ||||
|        "     * the text insertion cursor when the button is pressed.  We want\n", | ||||
|        "     * to control all of the cursor setting manually through the\n", | ||||
|        "     * 'cursor' event from matplotlib */\n", | ||||
|        "    event.preventDefault();\n", | ||||
|        "    return false;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._key_event_extra = function(event, name) {\n", | ||||
|        "    // Handle any extra behaviour associated with a key event\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.key_event = function(event, name) {\n", | ||||
|        "\n", | ||||
|        "    // Prevent repeat events\n", | ||||
|        "    if (name == 'key_press')\n", | ||||
|        "    {\n", | ||||
|        "        if (event.which === this._key)\n", | ||||
|        "            return;\n", | ||||
|        "        else\n", | ||||
|        "            this._key = event.which;\n", | ||||
|        "    }\n", | ||||
|        "    if (name == 'key_release')\n", | ||||
|        "        this._key = null;\n", | ||||
|        "\n", | ||||
|        "    var value = '';\n", | ||||
|        "    if (event.ctrlKey && event.which != 17)\n", | ||||
|        "        value += \"ctrl+\";\n", | ||||
|        "    if (event.altKey && event.which != 18)\n", | ||||
|        "        value += \"alt+\";\n", | ||||
|        "    if (event.shiftKey && event.which != 16)\n", | ||||
|        "        value += \"shift+\";\n", | ||||
|        "\n", | ||||
|        "    value += 'k';\n", | ||||
|        "    value += event.which.toString();\n", | ||||
|        "\n", | ||||
|        "    this._key_event_extra(event, name);\n", | ||||
|        "\n", | ||||
|        "    this.send_message(name, {key: value,\n", | ||||
|        "                             guiEvent: simpleKeys(event)});\n", | ||||
|        "    return false;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n", | ||||
|        "    if (name == 'download') {\n", | ||||
|        "        this.handle_save(this, null);\n", | ||||
|        "    } else {\n", | ||||
|        "        this.send_message(\"toolbar_button\", {name: name});\n", | ||||
|        "    }\n", | ||||
|        "};\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n", | ||||
|        "    this.message.textContent = tooltip;\n", | ||||
|        "};\n", | ||||
|        "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", | ||||
|        "\n", | ||||
|        "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", | ||||
|        "\n", | ||||
|        "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n", | ||||
|        "    // Create a \"websocket\"-like object which calls the given IPython comm\n", | ||||
|        "    // object with the appropriate methods. Currently this is a non binary\n", | ||||
|        "    // socket, so there is still some room for performance tuning.\n", | ||||
|        "    var ws = {};\n", | ||||
|        "\n", | ||||
|        "    ws.close = function() {\n", | ||||
|        "        comm.close()\n", | ||||
|        "    };\n", | ||||
|        "    ws.send = function(m) {\n", | ||||
|        "        //console.log('sending', m);\n", | ||||
|        "        comm.send(m);\n", | ||||
|        "    };\n", | ||||
|        "    // Register the callback with on_msg.\n", | ||||
|        "    comm.on_msg(function(msg) {\n", | ||||
|        "        //console.log('receiving', msg['content']['data'], msg);\n", | ||||
|        "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n", | ||||
|        "        ws.onmessage(msg['content']['data'])\n", | ||||
|        "    });\n", | ||||
|        "    return ws;\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.mpl_figure_comm = function(comm, msg) {\n", | ||||
|        "    // This is the function which gets called when the mpl process\n", | ||||
|        "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n", | ||||
|        "\n", | ||||
|        "    var id = msg.content.data.id;\n", | ||||
|        "    // Get hold of the div created by the display call when the Comm\n", | ||||
|        "    // socket was opened in Python.\n", | ||||
|        "    var element = $(\"#\" + id);\n", | ||||
|        "    var ws_proxy = comm_websocket_adapter(comm)\n", | ||||
|        "\n", | ||||
|        "    function ondownload(figure, format) {\n", | ||||
|        "        window.open(figure.imageObj.src);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var fig = new mpl.figure(id, ws_proxy,\n", | ||||
|        "                           ondownload,\n", | ||||
|        "                           element.get(0));\n", | ||||
|        "\n", | ||||
|        "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", | ||||
|        "    // web socket which is closed, not our websocket->open comm proxy.\n", | ||||
|        "    ws_proxy.onopen();\n", | ||||
|        "\n", | ||||
|        "    fig.parent_element = element.get(0);\n", | ||||
|        "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n", | ||||
|        "    if (!fig.cell_info) {\n", | ||||
|        "        console.error(\"Failed to find cell for figure\", id, fig);\n", | ||||
|        "        return;\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    var output_index = fig.cell_info[2]\n", | ||||
|        "    var cell = fig.cell_info[0];\n", | ||||
|        "\n", | ||||
|        "};\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_close = function(fig, msg) {\n", | ||||
|        "    var width = fig.canvas.width/mpl.ratio\n", | ||||
|        "    fig.root.unbind('remove')\n", | ||||
|        "\n", | ||||
|        "    // Update the output cell to use the data from the current canvas.\n", | ||||
|        "    fig.push_to_output();\n", | ||||
|        "    var dataURL = fig.canvas.toDataURL();\n", | ||||
|        "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n", | ||||
|        "    // the notebook keyboard shortcuts fail.\n", | ||||
|        "    IPython.keyboard_manager.enable()\n", | ||||
|        "    $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n", | ||||
|        "    fig.close_ws(fig, msg);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.close_ws = function(fig, msg){\n", | ||||
|        "    fig.send_message('closing', msg);\n", | ||||
|        "    // fig.ws.close()\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n", | ||||
|        "    // Turn the data on the canvas into data in the output cell.\n", | ||||
|        "    var width = this.canvas.width/mpl.ratio\n", | ||||
|        "    var dataURL = this.canvas.toDataURL();\n", | ||||
|        "    this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.updated_canvas_event = function() {\n", | ||||
|        "    // Tell IPython that the notebook contents must change.\n", | ||||
|        "    IPython.notebook.set_dirty(true);\n", | ||||
|        "    this.send_message(\"ack\", {});\n", | ||||
|        "    var fig = this;\n", | ||||
|        "    // Wait a second, then push the new image to the DOM so\n", | ||||
|        "    // that it is saved nicely (might be nice to debounce this).\n", | ||||
|        "    setTimeout(function () { fig.push_to_output() }, 1000);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._init_toolbar = function() {\n", | ||||
|        "    var fig = this;\n", | ||||
|        "\n", | ||||
|        "    var nav_element = $('<div/>');\n", | ||||
|        "    nav_element.attr('style', 'width: 100%');\n", | ||||
|        "    this.root.append(nav_element);\n", | ||||
|        "\n", | ||||
|        "    // Define a callback function for later on.\n", | ||||
|        "    function toolbar_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onclick(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "    function toolbar_mouse_event(event) {\n", | ||||
|        "        return fig.toolbar_button_onmouseover(event['data']);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    for(var toolbar_ind in mpl.toolbar_items){\n", | ||||
|        "        var name = mpl.toolbar_items[toolbar_ind][0];\n", | ||||
|        "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", | ||||
|        "        var image = mpl.toolbar_items[toolbar_ind][2];\n", | ||||
|        "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n", | ||||
|        "\n", | ||||
|        "        if (!name) { continue; };\n", | ||||
|        "\n", | ||||
|        "        var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n", | ||||
|        "        button.click(method_name, toolbar_event);\n", | ||||
|        "        button.mouseover(tooltip, toolbar_mouse_event);\n", | ||||
|        "        nav_element.append(button);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "    // Add the status bar.\n", | ||||
|        "    var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n", | ||||
|        "    nav_element.append(status_bar);\n", | ||||
|        "    this.message = status_bar[0];\n", | ||||
|        "\n", | ||||
|        "    // Add the close button to the window.\n", | ||||
|        "    var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n", | ||||
|        "    var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n", | ||||
|        "    button.click(function (evt) { fig.handle_close(fig, {}); } );\n", | ||||
|        "    button.mouseover('Stop Interaction', toolbar_mouse_event);\n", | ||||
|        "    buttongrp.append(button);\n", | ||||
|        "    var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", | ||||
|        "    titlebar.prepend(buttongrp);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._root_extra_style = function(el){\n", | ||||
|        "    var fig = this\n", | ||||
|        "    el.on(\"remove\", function(){\n", | ||||
|        "\tfig.close_ws(fig, {});\n", | ||||
|        "    });\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._canvas_extra_style = function(el){\n", | ||||
|        "    // this is important to make the div 'focusable\n", | ||||
|        "    el.attr('tabindex', 0)\n", | ||||
|        "    // reach out to IPython and tell the keyboard manager to turn it's self\n", | ||||
|        "    // off when our div gets focus\n", | ||||
|        "\n", | ||||
|        "    // location in version 3\n", | ||||
|        "    if (IPython.notebook.keyboard_manager) {\n", | ||||
|        "        IPython.notebook.keyboard_manager.register_events(el);\n", | ||||
|        "    }\n", | ||||
|        "    else {\n", | ||||
|        "        // location in version 2\n", | ||||
|        "        IPython.keyboard_manager.register_events(el);\n", | ||||
|        "    }\n", | ||||
|        "\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype._key_event_extra = function(event, name) {\n", | ||||
|        "    var manager = IPython.notebook.keyboard_manager;\n", | ||||
|        "    if (!manager)\n", | ||||
|        "        manager = IPython.keyboard_manager;\n", | ||||
|        "\n", | ||||
|        "    // Check for shift+enter\n", | ||||
|        "    if (event.shiftKey && event.which == 13) {\n", | ||||
|        "        this.canvas_div.blur();\n", | ||||
|        "        // select the cell after this one\n", | ||||
|        "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", | ||||
|        "        IPython.notebook.select(index + 1);\n", | ||||
|        "    }\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "mpl.figure.prototype.handle_save = function(fig, msg) {\n", | ||||
|        "    fig.ondownload(fig, null);\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "\n", | ||||
|        "mpl.find_output_cell = function(html_output) {\n", | ||||
|        "    // Return the cell and output element which can be found *uniquely* in the notebook.\n", | ||||
|        "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", | ||||
|        "    // IPython event is triggered only after the cells have been serialised, which for\n", | ||||
|        "    // our purposes (turning an active figure into a static one), is too late.\n", | ||||
|        "    var cells = IPython.notebook.get_cells();\n", | ||||
|        "    var ncells = cells.length;\n", | ||||
|        "    for (var i=0; i<ncells; i++) {\n", | ||||
|        "        var cell = cells[i];\n", | ||||
|        "        if (cell.cell_type === 'code'){\n", | ||||
|        "            for (var j=0; j<cell.output_area.outputs.length; j++) {\n", | ||||
|        "                var data = cell.output_area.outputs[j];\n", | ||||
|        "                if (data.data) {\n", | ||||
|        "                    // IPython >= 3 moved mimebundle to data attribute of output\n", | ||||
|        "                    data = data.data;\n", | ||||
|        "                }\n", | ||||
|        "                if (data['text/html'] == html_output) {\n", | ||||
|        "                    return [cell, data, j];\n", | ||||
|        "                }\n", | ||||
|        "            }\n", | ||||
|        "        }\n", | ||||
|        "    }\n", | ||||
|        "}\n", | ||||
|        "\n", | ||||
|        "// Register the function which deals with the matplotlib target/channel.\n", | ||||
|        "// The kernel may be null if the page has been refreshed.\n", | ||||
|        "if (IPython.notebook.kernel != null) {\n", | ||||
|        "    IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", | ||||
|        "}\n" | ||||
|       ], | ||||
|       "text/plain": [ | ||||
|        "<IPython.core.display.Javascript object>" | ||||
|       ] | ||||
|      }, | ||||
|      "metadata": {}, | ||||
|      "output_type": "display_data" | ||||
|     }, | ||||
|     { | ||||
|      "data": { | ||||
|       "text/html": [ | ||||
|        "<div id='215b26ff-80fe-45a3-bb52-9351ea1b2ebb'></div>" | ||||
|       ], | ||||
|       "text/plain": [ | ||||
|        "<IPython.core.display.HTML object>" | ||||
|       ] | ||||
|      }, | ||||
|      "metadata": {}, | ||||
|      "output_type": "display_data" | ||||
|     }, | ||||
|     { | ||||
|      "data": { | ||||
|       "text/plain": [ | ||||
|        "(0, 100)" | ||||
|       ] | ||||
|      }, | ||||
|      "execution_count": 131, | ||||
|      "metadata": {}, | ||||
|      "output_type": "execute_result" | ||||
|     } | ||||
|    ], | ||||
|    "source": [ | ||||
|     "def h(p,src,dest):\n", | ||||
|     "    c = (((src-dest)**2).sum())**0.5\n", | ||||
|     "    a = (((dest-p)**2).sum())**0.5\n", | ||||
|     "    b = (((src-p)**2).sum())**0.5\n", | ||||
|     "    ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n", | ||||
|     "    return a-b\n", | ||||
|     "S=np.array([10,10])\n", | ||||
|     "D=np.array([20,25])\n", | ||||
|     "\n", | ||||
|     "Rx=range(100)\n", | ||||
|     "Ry=range(100)\n", | ||||
|     "grid=np.zeros((len(Rx),len(Ry)))\n", | ||||
|     "for px,x in enumerate(Rx):\n", | ||||
|     "    for py,y in enumerate(Ry):\n", | ||||
|     "        grid[px,py]=h(np.array([x,y]),S,D)\n", | ||||
|     "imshow(grid,cmap='coolwarm_r',origin='lower')\n", | ||||
|     "colorbar()\n", | ||||
|     "\n", | ||||
|     "scatter(*S,color='green')\n", | ||||
|     "scatter(*D,color='red')\n", | ||||
|     "plt.xlim(0,100)\n", | ||||
|     "plt.ylim(0,100)" | ||||
|    ] | ||||
|   }, | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": 38, | ||||
|    "metadata": {}, | ||||
|    "outputs": [ | ||||
|     { | ||||
|      "name": "stdout", | ||||
|      "output_type": "stream", | ||||
|      "text": [ | ||||
|       "Object `np.magnitude` not found.\n" | ||||
|      ] | ||||
|     } | ||||
|    ], | ||||
|    "source": [ | ||||
|     "np." | ||||
|    ] | ||||
|   }, | ||||
|   { | ||||
|    "cell_type": "code", | ||||
|    "execution_count": null, | ||||
|    "metadata": {}, | ||||
|    "outputs": [], | ||||
|    "source": [] | ||||
|   } | ||||
|  ], | ||||
|  "metadata": { | ||||
|   "kernelspec": { | ||||
|    "display_name": "Python 3.8.1 64-bit ('anaconda': conda)", | ||||
|    "language": "python", | ||||
|    "name": "python38164bitanacondaconda2a51168e890d45bd836f654eb2ae46f7" | ||||
|   }, | ||||
|   "language_info": { | ||||
|    "codemirror_mode": { | ||||
|     "name": "ipython", | ||||
|     "version": 3 | ||||
|    }, | ||||
|    "file_extension": ".py", | ||||
|    "mimetype": "text/x-python", | ||||
|    "name": "python", | ||||
|    "nbconvert_exporter": "python", | ||||
|    "pygments_lexer": "ipython3", | ||||
|    "version": "3.8.1" | ||||
|   } | ||||
|  }, | ||||
|  "nbformat": 4, | ||||
|  "nbformat_minor": 4 | ||||
| } | ||||
							
								
								
									
										49
									
								
								icon/make.py
									
										
									
									
									
								
							
							
						
						
									
										49
									
								
								icon/make.py
									
										
									
									
									
								
							|  | @ -1,6 +1,9 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| import svgwrite | ||||
| import svgpathtools | ||||
| import random | ||||
| import tempfile | ||||
| import os | ||||
| from math import sin, cos, pi | ||||
| import tsp as m_tsp | ||||
| 
 | ||||
|  | @ -30,8 +33,6 @@ def make_points(n, size, min_dist=0): | |||
|         px, py = random.random(), random.random() | ||||
|         px *= size / 2 | ||||
|         py *= size / 2 | ||||
|         px += 70 | ||||
|         py += 70 | ||||
|         valid = True | ||||
|         for p in points: | ||||
|             if dist2(p, (px, py)) < min_dist: | ||||
|  | @ -60,9 +61,9 @@ def generate(seed, name=None, small=False): | |||
|     size = 1000 | ||||
|     if name is None: | ||||
|         name = seed | ||||
|     dwg = svgwrite.Drawing(filename="out/{}.svg".format(name)) | ||||
|     dwg.defs.add(dwg.style(".background { fill: #222; }")) | ||||
|     dwg.add(dwg.rect(size=("100%", "100%"), class_="background")) | ||||
|     out_path = "out/{}.svg".format(name) | ||||
|     dwg = svgwrite.Drawing(filename=out_path) | ||||
|     dwg.defs.add(dwg.style(".background { fill: #222 }")) | ||||
|     print("Generating points...") | ||||
|     color = "#eee" | ||||
|     pos = make_points(num_points, size, min_dist=min_dist) | ||||
|  | @ -74,12 +75,7 @@ def generate(seed, name=None, small=False): | |||
|             x2 /= sd | ||||
|             y1 /= sd | ||||
|             y2 /= sd | ||||
|         dwg.add(dwg.line( | ||||
|             (x1, y1), | ||||
|             (x2, y2), | ||||
|             stroke_width=w, | ||||
|             stroke=color | ||||
|         )) | ||||
|         dwg.add(dwg.line((x1, y1), (x2, y2), stroke_width=w, stroke=color)) | ||||
| 
 | ||||
|     for (px, py) in pos: | ||||
|         base_r = 3 | ||||
|  | @ -111,17 +107,13 @@ def generate(seed, name=None, small=False): | |||
|                 random.random() | ||||
|                 random.random() | ||||
|                 random.random() | ||||
|                 random.random() | ||||
|                 continue | ||||
|             r += ring_step(random.random()) | ||||
|             ring_col = color | ||||
|             if random.random() > 0.75: | ||||
|                 ring_col = "#ea0" | ||||
|             circ = dwg.add(dwg.circle( | ||||
|                 (px, py), | ||||
|                 r=r, | ||||
|                 stroke_width=w, | ||||
|                 stroke=ring_col | ||||
|             )) | ||||
|             circ = dwg.add(dwg.circle((px, py), r=r, stroke_width=w, stroke=ring_col)) | ||||
|             circ.fill(color, opacity=0) | ||||
|             d = random.random() * pi * 2 | ||||
|             dx = cos(d) | ||||
|  | @ -136,10 +128,27 @@ def generate(seed, name=None, small=False): | |||
|                 ) | ||||
|             ) | ||||
|             moon.fill(ring_col) | ||||
| 
 | ||||
|     dwg.save() | ||||
|     dwg.fit() | ||||
|     path = tempfile.TemporaryDirectory() | ||||
|     filename = os.path.join(path.name, "out.svg") | ||||
|     dwg.saveas(filename) | ||||
|     paths, attrs = svgpathtools.svg2paths(filename) | ||||
|     bbox = [float("inf"), float("-inf"), float("inf"), float("-inf")] | ||||
|     for path in paths: | ||||
|         path_bbox = path.bbox() | ||||
|         bbox[0] = min(bbox[0], path_bbox[0])  # xmin | ||||
|         bbox[1] = max(bbox[1], path_bbox[1])  # xmax | ||||
|         bbox[2] = min(bbox[2], path_bbox[2])  # ymin | ||||
|         bbox[3] = max(bbox[3], path_bbox[3])  # ymax | ||||
|     px = bbox[0] | ||||
|     sx = (bbox[1] - bbox[0]) | ||||
|     py = bbox[2] | ||||
|     sy = (bbox[3] - bbox[2]) | ||||
|     dwg.add(dwg.rect(x=px, y=px, size=(sx, sy), class_="background")) | ||||
|     dwg.elements.insert(1, dwg.elements.pop(-1)) | ||||
|     dwg.saveas(out_path) | ||||
| 
 | ||||
| 
 | ||||
| seed = -4 | ||||
| seed = -5 | ||||
| generate(seed, "icon_1", small=False) | ||||
| generate(seed, "icon_1_small", small=True) | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								icon/out/icon_1.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icon/out/icon_1.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 83 KiB | 
|  | @ -1,2 +1,2 @@ | |||
| <?xml version="1.0" encoding="utf-8" ?> | ||||
| <svg baseProfile="full" height="100%" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222; }]]></style></defs><rect class="background" height="100%" width="100%" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="188.02404486871725" x2="103.25754783979495" y1="121.5830171153579" y2="270.7955072425374" /><line stroke="#eee" stroke-width="2" x1="103.25754783979495" x2="528.9775215438594" y1="270.7955072425374" y2="470.2261757479043" /><line stroke="#eee" stroke-width="2" x1="528.9775215438594" x2="452.58130125271924" y1="470.2261757479043" y2="180.96408784515882" /><line stroke="#eee" stroke-width="2" x1="452.58130125271924" x2="338.3400040874068" y1="180.96408784515882" y2="208.34132172072512" /><circle cx="188.02404486871725" cy="121.5830171153579" fill="#eee" r="3.3185498772945903" stroke="#eee" stroke-width="2" /><circle cx="188.02404486871725" cy="121.5830171153579" fill="#eee" fill-opacity="0" r="22.429593602379832" stroke="#eee" stroke-width="2" /><circle cx="173.80179554276944" cy="104.23901834695114" fill="#eee" r="2.52050830126476" stroke="#eee" stroke-width="2" /><circle cx="103.25754783979495" cy="270.7955072425374" fill="#eee" r="3.4944547782059745" stroke="#eee" stroke-width="2" /><circle cx="103.25754783979495" cy="270.7955072425374" fill="#eee" fill-opacity="0" r="19.2697560241313" stroke="#eee" stroke-width="2" /><circle cx="115.03444741085107" cy="255.5433554690071" fill="#eee" r="3.7601015027223985" stroke="#eee" stroke-width="2" /><circle cx="103.25754783979495" cy="270.7955072425374" fill="#eee" fill-opacity="0" r="30.13693855048052" stroke="#eee" stroke-width="2" /><circle cx="89.02212054970202" cy="244.23260689141011" fill="#eee" r="3.0119075514208307" stroke="#eee" stroke-width="2" /><circle cx="528.9775215438594" cy="470.2261757479043" fill="#eee" r="4.420763662755435" stroke="#eee" stroke-width="2" /><circle cx="528.9775215438594" cy="470.2261757479043" fill="#eee" fill-opacity="0" r="22.44577790309402" stroke="#ea0" stroke-width="2" /><circle cx="549.9596596985477" cy="462.25354606049257" fill="#ea0" r="3.680925358835544" stroke="#ea0" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" r="3.8758250081323116" stroke="#eee" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" fill-opacity="0" r="21.8231723987879" stroke="#ea0" stroke-width="2" /><circle cx="430.78831758434035" cy="179.8166052191519" fill="#ea0" r="2.827892086263464" stroke="#ea0" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" fill-opacity="0" r="37.812297120687795" stroke="#eee" stroke-width="2" /><circle cx="472.57653937753463" cy="213.0570818761791" fill="#eee" r="2.6102231928654778" stroke="#eee" stroke-width="2" /><circle cx="452.58130125271924" cy="180.96408784515882" fill="#eee" fill-opacity="0" r="55.938220307034" stroke="#eee" stroke-width="2" /><circle cx="506.1669380410402" cy="197.01600427617765" fill="#eee" r="3.252701491079807" stroke="#eee" stroke-width="2" /><circle cx="338.3400040874068" cy="208.34132172072512" fill="#eee" r="4.603865384638267" stroke="#eee" stroke-width="2" /><circle cx="338.3400040874068" cy="208.34132172072512" fill="#eee" fill-opacity="0" r="20.00878719559634" stroke="#eee" stroke-width="2" /><circle cx="329.11968037233845" cy="190.58358550160054" fill="#eee" r="2.132876938772122" stroke="#eee" stroke-width="2" /><circle cx="338.3400040874068" cy="208.34132172072512" fill="#eee" fill-opacity="0" r="39.144105385654704" stroke="#eee" stroke-width="2" /><circle cx="301.84139133159863" cy="222.48742554279568" fill="#eee" r="2.3674072974299003" stroke="#eee" stroke-width="2" /></svg> | ||||
| <svg baseProfile="full" height="100%" preserveAspectRatio="xMidYMid meet" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222 }]]></style></defs><rect class="background" height="517.2983884727208" width="530.2849722660202" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="311.450847444851" x2="14.502614141807369" y1="370.89349463036467" y2="232.81132718905266" /><line stroke="#eee" stroke-width="2" x1="14.502614141807369" x2="450.45024587531134" y1="232.81132718905266" y2="56.60298232657218" /><line stroke="#eee" stroke-width="2" x1="450.45024587531134" x2="471.67835849915684" y1="56.60298232657218" y2="324.4872765684621" /><line stroke="#eee" stroke-width="2" x1="471.67835849915684" x2="397.5967827828483" y1="324.4872765684621" y2="471.2251418885252" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" r="3.739718497859491" stroke="#eee" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="14.019744021739154" stroke="#eee" stroke-width="2" /><circle cx="309.7972050350797" cy="356.9716165524724" fill="#eee" r="2.816302109949028" stroke="#eee" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="25.84050065951694" stroke="#ea0" stroke-width="2" /><circle cx="291.5987578747967" cy="387.4351394711004" fill="#ea0" r="3.5238508551887717" stroke="#ea0" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="36.559818016667215" stroke="#eee" stroke-width="2" /><circle cx="277.06522171012307" cy="383.3131981792842" fill="#eee" r="2.2644817084338493" stroke="#eee" stroke-width="2" /><circle cx="14.502614141807369" cy="232.81132718905266" fill="#0ae" r="3.015939411732324" stroke="#0ae" stroke-width="2" /><circle cx="14.502614141807369" cy="232.81132718905266" fill="#eee" fill-opacity="0" r="22.601271002174997" stroke="#eee" stroke-width="2" /><circle cx="25.825648341003884" cy="252.3716530410824" fill="#eee" r="2.6273027354699074" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" r="5.628356799732829" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="15.047795145337929" stroke="#ea0" stroke-width="2" /><circle cx="444.96726006047606" cy="42.5896670410885" fill="#ea0" r="3.933128624634391" stroke="#ea0" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="33.98521192110272" stroke="#eee" stroke-width="2" /><circle cx="428.59208164282967" cy="82.62634271119818" fill="#eee" r="2.331912114259491" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="45.44223101650954" stroke="#eee" stroke-width="2" /><circle cx="436.0392802753398" cy="99.69962291762357" fill="#eee" r="3.2062199948153087" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" r="5.033802748643073" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" fill-opacity="0" r="13.660224505510772" stroke="#eee" stroke-width="2" /><circle cx="466.96528173978857" cy="337.3086899461385" fill="#eee" r="3.3928396152823135" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" fill-opacity="0" r="25.507956906495714" stroke="#eee" stroke-width="2" /><circle cx="483.09849262326327" cy="347.2959679410741" fill="#eee" r="2.5123636718854243" stroke="#eee" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#0ae" r="5.847661430011579" stroke="#0ae" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#eee" fill-opacity="0" r="21.448808893881296" stroke="#eee" stroke-width="2" /><circle cx="402.63513027636975" cy="450.3764858766675" fill="#eee" r="2.7323689516837213" stroke="#eee" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#eee" fill-opacity="0" r="37.23399718445004" stroke="#eee" stroke-width="2" /><circle cx="433.2375240152304" cy="482.0004892489591" fill="#eee" r="2.3618389759020957" stroke="#eee" stroke-width="2" /></svg> | ||||
| Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icon/out/icon_1_pad.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icon/out/icon_1_pad.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 64 KiB | 
|  | @ -1,2 +1,2 @@ | |||
| <?xml version="1.0" encoding="utf-8" ?> | ||||
| <svg baseProfile="full" height="100%" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222; }]]></style></defs><rect class="background" height="100%" width="100%" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="94.01202243435863" x2="51.628773919897476" y1="60.79150855767895" y2="135.3977536212687" /><line stroke="#eee" stroke-width="2" x1="51.628773919897476" x2="264.4887607719297" y1="135.3977536212687" y2="235.11308787395214" /><line stroke="#eee" stroke-width="2" x1="264.4887607719297" x2="226.29065062635962" y1="235.11308787395214" y2="90.48204392257941" /><line stroke="#eee" stroke-width="2" x1="226.29065062635962" x2="169.1700020437034" y1="90.48204392257941" y2="104.17066086036256" /><circle cx="94.01202243435863" cy="60.79150855767895" fill="#eee" r="5.53091646215765" stroke="#eee" stroke-width="2" /><circle cx="51.628773919897476" cy="135.3977536212687" fill="#eee" r="6.358738772326545" stroke="#eee" stroke-width="2" /><circle cx="264.4887607719297" cy="235.11308787395214" fill="#0ae" r="9.400253756805997" stroke="#0ae" stroke-width="2" /><circle cx="226.29065062635962" cy="90.48204392257941" fill="#eee" r="6.236611100792434" stroke="#eee" stroke-width="2" /><circle cx="169.1700020437034" cy="104.17066086036256" fill="#eee" r="9.41158619939395" stroke="#eee" stroke-width="2" /></svg> | ||||
| <svg baseProfile="full" height="100%" preserveAspectRatio="xMidYMid meet" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222 }]]></style></defs><rect class="background" height="246.4377768305505" width="267.0041091126337" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="155.7254237224255" x2="7.251307070903684" y1="185.44674731518234" y2="116.40566359452633" /><line stroke="#eee" stroke-width="2" x1="7.251307070903684" x2="225.22512293765567" y1="116.40566359452633" y2="28.30149116328609" /><line stroke="#eee" stroke-width="2" x1="225.22512293765567" x2="235.83917924957842" y1="28.30149116328609" y2="162.24363828423105" /><line stroke="#eee" stroke-width="2" x1="235.83917924957842" x2="198.79839139142416" y1="162.24363828423105" y2="235.6125709442626" /><circle cx="155.7254237224255" cy="185.44674731518234" fill="#eee" r="6.232864163099151" stroke="#eee" stroke-width="2" /><circle cx="7.251307070903684" cy="116.40566359452633" fill="#0ae" r="5.026565686220541" stroke="#0ae" stroke-width="2" /><circle cx="225.22512293765567" cy="28.30149116328609" fill="#eee" r="9.380594666221384" stroke="#eee" stroke-width="2" /><circle cx="235.83917924957842" cy="162.24363828423105" fill="#eee" r="8.389671247738455" stroke="#eee" stroke-width="2" /><circle cx="198.79839139142416" cy="235.6125709442626" fill="#0ae" r="9.74610238335263" stroke="#0ae" stroke-width="2" /></svg> | ||||
| Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										132
									
								
								imgui_test/test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								imgui_test/test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,132 @@ | |||
| import dearpygui.core as dpg | ||||
| import dearpygui.simple as sdpg | ||||
| import uuid | ||||
| import logging | ||||
| from concurrent.futures import ProcessPoolExecutor | ||||
| 
 | ||||
| 
 | ||||
| def setup_logging(loglevel="INFO"): | ||||
|     import coloredlogs | ||||
|     from datetime import timedelta | ||||
| 
 | ||||
|     coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"} | ||||
|     coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"} | ||||
| 
 | ||||
|     class DeltaTimeFormatter(coloredlogs.ColoredFormatter): | ||||
|         def format(self, record): | ||||
|             seconds = record.relativeCreated / 1000 | ||||
|             duration = timedelta(seconds=seconds) | ||||
|             record.delta = str(duration) | ||||
|             return super().format(record) | ||||
| 
 | ||||
|     coloredlogs.ColoredFormatter = DeltaTimeFormatter | ||||
|     logfmt = " | ".join( | ||||
|         ["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"] | ||||
|     ) | ||||
|     numeric_level = getattr(logging, loglevel.upper(), None) | ||||
|     if not isinstance(numeric_level, int): | ||||
|         raise ValueError("Invalid log level: %s" % loglevel) | ||||
|     coloredlogs.install(level=numeric_level, fmt=logfmt) | ||||
| 
 | ||||
| 
 | ||||
| setup_logging() | ||||
| 
 | ||||
| def resolve_systems(stars, *args): | ||||
|     import _ed_lrr | ||||
| 
 | ||||
|     r = _ed_lrr.PyRouter(None) | ||||
|     r.load(stars) | ||||
|     return r.resolve_systems(*args) | ||||
| 
 | ||||
| 
 | ||||
| class EdLrrGui: | ||||
|     def __init__(self): | ||||
|         self.pool = ProcessPoolExecutor(1) | ||||
|         self.systems = [] | ||||
|         self.__resolve_job = None | ||||
|         self.logger = logging.getLogger("GUI") | ||||
| 
 | ||||
|     def __set_state(self, state): | ||||
|         print(state) | ||||
|         dpg.set_value("ed_lrr_state", state) | ||||
| 
 | ||||
|     def __resolve_done(self, fut): | ||||
|         data = fut.result() | ||||
|         self.logger.info(f"Gor resolver data back: {data}") | ||||
|         self.__resolve_job = None | ||||
|         for n, system in enumerate(self.systems): | ||||
|             if system["name"] in data: | ||||
|                 self.systems[n] = data[system["name"]] | ||||
|                 self.systems[n]["resolved"] = True | ||||
| 
 | ||||
|     def __resolve_systems(self, sender, data): | ||||
|         names = [] | ||||
|         for system in self.systems: | ||||
|             if not system.get("resolver", False): | ||||
|                 names.append(system["name"]) | ||||
|         if self.__resolve_job is None: | ||||
|             job = self.pool.submit(resolve_systems, "../stars.csv", *names) | ||||
|             self.logger.info(f"Resolving {len(names)} systems...") | ||||
|             self.__resolve_job = job | ||||
|             self.__resolve_job.add_done_callback(self.__resolve_done) | ||||
| 
 | ||||
|     def __render(self, sender, data): | ||||
|         dpg.clear_table("Systems") | ||||
|         for system in self.systems: | ||||
|             row = [ | ||||
|                 system.get("id", ""), | ||||
|                 system["name"], | ||||
|                 ", ".join(map(str, system.get("pos", []))), | ||||
|             ] | ||||
|             dpg.add_row("Systems", row) | ||||
| 
 | ||||
|     def __add_system(self, sender, data): | ||||
|         system_name = dpg.get_value("sys-name") | ||||
|         self.systems.append({"name": system_name}) | ||||
|         dpg.set_value("sys-name", "") | ||||
| 
 | ||||
|     def __select_system(self, sender, data): | ||||
|         system_row = dpg.get_table_selections("Systems") | ||||
|         idx = system_row[0][0] | ||||
|         dpg.add_data("selected-system-index", idx) | ||||
| 
 | ||||
|     def __remove_system(self, sender, data): | ||||
|         if self.systems: | ||||
|             system_index = dpg.get_data("selected-system-index") | ||||
|             self.systems.pop(system_index) | ||||
| 
 | ||||
|     def __clear_systems(self, sender, data): | ||||
|         self.systems = [] | ||||
| 
 | ||||
|     def show(self): | ||||
|         with sdpg.window("Main Window"): | ||||
|             dpg.set_main_window_size(550, 550) | ||||
|             dpg.set_main_window_resizable(False) | ||||
|             dpg.set_main_window_title("Elite: Dangerous Long Range Router") | ||||
| 
 | ||||
|             dpg.add_text("ED_LRR") | ||||
|             dpg.add_separator() | ||||
| 
 | ||||
|             dpg.add_input_text("System name", source="sys-name") | ||||
|             dpg.add_button("Add", callback=self.__add_system) | ||||
|             dpg.add_separator() | ||||
| 
 | ||||
|             dpg.add_table( | ||||
|                 "Systems", | ||||
|                 ["ID", "Name", "Position"], | ||||
|                 height=200, | ||||
|                 callback=self.__select_system, | ||||
|             ) | ||||
|             dpg.add_separator() | ||||
|             dpg.add_button("Remove", callback=self.__remove_system) | ||||
|             dpg.add_button("Clear", callback=self.__clear_systems) | ||||
|             dpg.add_button("Resolve", callback=self.__resolve_systems) | ||||
| 
 | ||||
|             # Render Callback and Start gui | ||||
|         dpg.set_render_callback(self.__render) | ||||
|         dpg.start_dearpygui(primary_window="Main Window") | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     edlrr_gui = EdLrrGui() | ||||
|     edlrr_gui.show() | ||||
							
								
								
									
										1
									
								
								logs/route_log_beam_0.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								logs/route_log_beam_0.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| {"route": [], "dt": 292.124997} | ||||
							
								
								
									
										839494
									
								
								logs/route_log_beam_0.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										839494
									
								
								logs/route_log_beam_0.txt
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -28,14 +28,14 @@ versions += ["3"] | |||
| nox.options.keywords = "test" | ||||
| 
 | ||||
| 
 | ||||
| @nox.session(venv_backend="conda") | ||||
| @nox.session(python=versions,venv_backend="conda") | ||||
| def devenv(session): | ||||
|     """Set up development environment""" | ||||
|     global path | ||||
|     location = os.path.abspath(session._runner.venv.location_name) | ||||
|     session.env["PATH"] = os.pathsep.join([location, path, location]) | ||||
|     session.conda_install("pycrypto", "ujson") | ||||
|     session.install("--no-cache-dir", "-e",".[all]") | ||||
|     session.install("--no-cache-dir", "-e",".") | ||||
|     logger.warning(f'Devenv set up, now run "conda activate {location}"') | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										64
									
								
								process_route_log.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								process_route_log.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| // cargo-deps: crossbeam-channel="0.5.1"
 | ||||
| extern crate crossbeam_channel; | ||||
| use std::io::{BufReader,BufRead, BufWriter,Write}; | ||||
| use std::fs::File; | ||||
| use std::collections::HashMap; | ||||
| use std::collections::HashSet; | ||||
| use crossbeam_channel::unbounded; | ||||
| use crossbeam_channel::Receiver; | ||||
| use std::thread; | ||||
| fn process(rx: Receiver<String>) -> (HashMap<usize,usize>,HashMap<usize,usize>) { | ||||
|     let mut hm_min: HashMap<usize,usize> = HashMap::new(); | ||||
|     let mut hm_max: HashMap<usize,usize> = HashMap::new(); | ||||
|     while let Ok(line) = rx.recv() { | ||||
|         let line: Vec<usize> = line.split(",").map(|s| s.parse::<usize>().unwrap()).collect(); | ||||
|         let id=line[0]; | ||||
|         let mut depth=line[1]; | ||||
|         hm_min.entry(id).and_modify(|e| { | ||||
|             *e=*e.min(&mut depth); | ||||
|         }).or_insert(depth); | ||||
|         hm_max.entry(id).and_modify(|e| { | ||||
|             *e=*e.max(&mut depth); | ||||
|         }).or_insert(depth); | ||||
|     } | ||||
|     (hm_min,hm_max) | ||||
| } | ||||
| fn main() { | ||||
|     let (tx,rx) = unbounded(); | ||||
|     let mut threads: Vec<_> = (0..8).map(|_| { | ||||
|         let rx=rx.clone(); | ||||
|         thread::spawn(|| { | ||||
|             process(rx) | ||||
|         }) | ||||
|     }).collect(); | ||||
|     let fh = BufReader::new(File::open(std::env::args().nth(1).unwrap()).unwrap()); | ||||
|     fh.lines().flatten().for_each(|line| { | ||||
|         tx.send(line).unwrap(); | ||||
|     }); | ||||
|     drop(tx); | ||||
|     let mut hm_min: HashMap<usize,usize> = HashMap::new(); | ||||
|     let mut hm_max: HashMap<usize,usize> = HashMap::new(); | ||||
|     for thread in threads.drain(..) { | ||||
|         let (min,max)=thread.join().unwrap(); | ||||
|         println!("Thread: {:?}",(min.len(),max.len())); | ||||
|         for (id,depth) in min { | ||||
|             hm_min.entry(id).and_modify(|e| { | ||||
|                 *e=(*e).min(depth); | ||||
|             }).or_insert(depth); | ||||
|         } | ||||
|         for (id,depth) in max { | ||||
|             hm_max.entry(id).and_modify(|e| { | ||||
|                 *e=(*e).max(depth); | ||||
|             }).or_insert(depth); | ||||
|         } | ||||
|     } | ||||
|     println!("Final: {:?}",(hm_min.len(),hm_max.len())); | ||||
|     let mut fh_max = BufWriter::new(File::create("route_log_max.txt").unwrap()); | ||||
|     for (id,depth) in hm_max { | ||||
|         write!(fh_max,"{},{}\n",id,depth).unwrap(); | ||||
|     } | ||||
|     let mut fh_min = BufWriter::new(File::create("route_log_min.txt").unwrap()); | ||||
|     for (id,depth) in hm_min { | ||||
|         write!(fh_min,"{},{}\n",id,depth).unwrap(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										79
									
								
								render_heatmap_datashader.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								render_heatmap_datashader.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| import sys | ||||
| import datashader as ds | ||||
| import pandas as pd | ||||
| import datashader.transfer_functions as tf | ||||
| from datashader.utils import export_image | ||||
| from datashader.transfer_functions import set_background | ||||
| import subprocess as SP | ||||
| import os | ||||
| import itertools as ITT | ||||
| from glob import glob | ||||
| 
 | ||||
| print("Loading stars...") | ||||
| stars = pd.read_csv("stars.csv", usecols=["id", "x", "z", "mult"], index_col=0) | ||||
| stars.loc[stars.mult == 1.0, "mult"] = float("nan") | ||||
| 
 | ||||
| steps = int(sys.argv[1]) | ||||
| size = 1080 | ||||
| mode = "eq_hist" | ||||
| 
 | ||||
| cvs = ds.Canvas(plot_width=size, plot_height=size) | ||||
| 
 | ||||
| print("Plotting density") | ||||
| density_agg = cvs.points(stars, "x", "z") | ||||
| density = tf.shade(density_agg, cmap=["black", "white"], how=mode) | ||||
| 
 | ||||
| print("Plotting neutrons") | ||||
| neutrons_agg = cvs.points(stars, "x", "z", agg=ds.count("mult")) | ||||
| neutrons = tf.shade(neutrons_agg, cmap=["darkblue", "lightblue"], how=mode) | ||||
| 
 | ||||
| base = tf.stack(density, neutrons) | ||||
| 
 | ||||
| # ffplay = SP.Popen([ | ||||
| #     "ffplay","-f","image2pipe","-" | ||||
| # ],stdin=SP.PIPE,bufsize=0) | ||||
| 
 | ||||
| 
 | ||||
| for rh_fn in ITT.chain.from_iterable(map(glob, sys.argv[2:])): | ||||
|     basename = os.path.splitext(os.path.split(rh_fn)[-1])[0] | ||||
|     filename = "img/{}_{}_{}.mkv".format(basename, size, mode) | ||||
|     ffmpeg = SP.Popen( | ||||
|         [ | ||||
|             "ffmpeg", | ||||
|             "-y", | ||||
|             "-f", | ||||
|             "image2pipe", | ||||
|             "-i", | ||||
|             "-", | ||||
|             "-crf", | ||||
|             "17", | ||||
|             "-r", | ||||
|             "25", | ||||
|             "-pix_fmt", | ||||
|             "yuv420p", | ||||
|             filename, | ||||
|         ], | ||||
|         stdin=SP.PIPE, | ||||
|         bufsize=0, | ||||
|     ) | ||||
| 
 | ||||
|     print("Loading", rh_fn) | ||||
|     route_hist = pd.read_csv( | ||||
|         rh_fn, names=["id", "d"], index_col=0, dtype={"d": int}, low_memory=False, | ||||
|     ) | ||||
|     exp_span = [route_hist.d.min(), route_hist.d.max()] | ||||
|     stars["d"] = float("nan") | ||||
|     rng = range(route_hist.d.min(), route_hist.d.max() + 1, steps) | ||||
|     if steps == 0: | ||||
|         rng = [route_hist.d.max() + 1] | ||||
|     for n in rng: | ||||
|         stars['d'] = route_hist[route_hist.d < n]  # slow | ||||
|         explored_agg = cvs.points(stars, "x", "z", agg=ds.mean("d"))  # slow | ||||
|         explored = tf.shade( | ||||
|             explored_agg, cmap=["darkred", "lightpink"], how="linear", span=exp_span | ||||
|         ) | ||||
|         img = set_background(tf.stack(base, explored), "black").to_pil() | ||||
|         img.save(ffmpeg.stdin, "png") | ||||
|         img.save("img/current.png") | ||||
|     ffmpeg.stdin.close() | ||||
|     ffmpeg.wait() | ||||
							
								
								
									
										79
									
								
								render_heatmap_img_vaex.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								render_heatmap_img_vaex.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| import pandas as pd | ||||
| import vaex as vx | ||||
| from PIL import Image, ImageDraw, ImageFont | ||||
| from skimage import exposure | ||||
| from skimage.util import img_as_ubyte | ||||
| import numpy as np | ||||
| from matplotlib import cm | ||||
| import sys | ||||
| 
 | ||||
| base_size = 1080, 1920 | ||||
| 
 | ||||
| 
 | ||||
| def scale_to(width=None, height=None): | ||||
|     isnone = (width is None, height is None) | ||||
|     ret = { | ||||
|         (False, False): lambda w, h: (w, h), | ||||
|         (True, True): lambda w, h: (width, height), | ||||
|         (False, True): lambda w, h: (width, width * (h / w)), | ||||
|         (True, False): lambda w, h: (height * (w / h), height), | ||||
|     } | ||||
|     return lambda *args: tuple(map(int, ret[isnone](*args))) | ||||
| 
 | ||||
| 
 | ||||
| # xz -1 1 | ||||
| bining = { | ||||
|     ("zx", -1, 1): scale_to(width=base_size[0]),  # main view, top down | ||||
|     # ('yx',1,1): lambda size,w,h: (size,int(size*(w/h))), # | ||||
|     # ('zy',-1,1): lambda size,w,h: (int(size*(h/w)),size), # | ||||
| } | ||||
| 
 | ||||
| print("Loading stars.csv") | ||||
| stars = pd.read_csv( | ||||
|     "stars.csv", | ||||
|     names=["id", "name", "num_bodies", "has_scoopable", "mult", "x", "y", "z"], | ||||
|     usecols=["id", "num_bodies", "x", "y", "z", "mult"], | ||||
|     index_col=0, | ||||
| ) | ||||
| stars = vx.from_pandas(stars, copy_index=False) | ||||
| 
 | ||||
| filename = "heuristic.png" | ||||
| fnt = ImageFont.truetype(r"FiraCode-Regular", 40) | ||||
| 
 | ||||
| for (binby_key, m1, m2), calcshape in bining.items(): | ||||
|     binby = [m1 * stars[binby_key[0]], m2 * stars[binby_key[1]]] | ||||
| 
 | ||||
|     mm = [binby[0].minmax(), binby[1].minmax()] | ||||
| 
 | ||||
|     w, h = [mm[0][1] - mm[0][0], mm[1][1] - mm[1][0]] | ||||
|     shape = calcshape(w, h) | ||||
|     hm_all = stars.sum("num_bodies", binby=binby, shape=shape, limits="minmax") | ||||
|     hm_all_mask = hm_all != 0 | ||||
|     hm_all = exposure.equalize_hist(hm_all) | ||||
|     hm_all -= hm_all.min() | ||||
|     hm_all /= hm_all.max() | ||||
| 
 | ||||
|     hm_boost = stars.sum( | ||||
|         "astype(mult>1.0,'int')", binby=binby, shape=shape, limits="minmax" | ||||
|     ) | ||||
|     hm_boost_mask = hm_boost != 0 | ||||
| 
 | ||||
|     hm_boost = exposure.equalize_hist(hm_boost) | ||||
|     hm_boost -= hm_boost.min() | ||||
|     hm_boost /= hm_boost.max() | ||||
| 
 | ||||
|     # R = cm.Reds_r() | ||||
|     G = cm.Greens_r(hm_all) | ||||
|     B = cm.Blues_r(hm_boost) | ||||
| 
 | ||||
|     img = np.zeros((base_size[0], base_size[1], 4)) | ||||
|     img[:, :, :] = 0.0 | ||||
|     img[:, :, 3] = 1.0 | ||||
|     canvas = img[: shape[0], : shape[1], :] | ||||
|     canvas[hm_all_mask] = G[hm_all_mask] | ||||
|     canvas[hm_boost_mask] = B[hm_boost_mask] | ||||
|     pil_img = Image.fromarray(img_as_ubyte(img)) | ||||
|     draw = ImageDraw.Draw(pil_img) | ||||
|     messages = ["Hello World"] | ||||
|     draw.multiline_text((shape[0], 0), "\n".join(messages), font=fnt) | ||||
|     pil_img.save(filename) | ||||
							
								
								
									
										208
									
								
								render_heatmap_vid_vaex.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								render_heatmap_vid_vaex.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,208 @@ | |||
| import pandas as pd | ||||
| import vaex as vx | ||||
| import json | ||||
| from PIL import Image, ImageDraw, ImageFont | ||||
| from skimage import exposure | ||||
| from skimage.io import imsave | ||||
| from skimage.util import img_as_ubyte | ||||
| import numpy as np | ||||
| from matplotlib import cm | ||||
| import subprocess as SP | ||||
| import os | ||||
| import sys | ||||
| import gc | ||||
| from datetime import timedelta | ||||
| import itertools as ITT | ||||
| from glob import glob | ||||
| 
 | ||||
| base_size = 1080, 1920 | ||||
| steps = 1 | ||||
| framerate = 25 | ||||
| 
 | ||||
| rh_fn = sys.argv[1] | ||||
| 
 | ||||
| 
 | ||||
| def scale_to(width=None, height=None): | ||||
|     isnone = (width is None, height is None) | ||||
|     ret = { | ||||
|         (False, False): lambda w, h: (w, h), | ||||
|         (True, True): lambda w, h: (width, height), | ||||
|         (False, True): lambda w, h: (width, width * (h / w)), | ||||
|         (True, False): lambda w, h: (height * (w / h), height), | ||||
|     } | ||||
|     return lambda *args: tuple(map(int, ret[isnone](*args))) | ||||
| 
 | ||||
| 
 | ||||
| # xz -1 1 | ||||
| bining = { | ||||
|     ("zx", -1, 1): scale_to(width=base_size[0]),  # main view, top down | ||||
|     # ('yx',1,1): lambda size,w,h: (size,int(size*(w/h))), # | ||||
|     # ('zy',-1,1): lambda size,w,h: (int(size*(h/w)),size), # | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def apply_depth(stars, rh_fn): | ||||
|     print("Loading", rh_fn, flush=True, end=" ") | ||||
|     route_hist = pd.read_csv( | ||||
|         rh_fn, | ||||
|         names=["id", "depth"], | ||||
|         index_col=0, | ||||
|         dtype={"depth": int}, | ||||
|         low_memory=False, | ||||
|     ) | ||||
|     print("OK") | ||||
|     print("Converting to pandas dataframe", flush=True, end=" ") | ||||
|     stars = stars.to_pandas_df() | ||||
|     gc.collect() | ||||
|     print("OK") | ||||
|     print("Applying depth", flush=True, end=" ") | ||||
|     stars["depth"] = float("nan") | ||||
|     print("...",flush=True,end=" ") | ||||
|     stars["depth"] = route_hist.depth + 1.0 | ||||
|     print("OK") | ||||
|     print("Converting to vaex dataframe", flush=True, end=" ") | ||||
|     stars = vx.from_pandas(stars, copy_index=False) | ||||
|     gc.collect() | ||||
|     print("OK") | ||||
|     return stars, route_hist.depth.max() | ||||
| 
 | ||||
| 
 | ||||
| """ | ||||
| #[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)] | ||||
| pub struct System { | ||||
|     /// Unique System id | ||||
|     pub id: u32, | ||||
|     /// Star system | ||||
|     pub name: String, | ||||
|     /// Number of bodies | ||||
|     pub num_bodies: u8, | ||||
|     /// Does the system have a scoopable star? | ||||
|     pub has_scoopable: bool, | ||||
|     /// Jump range multiplier (1.5 for white dwarfs, 4.0 for neutron stars, 1.0 otherwise) | ||||
|     pub mult: f32, | ||||
|     /// Position | ||||
|     pub pos: [f32; 3], | ||||
| } | ||||
| """ | ||||
| 
 | ||||
| print("Loading stars.csv") | ||||
| stars = pd.read_csv( | ||||
|     "stars.csv", | ||||
|     names=["id", "name", "num_bodies", "has_scoopable", "mult", "x", "y", "z"], | ||||
|     usecols=["id", "num_bodies", "x", "y", "z", "mult"], | ||||
|     index_col=0, | ||||
| ) | ||||
| stars = vx.from_pandas(stars, copy_index=False) | ||||
| 
 | ||||
| 
 | ||||
| def render(stars, rh_fn): | ||||
|     print("Rendering") | ||||
|     json_file = os.path.splitext(rh_fn)[0] + ".json" | ||||
|     if os.path.isfile(json_file): | ||||
|         with open(json_file) as fh: | ||||
|             route_info = json.load(fh) | ||||
|             route_len = len(route_info["route"]) | ||||
|             time_taken = str(timedelta(seconds=route_info["dt"])) | ||||
|             route_rate = route_len / route_info["dt"] | ||||
|     else: | ||||
|         time_taken = "N/A" | ||||
|         route_len = 0 | ||||
|         route_rate = 0 | ||||
|         route_info = {"dt": -1.0} | ||||
|     stars, d_max = apply_depth(stars, rh_fn) | ||||
|     basename = os.path.splitext(os.path.split(rh_fn)[-1])[0] | ||||
|     filename = "img/{}.mkv".format(basename) | ||||
|     if os.path.isfile(filename): | ||||
|         return | ||||
|     ffmpeg = SP.Popen( | ||||
|         [ | ||||
|             "ffmpeg", | ||||
|             "-y", | ||||
|             "-f", | ||||
|             "image2pipe", | ||||
|             "-probesize", | ||||
|             "128M", | ||||
|             "-i", | ||||
|             "-", | ||||
|             "-crf", | ||||
|             "17", | ||||
|             "-preset", | ||||
|             "veryslow", | ||||
|             "-r", | ||||
|             str(framerate), | ||||
|             "-pix_fmt", | ||||
|             "yuv420p", | ||||
|             filename, | ||||
|         ], | ||||
|         stdin=SP.PIPE, | ||||
|         bufsize=0, | ||||
|     ) | ||||
| 
 | ||||
|     total = stars.length() | ||||
|     fnt = ImageFont.truetype(r"FiraCode-Regular", 40) | ||||
| 
 | ||||
|     for (binby_key, m1, m2), calcshape in bining.items(): | ||||
|         binby = [m1 * stars[binby_key[0]], m2 * stars[binby_key[1]]] | ||||
| 
 | ||||
|         mm = [binby[0].minmax(), binby[1].minmax()] | ||||
| 
 | ||||
|         w, h = [mm[0][1] - mm[0][0], mm[1][1] - mm[1][0]] | ||||
|         shape = calcshape(w, h) | ||||
|         hm_all = stars.sum("num_bodies", binby=binby, shape=shape, limits="minmax") | ||||
|         hm_all_mask = hm_all != 0 | ||||
|         hm_all = exposure.equalize_hist(hm_all) | ||||
|         hm_all -= hm_all.min() | ||||
|         hm_all /= hm_all.max() | ||||
| 
 | ||||
|         hm_boost = stars.sum( | ||||
|             "astype(mult>1.0,'int')", binby=binby, shape=shape, limits="minmax" | ||||
|         ) | ||||
|         hm_boost_mask = hm_boost != 0 | ||||
| 
 | ||||
|         hm_boost = exposure.equalize_hist(hm_boost) | ||||
|         hm_boost -= hm_boost.min() | ||||
|         hm_boost /= hm_boost.max() | ||||
| 
 | ||||
|         G = cm.Greens_r(hm_all) | ||||
|         B = cm.Blues_r(hm_boost) | ||||
|         hm_exp = stars.mean("depth", binby=binby, shape=shape, limits="minmax") | ||||
|         hm_exp[np.isnan(hm_exp)] = 0.0 | ||||
| 
 | ||||
|         hm_exp -= hm_exp.min() | ||||
|         hm_exp /= d_max | ||||
|         R = cm.Reds_r(hm_exp) | ||||
| 
 | ||||
|         hm_exp_mask_base = hm_exp != 0.0 | ||||
|         img = np.zeros((base_size[0], base_size[1], 4)) | ||||
|         d_array = stars[~stars["depth"].isna()]["depth"].values | ||||
|         exploration_rate = (d_array <= d_max).sum() / route_info["dt"] | ||||
|         print("Total frames:",d_max) | ||||
|         for d in range(0, d_max, steps): | ||||
|             hm_exp_mask = np.logical_and(hm_exp_mask_base, hm_exp <= (d / d_max)) | ||||
|             num_explored = (d_array <= d).sum() | ||||
|             img[:, :, :] = 0.0 | ||||
|             img[:, :, 3] = 1.0 | ||||
|             canvas = img[: shape[0], : shape[1], :] | ||||
|             canvas[hm_all_mask] = G[hm_all_mask] | ||||
|             canvas[hm_boost_mask] = B[hm_boost_mask] | ||||
|             canvas[hm_exp_mask] = R[hm_exp_mask] | ||||
|             pil_img = Image.fromarray(img_as_ubyte(img)) | ||||
|             draw = ImageDraw.Draw(pil_img) | ||||
|             messages = [ | ||||
|                 "Filename: {}".format(basename), | ||||
|                 "Total Stars: {:,}".format(total), | ||||
|                 "Explored: {:,} ({:.2%})".format(num_explored, num_explored / total), | ||||
|                 "Search Depth: {:,}/{:,}".format(d, route_len), | ||||
|                 "Time: {}".format(time_taken), | ||||
|                 "Rate: {:.3f} waypoints/s".format(route_rate), | ||||
|                 "Exploration Rate: {:.3f} stars/s".format(exploration_rate), | ||||
|             ] | ||||
|             draw.multiline_text((shape[0], 0), "\n".join(messages), font=fnt) | ||||
|             pil_img.save(ffmpeg.stdin, "bmp") | ||||
|     ffmpeg.stdin.close() | ||||
|     ffmpeg.wait() | ||||
| 
 | ||||
| 
 | ||||
| for rh_fn in ITT.chain.from_iterable(map(glob, sys.argv[1:])): | ||||
|     render(stars, rh_fn) | ||||
| 
 | ||||
							
								
								
									
										5356
									
								
								route_log_max.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5356
									
								
								route_log_max.txt
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										5356
									
								
								route_log_min.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5356
									
								
								route_log_min.txt
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										19
									
								
								rust/.vscode/settings.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								rust/.vscode/settings.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| { | ||||
|     "spellright.language": [ | ||||
|         "de", | ||||
|         "en" | ||||
|     ], | ||||
|     "spellright.documentTypes": [ | ||||
|         "latex", | ||||
|         "plaintext", | ||||
|         "git-commit" | ||||
|     ], | ||||
|     "discord.enabled": true, | ||||
|     "python.pythonPath": "..\\.nox\\devenv-3-8\\python.exe", | ||||
|     "jupyter.jupyterServerType": "remote", | ||||
|     "files.associations": { | ||||
|         "*.ksy": "yaml", | ||||
|         "*.vpy": "python", | ||||
|         "stat.h": "c" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1660
									
								
								rust/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1660
									
								
								rust/Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,42 +1,79 @@ | |||
| [package] | ||||
| name = "ed_lrr" | ||||
| version = "0.2.0" | ||||
| authors = [ "Daniel Seiller <earthnuker@gmail.com>",] | ||||
| authors = ["Daniel Seiller <earthnuker@gmail.com>"] | ||||
| edition = "2018" | ||||
| repository = "https://gitlab.com/Earthnuker/ed_lrr.git" | ||||
| license = "MIT" | ||||
| 
 | ||||
| [lib] | ||||
| crate-type = [ "cdylib",] | ||||
| crate-type = ["cdylib"] | ||||
| name = "_ed_lrr" | ||||
| 
 | ||||
| [dependencies] | ||||
| csv = "1.1.3" | ||||
| humantime = "2.0.1" | ||||
| permutohedron = "0.2.4" | ||||
| serde_json = "1.0.55" | ||||
| fnv = "1.0.7" | ||||
| bincode = "1.2.1" | ||||
| sha3 = "0.9.0" | ||||
| byteorder = "1.3.4" | ||||
| strsim = "0.10.0" | ||||
| rstar = "0.8.0" | ||||
| crossbeam-channel = "0.4.2" | ||||
| better-panic = "0.2.0" | ||||
| derivative = "2.1.1" | ||||
| dict_derive = "0.2.0" | ||||
| num_cpus = "1.13.0" | ||||
| regex = "1.3.9" | ||||
| chrono = "0.4.11" | ||||
| 
 | ||||
| [dependencies.pyo3] | ||||
| git = "https://github.com/PyO3/pyo3" | ||||
| features = [ "extension-module",] | ||||
| 
 | ||||
| [dependencies.serde] | ||||
| version = "1.0.112" | ||||
| features = [ "derive",] | ||||
| 
 | ||||
| [profile.release] | ||||
| codegen-units = 1 | ||||
| lto = true | ||||
| opt-level = 3 | ||||
| debug = true | ||||
| lto = "fat" | ||||
| 
 | ||||
| 
 | ||||
| [dependencies] | ||||
| pyo3 = { version = "0.15.1", features = ["extension-module","eyre"] } | ||||
| csv = "1.1.6" | ||||
| humantime = "2.1.0" | ||||
| permutohedron = "0.2.4" | ||||
| serde_json = "1.0.74" | ||||
| fnv = "1.0.7" | ||||
| bincode = "1.3.3" | ||||
| sha3 = "0.10.0" | ||||
| byteorder = "1.4.3" | ||||
| rstar = "0.9.2" | ||||
| crossbeam-channel = "0.5.2" | ||||
| better-panic = "0.3.0" | ||||
| derivative = "2.2.0" | ||||
| dict_derive = "0.4.0" | ||||
| regex = "1.5.4" | ||||
| num_cpus = "1.13.1" | ||||
| eddie = "0.4.2" | ||||
| thiserror = "1.0.30" | ||||
| pyo3-log = "0.5.0" | ||||
| log = "0.4.14" | ||||
| flate2 = "1.0.22" | ||||
| eval = "0.4.3" | ||||
| pythonize = "0.15.0" | ||||
| itertools = "0.10.3" | ||||
| intmap = "0.7.1" | ||||
| diff-struct = "0.4.1" | ||||
| rustc-hash = "1.1.0" | ||||
| stats_alloc = "0.1.8" | ||||
| 
 | ||||
| tracing = { version = "0.1.29", optional = true } | ||||
| tracing-subscriber = { version = "0.3.5", optional = true } | ||||
| tracing-tracy = { version = "0.8.0", optional = true } | ||||
| tracing-unwrap = { version = "0.9.2", optional = true } | ||||
| tracy-client = { version = "0.12.6", optional = true } | ||||
| tracing-chrome = "0.4.0" | ||||
| rand = "0.8.4" | ||||
| eyre = "0.6.6" | ||||
| memmap = "0.7.0" | ||||
| csv-core = "0.1.10" | ||||
| postcard = { version = "0.7.3", features = ["alloc"] } | ||||
| nohash-hasher = "0.2.0" | ||||
| 
 | ||||
| 
 | ||||
| [features] | ||||
| profiling = ["tracing","tracing-subscriber","tracing-tracy","tracing-unwrap","tracy-client"] | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| criterion = { version = "0.3.5", features = ["real_blackbox"] } | ||||
| rand = "0.8.4" | ||||
| rand_distr = "0.4.2" | ||||
| 
 | ||||
| [dependencies.serde] | ||||
| version = "1.0.133" | ||||
| features = ["derive"] | ||||
| 
 | ||||
| 
 | ||||
| [[bench]] | ||||
| name = "dot_bench" | ||||
| harness = false | ||||
|  |  | |||
							
								
								
									
										29
									
								
								rust/analyze_logs.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								rust/analyze_logs.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| import ujson | ||||
| from glob import glob | ||||
| import pandas as pd | ||||
| from datetime import timedelta | ||||
| 
 | ||||
| route_info = {} | ||||
| for log in glob("../logs/route_log*.json"): | ||||
|     name = log.split("route_log_")[1].rsplit(".", 1)[0] | ||||
|     data = ujson.load(open(log)) | ||||
|     dt = data["dt"] | ||||
|     route_len = len(data["route"]) | ||||
|     if route_len: | ||||
|         route_info[name] = (dt, route_len) | ||||
| dt, route_len = route_info["beam_0"]  # BFS as baseline | ||||
| 
 | ||||
| data = [] | ||||
| 
 | ||||
| for name, (dt_o, l_o) in sorted(route_info.items(), key=lambda v: v[1][0] / v[1][1]): | ||||
|     dt_s = str(timedelta(seconds=round(dt_o, 2))).rstrip("0") | ||||
|     data.append( | ||||
|         { | ||||
|             "name": name, | ||||
|             "time": "{} ({:.2f}x)".format(dt_s, dt / dt_o), | ||||
|             "length": "{} (+{:.2%})".format(l_o, (l_o / route_len) - 1), | ||||
|             "time/hop": "{:.2} s".format(dt_o / l_o), | ||||
|         } | ||||
|     ) | ||||
| df = pd.DataFrame(data) | ||||
| print(df.to_markdown(index=False)) | ||||
							
								
								
									
										142
									
								
								rust/benches/dot_bench.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								rust/benches/dot_bench.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,142 @@ | |||
| use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; | ||||
| use rand::Rng; | ||||
| use rand_distr::StandardNormal; | ||||
| 
 | ||||
| fn rand_v3() -> [f32; 3] { | ||||
|     let mut rng = rand::thread_rng(); | ||||
|     [ | ||||
|         rng.sample(StandardNormal), | ||||
|         rng.sample(StandardNormal), | ||||
|         rng.sample(StandardNormal), | ||||
|     ] | ||||
| } | ||||
| 
 | ||||
| fn arand() -> f32 { | ||||
|     let mut rng = rand::thread_rng(); | ||||
|     rng.sample::<f32, _>(StandardNormal).abs() | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn veclen(v: &[f32; 3]) -> f32 { | ||||
|     (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt() | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     let dx = p1[0] - p2[0]; | ||||
|     let dy = p1[1] - p2[1]; | ||||
|     let dz = p1[2] - p2[2]; | ||||
| 
 | ||||
|     dx * dx + dy * dy + dz * dz | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     dist2(p1, p2).sqrt() | ||||
| } | ||||
| 
 | ||||
| /// Dot product (cosine of angle) between two 3D vectors
 | ||||
| #[inline(always)] | ||||
| pub fn ndot_vec_dist(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let z: [f32; 3] = [0.0; 3]; | ||||
|     let lm = dist(u, &z) * dist(v, &z); | ||||
|     ((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm | ||||
| } | ||||
| 
 | ||||
| /// Dot product (cosine of angle) between two 3D vectors
 | ||||
| #[inline(always)] | ||||
| pub fn ndot_vec_len(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let lm = veclen(u) * veclen(v); | ||||
|     ((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| pub fn ndot_iter(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let mut l_u = 0.0; | ||||
|     let mut l_v = 0.0; | ||||
|     let mut l_s = 0.0; | ||||
|     for (u, v) in u.iter().zip(v.iter()) { | ||||
|         l_s += u * v; | ||||
|         l_u += u * u; | ||||
|         l_v += v * v; | ||||
|     } | ||||
|     l_s / (l_u * l_v).sqrt() | ||||
| } | ||||
| 
 | ||||
| fn bench_ndot(c: &mut Criterion) { | ||||
|     let mut g = c.benchmark_group("ndot"); | ||||
|     g.bench_function("vec_dist", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), rand_v3()), | ||||
|             |(v1, v2)| ndot_vec_dist(&v1, &v2), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
|     g.bench_function("vec_len", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), rand_v3()), | ||||
|             |(v1, v2)| ndot_vec_len(&v1, &v2), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
|     g.bench_function("iter", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), rand_v3()), | ||||
|             |(v1, v2)| ndot_iter(&v1, &v2), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
|     g.finish(); | ||||
| } | ||||
| 
 | ||||
| fn bench_dist(c: &mut Criterion) { | ||||
|     let mut g = c.benchmark_group("dist"); | ||||
|     g.bench_function("dist", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), rand_v3()), | ||||
|             |(v1, v2)| dist(&v1, &v2), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
|     g.bench_function("dist2", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), rand_v3()), | ||||
|             |(v1, v2)| dist2(&v1, &v2), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
|     g.finish(); | ||||
| } | ||||
| 
 | ||||
| fn vsub(a: &[f32; 3], b: &[f32; 3]) -> [f32; 3] { | ||||
|     [a[0] - b[0], a[1] - b[1], a[2] - b[2]] | ||||
| } | ||||
| 
 | ||||
| pub fn h_old(node: &[f32; 3], m: f32, goal: &[f32; 3], r: f32) -> f32 { | ||||
|     (dist(node, goal) - (r * m)).max(0.0) | ||||
| } | ||||
| 
 | ||||
| pub fn h_new(node: &[f32; 3], next: &[f32; 3], goal: &[f32; 3]) -> f32 { | ||||
|     -ndot_iter(&vsub(node, goal), &vsub(node, next)).acos() | ||||
| } | ||||
| 
 | ||||
| fn bench_new_heur(c: &mut Criterion) { | ||||
|     c.bench_function("old_heuristic", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), arand(), rand_v3(), arand()), | ||||
|             |(node, m, goal, range)| h_old(&node, m, &goal, range), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     c.bench_function("new_heuristic", |b| { | ||||
|         b.iter_batched( | ||||
|             || (rand_v3(), rand_v3(), rand_v3()), | ||||
|             |(v1, v2, v3)| h_new(&v1, &v2, &v3), | ||||
|             BatchSize::SmallInput, | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| criterion_group!(benches, bench_ndot, bench_dist, bench_new_heur); | ||||
| criterion_main!(benches); | ||||
							
								
								
									
										2203
									
								
								rust/ch.txt
									
										
									
									
									
								
							
							
						
						
									
										2203
									
								
								rust/ch.txt
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										0
									
								
								rust/deps.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								rust/deps.svg
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										46
									
								
								rust/multi_test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								rust/multi_test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| import os | ||||
| 
 | ||||
| 
 | ||||
| def setup_logging(loglevel="INFO"): | ||||
|     import logging | ||||
|     import coloredlogs | ||||
|     import datetime | ||||
| 
 | ||||
|     coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"} | ||||
|     coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"} | ||||
| 
 | ||||
|     class DeltaTimeFormatter(coloredlogs.ColoredFormatter): | ||||
|         def format(self, record): | ||||
|             seconds = record.relativeCreated / 1000 | ||||
|             duration = datetime.timedelta(seconds=seconds) | ||||
|             record.delta = str(duration) | ||||
|             return super().format(record) | ||||
| 
 | ||||
|     coloredlogs.ColoredFormatter = DeltaTimeFormatter | ||||
|     logfmt = " | ".join( | ||||
|         ["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"] | ||||
|     ) | ||||
|     numeric_level = getattr(logging, loglevel.upper(), None) | ||||
|     if not isinstance(numeric_level, int): | ||||
|         raise ValueError("Invalid log level: %s" % loglevel) | ||||
|     coloredlogs.install(level=numeric_level, fmt=logfmt) | ||||
| 
 | ||||
| 
 | ||||
| setup_logging() | ||||
| _ed_lrr = __import__("_ed_lrr") | ||||
| 
 | ||||
| r = _ed_lrr.PyRouter(None) | ||||
| r.load("stars.csv") | ||||
| # r.run_bfs(48) | ||||
| r.test(48) | ||||
| exit() | ||||
| 
 | ||||
| _ed_lrr.PyRouter.preprocess_galaxy("E:/EDSM/galaxy.json.gz", "E:/EDSM/stars.csv") | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| r = _ed_lrr.PyRouter(print) | ||||
| r.load("../stars.csv") | ||||
| systems = r.resolve_systems((0, 0, 0), "Colonia", 18627) | ||||
| print(systems) | ||||
| print(systems[0, 0, 0]) | ||||
							
								
								
									
										0
									
								
								rust/route_log.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								rust/route_log.txt
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										221
									
								
								rust/run_test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								rust/run_test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,221 @@ | |||
| import subprocess as SP | ||||
| import sys | ||||
| from datetime import datetime, timedelta | ||||
| import os | ||||
| import shutil | ||||
| import json | ||||
| 
 | ||||
| 
 | ||||
| def setup_logging(loglevel="INFO"): | ||||
|     import logging | ||||
|     import coloredlogs | ||||
| 
 | ||||
|     coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"} | ||||
|     coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"} | ||||
| 
 | ||||
|     class DeltaTimeFormatter(coloredlogs.ColoredFormatter): | ||||
|         def format(self, record): | ||||
|             seconds = record.relativeCreated / 1000 | ||||
|             duration = timedelta(seconds=seconds) | ||||
|             record.delta = str(duration) | ||||
|             return super().format(record) | ||||
| 
 | ||||
|     coloredlogs.ColoredFormatter = DeltaTimeFormatter | ||||
|     logfmt = " | ".join( | ||||
|         ["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"] | ||||
|     ) | ||||
|     numeric_level = getattr(logging, loglevel.upper(), None) | ||||
|     if not isinstance(numeric_level, int): | ||||
|         raise ValueError("Invalid log level: %s" % loglevel) | ||||
|     coloredlogs.install(level=numeric_level, fmt=logfmt) | ||||
| 
 | ||||
| 
 | ||||
| setup_logging() | ||||
| 
 | ||||
| JUMP_RANGE = 48 | ||||
| globals().setdefault("__file__", r"D:\devel\rust\ed_lrr_gui\rust\run_test.py") | ||||
| dirname = os.path.dirname(__file__) or "." | ||||
| os.chdir(dirname) | ||||
| t_start = datetime.now() | ||||
| os.environ["PYO3_PYTHON"] = sys.executable | ||||
| if "--clean" in sys.argv[1:]: | ||||
|     SP.check_call(["cargo","clean"]) | ||||
| if "--build" in sys.argv[1:]: | ||||
|     SP.check_call(["cargo","lcheck"]) | ||||
|     SP.check_call([sys.executable, "-m", "pip", "install", "-e", ".."]) | ||||
|     print("Build+Install took:", datetime.now() - t_start) | ||||
| 
 | ||||
| sys.path.append("..") | ||||
| _ed_lrr = __import__("_ed_lrr") | ||||
| 
 | ||||
| def callback(state): | ||||
|     print(state) | ||||
| print(_ed_lrr) | ||||
| r = _ed_lrr.PyRouter(callback) | ||||
| r.load("../stars_2.csv", immediate=False) | ||||
| print(r) | ||||
| r.str_tree_test() | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| r = _ed_lrr.PyRouter(callback) | ||||
| r.load("../stars.csv", immediate=False) | ||||
| print(r.resolve("Sol","Saggitarius A","Colonia","Merope")) | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| ships = _ed_lrr.PyShip.from_journal() | ||||
| r = _ed_lrr.PyRouter(callback) | ||||
| r.load("../stars.csv", immediate=False) | ||||
| 
 | ||||
| def func(*args,**kwargs): | ||||
|     print(kwargs) | ||||
|     return 12 | ||||
| 
 | ||||
| r.precompute_neighbors(JUMP_RANGE) | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| # start, end = "Sol", "Colonia"  # # 135 in 22m 36s 664ms 268us 800ns | ||||
| 
 | ||||
| 
 | ||||
| """ | ||||
| {'mode': 'BFS_serial', 'system': 'Nuwo OP-N c23-1', 'from': 'Sol', 'to': 'Beagle Point', 'depth': 492, 'queue_size': 1602, 'd_rem': 2456.31298828125, 'd_total': 65279.3515625, 'prc_done': 96.23722839355469, 'n_seen': 17366296, 'prc_seen': 26.25494384765625} | ||||
| [0:43:19.715858] INFO | _ed_lrr.route:src\route.rs:2402 | Took: 34m 38s 40ms 256us 500ns | ||||
| """ | ||||
| 
 | ||||
| """ | ||||
| {'mode': 'BFS_serial', 'system': 'Syriae Thaa DN-B d13-2', 'from': 'Sol', 'to': 'Beagle Point', 'depth': 521, 'queue_size': 2311, 'd_rem': 492.8757019042969, 'd_total': 65279.3515625, 'prc_done': 99.2449722290039, 'n_seen': 19566797, 'prc_seen': 29.58173179626465} | ||||
| [0:53:28.431326] INFO | _ed_lrr.route:src\route.rs:2402 | Took: 48m 34s 958ms 326us 300ns | ||||
| """ | ||||
| 
 | ||||
| """ | ||||
| [0:36:02.738233] INFO | _ed_lrr.route:src\route.rs:2404 | Took: 27m 6s 216ms 161us 100ns | ||||
| Optimal route: 534 | ||||
| """ | ||||
| 
 | ||||
| """ | ||||
| Sol, Colonia | ||||
| Took: 30m 22s 63ms 818us | ||||
| Allocs: 26622742 | ||||
| Reallocs: 45809664 | ||||
| Deallocs: 26622600 | ||||
| Optimal route: 135 | ||||
| """ | ||||
| 
 | ||||
| """ | ||||
| Sol, Ix | ||||
| Took: 1s 995ms 115us 100ns | ||||
| Allocs: 17058 | ||||
| Reallocs: 32042 | ||||
| Deallocs: 17047 | ||||
| Optimal route: 4 | ||||
| """ | ||||
| 
 | ||||
| # Stats { allocations: 23257531, deallocations: 23257389, reallocations: 42747420, bytes_allocated: 179667997387, bytes_deallocated: 179667853217, bytes_reallocated: 151573742821 } | ||||
| 
 | ||||
| start, end = "Sol", "Colonia" | ||||
| 
 | ||||
| systems = r.resolve(start, end) | ||||
| sys_ids = {k: v["id"] for k, v in systems.items()} | ||||
| 
 | ||||
| cfg = {} | ||||
| cfg["mode"] = "incremental_broadening" | ||||
| # input("{}>".format(os.getpid())) | ||||
| route = r.route([sys_ids[start], sys_ids[end]], JUMP_RANGE, cfg, 0) | ||||
| print("Optimal route:", len(route)) | ||||
| 
 | ||||
| # cfg["mode"] = "beam_stack" | ||||
| # route = r.route([sys_ids[start], sys_ids[end]], JUMP_RANGE, cfg, 0) | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| # bw_l = [ | ||||
| #     1, | ||||
| #     2, | ||||
| #     4, | ||||
| #     8, | ||||
| #     16, | ||||
| #     32, | ||||
| #     64, | ||||
| #     128, | ||||
| #     256, | ||||
| #     512, | ||||
| #     1024, | ||||
| #     2048, | ||||
| #     4096, | ||||
| #     8192, | ||||
| #     16384, | ||||
| #     0.1, | ||||
| #     0.25, | ||||
| #     0.5, | ||||
| #     0.75, | ||||
| #     0.9, | ||||
| #     0.99, | ||||
| #     0, | ||||
| # ] | ||||
| 
 | ||||
| # cfg = { | ||||
| #     "mode": "bfs", | ||||
| #     "greedyness": 0, | ||||
| # } | ||||
| 
 | ||||
| # bw_l = [0] | ||||
| 
 | ||||
| # for bw in bw_l: | ||||
| #     ofn = "../logs/route_log_beam_{}.txt".format(bw) | ||||
| #     # if os.path.isfile(ofn): | ||||
| #     #     continue | ||||
| #     print(ofn) | ||||
| #     t_start = datetime.today() | ||||
| #     try: | ||||
| #         if isinstance(bw, int): | ||||
| #             cfg["beam_width"] = {"absolute": bw} | ||||
| #         else: | ||||
| #             cfg["beam_width"] = {"fraction": bw} | ||||
| #         route = r.route([sys_ids["Sol"], sys_ids["Beagle Point"]], JUMP_RANGE, cfg, 8) | ||||
| #         print(route) | ||||
| #     except Exception as e: | ||||
| #         print("Error:", e) | ||||
| #         route = [] | ||||
| #     dt = (datetime.today() - t_start).total_seconds() | ||||
| #     shutil.copy("route_log.txt", ofn) | ||||
| #     with open(ofn.replace(".txt", ".json"), "w") as of: | ||||
| #         json.dump({"route": route, "dt": dt}, of) | ||||
| 
 | ||||
| # g_l = [1.0, 0.99, 0.9, 0.75, 0.5, 0.25] | ||||
| 
 | ||||
| # g_l.clear() | ||||
| # cfg["beam_width"] = 0 | ||||
| # for g in g_l: | ||||
| #     ofn = "../logs/route_log_g_{}.txt".format(g) | ||||
| #     if os.path.isfile(ofn): | ||||
| #         continue | ||||
| #     print(ofn) | ||||
| #     t_start = datetime.today() | ||||
| #     try: | ||||
| #         cfg["greedyness"] = g | ||||
| #         route = r.route([sys_ids["Sol"], sys_ids["Beagle Point"]], JUMP_RANGE, cfg) | ||||
| #     except Exception as e: | ||||
| #         print("Error:", e) | ||||
| #         route = [] | ||||
| #     dt = (datetime.today() - t_start).total_seconds() | ||||
| #     shutil.copy("route_log.txt", ofn) | ||||
| #     with open(ofn.replace(".txt", ".json"), "w") as of: | ||||
| #         json.dump({"route": route, "dt": dt}, of) | ||||
| # r.unload() | ||||
| # exit() | ||||
| # os.chdir("..") | ||||
| # SP.check_call( | ||||
| #     [ | ||||
| #         "conda", | ||||
| #         "run", | ||||
| #         "-n", | ||||
| #         "base", | ||||
| #         "--no-capture-output", | ||||
| #         "python", | ||||
| #         "plot_heatmap_vaex.py", | ||||
| #         "logs/route_log_*.txt", | ||||
| #     ], | ||||
| #     shell=True, | ||||
| # ) | ||||
|  | @ -1,13 +1,257 @@ | |||
| use crate::route::Router; | ||||
| //! # Common utlility functions
 | ||||
| use crate::route::{LineCache, Router}; | ||||
| use bincode::Options; | ||||
| use crossbeam_channel::{bounded, Receiver}; | ||||
| use csv::ByteRecord; | ||||
| use dict_derive::IntoPyObject; | ||||
| use pyo3::conversion::ToPyObject; | ||||
| use eyre::Result; | ||||
| use log::*; | ||||
| use pyo3::prelude::*; | ||||
| use pyo3::types::{PyDict, PyTuple}; | ||||
| use pyo3::types::PyDict; | ||||
| use pyo3::{conversion::ToPyObject, create_exception}; | ||||
| use pythonize::depythonize; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::cmp::Ordering; | ||||
| use std::collections::HashMap; | ||||
| use std::path::PathBuf; | ||||
| use sha3::{Digest, Sha3_256}; | ||||
| use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; | ||||
| use std::hash::{Hash, Hasher, BuildHasherDefault}; | ||||
| use std::io::Write; | ||||
| use std::ops::{Deref, DerefMut}; | ||||
| use std::path::Path; | ||||
| use std::str::FromStr; | ||||
| use std::thread; | ||||
| use std::{cmp::Ordering, cmp::Reverse, collections::BinaryHeap}; | ||||
| use std::{ | ||||
|     fs::File, | ||||
|     io::{BufReader, BufWriter}, | ||||
|     path::PathBuf, | ||||
| }; | ||||
| use nohash_hasher::NoHashHasher; | ||||
| use thiserror::Error; | ||||
| 
 | ||||
| #[inline(always)] | ||||
| pub fn heuristic(range: f32, node: &TreeNode, goal: &TreeNode) -> f32 { | ||||
|     // distance remaining after jumping from node towards goal
 | ||||
|     let a2 = dist2(&node.pos, &goal.pos); | ||||
|     let mult=node.get_mult(); | ||||
|     let b2 = range * range * mult*mult; | ||||
|     return (a2 - b2).max(0.0); | ||||
| } | ||||
| 
 | ||||
| /// Min-heap priority queue using f32 as priority
 | ||||
| pub struct MinFHeap<T: Ord>(pub BinaryHeap<(Reverse<F32>, T)>); | ||||
| /// Max-heap priority queue using f32 as priority
 | ||||
| pub struct MaxFHeap<T: Ord>(pub BinaryHeap<(F32, T)>); | ||||
| 
 | ||||
| impl<T: Ord> MaxFHeap<T> { | ||||
|     /// Create new, empty priority queue
 | ||||
|     pub fn new() -> Self { | ||||
|         MaxFHeap(BinaryHeap::new()) | ||||
|     } | ||||
| 
 | ||||
|     /// push value `item` with priority `w` into queue
 | ||||
|     pub fn push(&mut self, w: f32, item: T) { | ||||
|         self.0.push((F32(w), item)) | ||||
|     } | ||||
| 
 | ||||
|     /// Remove and return largest item and priority
 | ||||
|     pub fn pop(&mut self) -> Option<(f32, T)> { | ||||
|         self.0.pop().map(|(F32(w), item)| (w, item)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> Default for MaxFHeap<T> { | ||||
|     fn default() -> Self { | ||||
|         return MaxFHeap(BinaryHeap::new()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> Deref for MaxFHeap<T> { | ||||
|     type Target = BinaryHeap<(F32, T)>; | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> DerefMut for MaxFHeap<T> { | ||||
|     fn deref_mut(&mut self) -> &mut Self::Target { | ||||
|         &mut self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> MinFHeap<T> { | ||||
|     /// Create new, empty priority queue
 | ||||
|     pub fn new() -> Self { | ||||
|         MinFHeap(BinaryHeap::new()) | ||||
|     } | ||||
| 
 | ||||
|     /// push value `item` with priority `w` into queue
 | ||||
|     pub fn push(&mut self, w: f32, item: T) { | ||||
|         self.0.push((Reverse(F32(w)), item)) | ||||
|     } | ||||
| 
 | ||||
|     /// Remove and return smallest item and priority
 | ||||
|     pub fn pop(&mut self) -> Option<(f32, T)> { | ||||
|         self.0.pop().map(|(Reverse(F32(w)), item)| (w, item)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> Default for MinFHeap<T> { | ||||
|     fn default() -> Self { | ||||
|         return MinFHeap(BinaryHeap::new()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> Deref for MinFHeap<T> { | ||||
|     type Target = BinaryHeap<(Reverse<F32>, T)>; | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Ord> DerefMut for MinFHeap<T> { | ||||
|     fn deref_mut(&mut self) -> &mut Self::Target { | ||||
|         &mut self.0 | ||||
|     } | ||||
| } | ||||
| /// ED LRR error type
 | ||||
| #[derive(Error, Debug)] | ||||
| pub enum EdLrrError { | ||||
|     #[error("failed to compute route from {from:?} to {to:?}: {reason}")] | ||||
|     RouteError { | ||||
|         from: Option<System>, | ||||
|         to: Option<System>, | ||||
|         reason: String, | ||||
|     }, | ||||
| 
 | ||||
|     #[error("failed to find system matching {0:?}")] | ||||
|     ResolveError(String), | ||||
| 
 | ||||
|     #[error("runtime error: {0:?}")] | ||||
|     RuntimeError(String), | ||||
| 
 | ||||
|     #[error("Failed to process {0}")] | ||||
|     ProcessingError(PathBuf), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     EvalError(#[from] eval::Error), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     CSVError(#[from] csv::Error), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     IOError(#[from] std::io::Error), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     BincodeError(#[from] Box<bincode::ErrorKind>), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     PyError(#[from] pyo3::PyErr), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     Error(#[from] eyre::Error), | ||||
| 
 | ||||
|     #[error("unknown error")] | ||||
|     Unknown, | ||||
| } | ||||
| 
 | ||||
| pub mod py_exceptions { | ||||
|     use super::*; | ||||
|     pub use pyo3::exceptions::*; | ||||
|     create_exception!(_ed_lrr, RouteError, PyException); | ||||
|     create_exception!(_ed_lrr, ResolveError, PyException); | ||||
|     create_exception!(_ed_lrr, EdLrrException, PyException); | ||||
|     create_exception!(_ed_lrr, ProcessingError, PyException); | ||||
|     create_exception!(_ed_lrr, FileFormatError, PyException); | ||||
| } | ||||
| 
 | ||||
| impl FromStr for EdLrrError { | ||||
|     type Err = (); | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(Self::RuntimeError(s.to_owned())) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::convert::From<String> for EdLrrError { | ||||
|     fn from(s: String) -> Self { | ||||
|         Self::RuntimeError(s) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::convert::From<EdLrrError> for PyErr { | ||||
|     fn from(err: EdLrrError) -> PyErr { | ||||
|         match err { | ||||
|             EdLrrError::PyError(e) => e, | ||||
|             EdLrrError::BincodeError(..) => { | ||||
|                 py_exceptions::FileFormatError::new_err(err.to_string()) | ||||
|             } | ||||
|             EdLrrError::RouteError { .. } => py_exceptions::RouteError::new_err(err.to_string()), | ||||
|             EdLrrError::RuntimeError(msg) => py_exceptions::PyRuntimeError::new_err(msg), | ||||
|             EdLrrError::ResolveError(..) => py_exceptions::PyRuntimeError::new_err(err.to_string()), | ||||
|             EdLrrError::EvalError(err) => py_exceptions::PyRuntimeError::new_err(err.to_string()), | ||||
|             EdLrrError::CSVError(err) => py_exceptions::PyRuntimeError::new_err(err.to_string()), | ||||
|             EdLrrError::IOError(err) => py_exceptions::PyIOError::new_err(err.to_string()), | ||||
|             EdLrrError::Error(err) => py_exceptions::EdLrrException::new_err(err.to_string()), | ||||
|             EdLrrError::ProcessingError(buf) => { | ||||
|                 py_exceptions::ProcessingError::new_err(format!("{}", buf.display())) | ||||
|             } | ||||
|             EdLrrError::Unknown => { | ||||
|                 py_exceptions::EdLrrException::new_err("Unknown error!".to_string()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub type EdLrrResult<T> = Result<T, EdLrrError>; | ||||
| 
 | ||||
| /// f32 compare wrapper
 | ||||
| pub fn fcmp(a: f32, b: f32) -> Ordering { | ||||
|     match (a, b) { | ||||
|         (x, y) if x.is_nan() && y.is_nan() => Ordering::Equal, | ||||
|         (x, _) if x.is_nan() => Ordering::Greater, | ||||
|         (_, y) if y.is_nan() => Ordering::Less, | ||||
|         (..) => a.partial_cmp(&b).unwrap(), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// f32 warpper type implementing `Eq` and `Ord`
 | ||||
| #[derive(Debug)] | ||||
| pub struct F32(pub f32); | ||||
| 
 | ||||
| impl PartialEq for F32 { | ||||
|     fn eq(&self, other: &F32) -> bool { | ||||
|         fcmp(self.0, other.0) == std::cmp::Ordering::Equal | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Eq for F32 {} | ||||
| 
 | ||||
| impl PartialOrd for F32 { | ||||
|     fn partial_cmp(&self, other: &F32) -> Option<std::cmp::Ordering> { | ||||
|         Some(fcmp(self.0, other.0)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Ord for F32 { | ||||
|     fn cmp(&self, other: &F32) -> std::cmp::Ordering { | ||||
|         fcmp(self.0, other.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Deref for F32 { | ||||
|     type Target = f32; | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DerefMut for F32 { | ||||
|     fn deref_mut(&mut self) -> &mut Self::Target { | ||||
|         &mut self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Returns additional jump range (in Ly) granted by specified class of Guardian FSD Booster
 | ||||
| pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> { | ||||
|     // Data from https://elite-dangerous.fandom.com/wiki/Guardian_Frame_Shift_Drive_Booster
 | ||||
|     let ret = match class { | ||||
|  | @ -22,6 +266,7 @@ pub fn get_fsd_booster_info(class: usize) -> Result<f32, String> { | |||
|     return Ok(ret); | ||||
| } | ||||
| 
 | ||||
| /// Returns optimal mass and maximum fuel per jump for the given FSD rating and class as a hash map
 | ||||
| pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, String> { | ||||
|     let mut ret = HashMap::new(); | ||||
|     // Data from https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Specifications
 | ||||
|  | @ -68,6 +313,7 @@ pub fn get_fsd_info(rating: usize, class: usize) -> Result<HashMap<String, f32>, | |||
|     return Ok(ret); | ||||
| } | ||||
| 
 | ||||
| /// Returns jump range multiplier for the specified star type (4 for neutron stars, 1.5 for white dwarfs and 1.0 otherwise)
 | ||||
| pub fn get_mult(star_type: &str) -> f32 { | ||||
|     if star_type.contains("White Dwarf") { | ||||
|         return 1.5; | ||||
|  | @ -78,133 +324,403 @@ pub fn get_mult(star_type: &str) -> f32 { | |||
|     1.0 | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Deserialize)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| pub enum BeamWidth { | ||||
|     Absolute(usize), | ||||
|     Fraction(f32), | ||||
|     Radius(f32), | ||||
|     Infinite, | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Display for BeamWidth { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         match self { | ||||
|             BeamWidth::Absolute(n) => write!(f, "{}", n), | ||||
|             BeamWidth::Fraction(v) => write!(f, "{}%", (*v) * 100.0), | ||||
|             BeamWidth::Radius(r) => write!(f, "{} Ly", r), | ||||
|             BeamWidth::Infinite => write!(f, "Infinite"), | ||||
|         }?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Default for BeamWidth { | ||||
|     fn default() -> Self { | ||||
|         Self::Infinite | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromPyObject<'_> for BeamWidth { | ||||
|     fn extract(ob: &PyAny) -> PyResult<Self> { | ||||
|         depythonize(ob).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl BeamWidth { | ||||
|     pub fn is_set(&self) -> bool { | ||||
|         match self { | ||||
|             Self::Fraction(f) => *f > 0.0, | ||||
|             Self::Absolute(n) => *n != 0, | ||||
|             Self::Radius(r) => *r > 0.0, | ||||
|             Self::Infinite => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_infinite(&self) -> bool { | ||||
|         matches!(self, Self::Infinite) | ||||
|     } | ||||
| 
 | ||||
|     pub fn compute(&self, nodes: usize) -> usize { | ||||
|         match self { | ||||
|             Self::Fraction(f) => { | ||||
|                 let w = (nodes as f32) * f.max(0.0).min(1.0); | ||||
|                 return (w.ceil() as usize).max(1); | ||||
|             } | ||||
|             Self::Absolute(n) => *n, | ||||
|             Self::Radius(_) | Self::Infinite => nodes, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| /// Represents an uresolved system to be searched for by name, id or position
 | ||||
| #[derive(Debug, FromPyObject)] | ||||
| pub enum SysEntry { | ||||
|     ID(u32), | ||||
|     Name(String), | ||||
|     Pos((f32, f32, f32)), | ||||
| } | ||||
| 
 | ||||
| impl SysEntry { | ||||
|     pub fn parse(s: &str) -> Self { | ||||
|         if let Ok(n) = s.parse() { | ||||
|             SysEntry::ID(n) | ||||
|         } else { | ||||
|             SysEntry::Name(s.to_owned()) | ||||
| impl ToPyObject for SysEntry { | ||||
|     fn to_object(&self, py: Python) -> PyObject { | ||||
|         match self { | ||||
|             Self::ID(id) => id.to_object(py), | ||||
|             Self::Name(name) => name.to_object(py), | ||||
|             Self::Pos(pos) => pos.to_object(py), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn find_matches( | ||||
|     path: &PathBuf, | ||||
| pub fn grid_stats( | ||||
|     path: &Path, | ||||
|     grid_size: f32, | ||||
| ) -> Result<BTreeMap<(i64, i64, i64), Vec<u32>>, String> { | ||||
|     let mut reader = match csv::ReaderBuilder::new().has_headers(false).from_path(path) { | ||||
|         Ok(rdr) => rdr, | ||||
|         Err(e) => { | ||||
|             return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e)); | ||||
|         } | ||||
|     }; | ||||
|     let systems = reader.deserialize::<System>().map(Result::unwrap); | ||||
|     let mut ret: BTreeMap<(i64, i64, i64), Vec<u32>> = BTreeMap::new(); | ||||
|     for sys in systems { | ||||
|         let k = ( | ||||
|             ((sys.pos[0] / grid_size).round() * grid_size) as i64, | ||||
|             ((sys.pos[1] / grid_size).round() * grid_size) as i64, | ||||
|             ((sys.pos[2] / grid_size).round() * grid_size) as i64, | ||||
|         ); | ||||
|         ret.entry(k).or_default().push(sys.id); | ||||
|     } | ||||
|     Ok(ret) | ||||
| } | ||||
| 
 | ||||
| pub enum Node { | ||||
|     Start, | ||||
|     Goal, | ||||
|     ID(u32), | ||||
| } | ||||
| pub enum Weight { | ||||
|     Dist(Node), | ||||
|     Depth, | ||||
| } | ||||
| 
 | ||||
| impl Weight { | ||||
|     fn eval(&self) -> f32 { | ||||
|         todo!() | ||||
|     } | ||||
| } | ||||
| struct Weights(Vec<(f32, Weight)>); | ||||
| 
 | ||||
| impl Weights { | ||||
|     fn new() -> Self { | ||||
|         Self(vec![]) | ||||
|     } | ||||
|     fn add(&mut self, w: f32, v: Weight) { | ||||
|         self.0.push((w, v)); | ||||
|     } | ||||
| 
 | ||||
|     fn eval(&mut self) -> f32 { | ||||
|         self.0.iter().map(|(w, v)| w * v.eval()).sum() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| pub fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     let dx = p1[0] - p2[0]; | ||||
|     let dy = p1[1] - p2[1]; | ||||
|     let dz = p1[2] - p2[2]; | ||||
| 
 | ||||
|     dx * dx + dy * dy + dz * dz | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| pub fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     dist2(p1, p2).sqrt() | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| pub fn distm(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     let dx = (p1[0] - p2[0]).abs(); | ||||
|     let dy = (p1[1] - p2[1]).abs(); | ||||
|     let dz = (p1[2] - p2[2]).abs(); | ||||
|     dx + dy + dz | ||||
| } | ||||
| 
 | ||||
| /// Dot product (cosine of angle) between two 3D vectors
 | ||||
| #[inline(always)] | ||||
| pub fn ndot(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let z: [f32; 3] = [0.0; 3]; | ||||
|     let lm = dist(u, &z) * dist(v, &z); | ||||
|     (u[0] * v[0]) / lm + (u[1] * v[1]) / lm + (u[2] * v[2]) / lm | ||||
| } | ||||
| 
 | ||||
| /// Fuzzy string matcher, use to resolve star system names
 | ||||
| #[cfg_attr(feature = "profiling", tracing::instrument(skip(rx)))] | ||||
| fn matcher( | ||||
|     rx: Receiver<ByteRecord>, | ||||
|     names: Vec<String>, | ||||
|     exact: bool, | ||||
| ) -> Result<HashMap<String, (f64, Option<System>)>, String> { | ||||
|     let mut best: HashMap<String, (f64, Option<System>)> = HashMap::new(); | ||||
| ) -> HashMap<String, (f64, Option<u32>)> { | ||||
|     let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new(); | ||||
|     for name in &names { | ||||
|         best.insert(name.to_string(), (0.0, None)); | ||||
|     } | ||||
|     let names_u8: Vec<(String, _)> = names.iter().map(|n| (n.clone(), n.as_bytes())).collect(); | ||||
|     let sdist = eddie::slice::Levenshtein::new(); | ||||
|     for sys in rx.into_iter() { | ||||
|         for (name, name_b) in &names_u8 { | ||||
|             if let Some(ent) = best.get_mut(name) { | ||||
|                 if (ent.0 - 1.0).abs() < std::f64::EPSILON { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if exact && (&sys[1] == *name_b) { | ||||
|                     let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap(); | ||||
|                     *ent = (1.0, Some(id)); | ||||
|                     continue; | ||||
|                 } | ||||
|                 let d = sdist.similarity(&sys[1], name_b); | ||||
|                 if d > ent.0 { | ||||
|                     let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap(); | ||||
|                     *ent = (d, Some(id)); | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|     best | ||||
| } | ||||
| 
 | ||||
| /// Scan through the csv file at `path` and return a hash map
 | ||||
| /// mapping the strings from `names` to a tuple `(score, Option<system_id>)`.
 | ||||
| /// Scoring matching uses the normalized Levenshtein distance where 1.0 is an exact match.
 | ||||
| pub fn find_matches( | ||||
|     path: &Path, | ||||
|     names: Vec<String>, | ||||
|     exact: bool, | ||||
| ) -> Result<HashMap<String, (f64, Option<u32>)>, String> { | ||||
|     let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new(); | ||||
|     if names.is_empty() { | ||||
|         return Ok(best); | ||||
|     } | ||||
|     for name in &names { | ||||
|         best.insert(name.to_string(), (0.0, None)); | ||||
|     } | ||||
|     let mut reader = match csv::ReaderBuilder::new().from_path(path) { | ||||
| 
 | ||||
|     let mut workers = Vec::new(); | ||||
|     let ncpus = num_cpus::get(); | ||||
|     let (tx, rx) = bounded(4096 * ncpus); | ||||
|     for _ in 0..ncpus { | ||||
|         let names = names.clone(); | ||||
|         let rx = rx.clone(); | ||||
|         let th = thread::spawn(move || matcher(rx, names, exact)); | ||||
|         workers.push(th); | ||||
|     } | ||||
| 
 | ||||
|     let mut rdr = match csv::ReaderBuilder::new().has_headers(false).from_path(path) { | ||||
|         Ok(rdr) => rdr, | ||||
|         Err(e) => { | ||||
|             return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e)); | ||||
|         } | ||||
|     }; | ||||
|     let systems = reader.deserialize::<SystemSerde>(); | ||||
|     for sys in systems { | ||||
|         let sys = sys.unwrap(); | ||||
|         for name in &names { | ||||
|             best.entry(name.clone()).and_modify(|ent| { | ||||
|                 if (exact) && (&sys.system == name) { | ||||
|                     *ent = (1.0, Some(sys.clone().build())) | ||||
|                 } else { | ||||
|                     let d1 = strsim::normalized_levenshtein(&sys.system, &name); | ||||
|                     let d2 = strsim::normalized_levenshtein(&sys.body, &name); | ||||
|                     if d1 > ent.0 { | ||||
|                         *ent = (d1, Some(sys.clone().build())) | ||||
|                     } else if d2 > ent.0 { | ||||
|                         *ent = (d2, Some(sys.clone().build())) | ||||
|     let t_start = std::time::Instant::now(); | ||||
|     let mut processed: usize = 0; | ||||
|     for record in rdr.byte_records().flat_map(|v| v.ok()) { | ||||
|         tx.send(record).unwrap(); | ||||
|         processed += 1; | ||||
|     } | ||||
|     drop(tx); | ||||
|     while let Some(th) = workers.pop() { | ||||
|         for (name, (score, sys)) in th.join().unwrap().iter() { | ||||
|             best.entry(name.clone()).and_modify(|ent| { | ||||
|                 if score > &ent.0 { | ||||
|                     *ent = (*score, *sys); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     let dt = std::time::Instant::now() - t_start; | ||||
|     info!( | ||||
|         "Searched {} records in {:?}: {} records/second", | ||||
|         processed, | ||||
|         dt, | ||||
|         (processed as f64) / dt.as_secs_f64() | ||||
|     ); | ||||
|     Ok(best) | ||||
| } | ||||
| 
 | ||||
| /// Hash the contents of `path` with sha3 and return the hash as a vector of bytes
 | ||||
| fn hash_file(path: &Path) -> Vec<u8> { | ||||
|     let mut hash_reader = BufReader::new(File::open(path).unwrap()); | ||||
|     let mut hasher = Sha3_256::new(); | ||||
|     std::io::copy(&mut hash_reader, &mut hasher).unwrap(); | ||||
|     hasher.finalize().iter().copied().collect() | ||||
| } | ||||
| 
 | ||||
| /// Construct and `O(1)` lookup index for the csv file at `path`.
 | ||||
| /// The structure of the index is `(sha3, Vec<usize>)`
 | ||||
| /// where the first element is the sha3 hash of the file the index belongs to
 | ||||
| /// followed by a deltified vector where the entry at index `i` is the file offset for line `i` of the csv file.
 | ||||
| pub fn build_index(path: &Path) -> std::io::Result<()> { | ||||
|     let file_hash = hash_file(path); | ||||
|     let mut wtr = BufWriter::new(File::create(path.with_extension("idx"))?); | ||||
|     let mut idx: Vec<u8> = Vec::new(); | ||||
|     let mut records = (csv::ReaderBuilder::new() | ||||
|         .has_headers(false) | ||||
|         .from_path(path)?) | ||||
|     .into_deserialize::<System>(); | ||||
|     let mut n: usize = 0; | ||||
|     let mut size; | ||||
|     idx.push(0); | ||||
|     loop { | ||||
|         n += 1; | ||||
|         if n % 100000 == 0 { | ||||
|             info!("{} Bodies processed", n); | ||||
|         } | ||||
|         let new_pos = records.reader().position().byte(); | ||||
|         if records.next().is_none() { | ||||
|             break; | ||||
|         } | ||||
|         size = records.reader().position().byte() - new_pos; | ||||
|         idx.push(size as u8); | ||||
|     } | ||||
|     assert_eq!(idx.len(), n); | ||||
|     bincode::serialize_into(&mut wtr, &(file_hash, idx)).unwrap(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// Node for R*-Tree
 | ||||
| #[derive(Debug, Clone, Copy, Serialize, Deserialize)] | ||||
| pub struct TreeNode { | ||||
|     /// System ID
 | ||||
|     pub id: u32, | ||||
|     /// Position in space
 | ||||
|     pub pos: [f32; 3], | ||||
|     pub mult: f32, | ||||
|     /// flags
 | ||||
|     /// 00 unscoopable
 | ||||
|     /// 01 scoopable
 | ||||
|     /// 10 white dward
 | ||||
|     /// 11 neutron star
 | ||||
|     pub flags: u8, | ||||
| } | ||||
| 
 | ||||
| impl ToPyObject for TreeNode { | ||||
|     fn to_object(&self, py: Python) -> PyObject { | ||||
|         pythonize::pythonize(py, self).unwrap() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TreeNode { | ||||
|     pub fn get(&self, router: &Router) -> Option<System> { | ||||
|         let mut cache = router.cache.as_ref().unwrap().lock().unwrap(); | ||||
|         cache.get(self.id) | ||||
|     /// Retrieve matching [System] for this tree node
 | ||||
|     pub fn get(&self, router: &Router) -> Result<Option<System>, String> { | ||||
|         router.get(self.id) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_mult(&self) -> f32 { | ||||
|         match self.flags { | ||||
|             0b11 => 4.0, | ||||
|             0b10 => 1.5, | ||||
|             _ => 1.0 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl PartialEq for TreeNode { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         self.id == other.id | ||||
|     } | ||||
| } | ||||
| impl Eq for TreeNode {} | ||||
| 
 | ||||
| impl PartialOrd for TreeNode { | ||||
|     fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||||
|         Some(self.id.cmp(&other.id)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Ord for TreeNode { | ||||
|     fn cmp(&self, other: &TreeNode) -> Ordering { | ||||
|         self.id.cmp(&other.id) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Hash for TreeNode { | ||||
|     fn hash<H: Hasher>(&self, state: &mut H) { | ||||
|         self.id.hash(state); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Star system info read from CSV
 | ||||
| #[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)] | ||||
| pub struct SystemSerde { | ||||
|     pub id: u32, | ||||
|     pub star_type: String, | ||||
|     pub system: String, | ||||
|     pub body: String, | ||||
|     pub mult: f32, | ||||
|     pub distance: f32, | ||||
|     pub x: f32, | ||||
|     pub y: f32, | ||||
|     pub z: f32, | ||||
| } | ||||
| 
 | ||||
| impl SystemSerde { | ||||
|     pub fn build(self) -> System { | ||||
|         System { | ||||
|             id: self.id, | ||||
|             star_type: self.star_type, | ||||
|             system: self.system, | ||||
|             body: self.body, | ||||
|             mult: self.mult, | ||||
|             distance: self.distance, | ||||
|             pos: [self.x, self.y, self.z], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_node(&self) -> TreeNode { | ||||
|         TreeNode { | ||||
|             id: self.id, | ||||
|             pos: [self.x, self.y, self.z], | ||||
|             mult: self.mult, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Deserialize, Serialize)] | ||||
| pub struct System { | ||||
|     /// Unique System id
 | ||||
|     pub id: u32, | ||||
|     pub star_type: String, | ||||
|     pub system: String, | ||||
|     pub body: String, | ||||
|     /// Star system
 | ||||
|     pub name: String, | ||||
|     /// Number of bodies
 | ||||
|     pub num_bodies: u8, | ||||
|     /// Does the system have a scoopable star?
 | ||||
|     pub has_scoopable: bool, | ||||
|     /// Jump range multiplier (1.5 for white dwarfs, 4.0 for neutron stars, 1.0 otherwise)
 | ||||
|     pub mult: f32, | ||||
|     pub distance: f32, | ||||
|     /// Position
 | ||||
|     pub pos: [f32; 3], | ||||
| } | ||||
| 
 | ||||
| impl System { | ||||
|     fn get_flags(&self) -> u8 { | ||||
|         let mut flags=0; | ||||
|         if self.mult==4.0 { | ||||
|             return 0b11 | ||||
|         } | ||||
|         if self.mult==1.5 { | ||||
|             return 0b10 | ||||
|         } | ||||
|         if self.has_scoopable { | ||||
|             return 0b01 | ||||
|         } | ||||
|         return 0b00 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToPyObject for System { | ||||
|     fn to_object(&self, py: Python) -> PyObject { | ||||
|         let pos = PyTuple::new(py, self.pos.iter()); | ||||
|         let elem = PyDict::new(py); | ||||
|         elem.set_item("star_type", self.star_type.clone()).unwrap(); | ||||
|         elem.set_item("system", self.system.clone()).unwrap(); | ||||
|         elem.set_item("body", self.body.clone()).unwrap(); | ||||
|         elem.set_item("distance", self.distance).unwrap(); | ||||
|         elem.set_item("mult", self.mult).unwrap(); | ||||
|         elem.set_item("id", self.id).unwrap(); | ||||
|         elem.set_item("pos", pos).unwrap(); | ||||
|         elem.to_object(py) | ||||
|         let d = PyDict::new(py); | ||||
|         d.set_item("id", self.id).unwrap(); | ||||
|         d.set_item("name", self.name.clone()).unwrap(); | ||||
|         d.set_item("num_bodies", self.num_bodies).unwrap(); | ||||
|         d.set_item("has_scoopable", self.has_scoopable).unwrap(); | ||||
|         d.set_item("mult", self.mult).unwrap(); | ||||
|         d.set_item("pos", (self.pos[0], self.pos[1], self.pos[2])) | ||||
|             .unwrap(); | ||||
|         return d.to_object(py); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -213,7 +729,7 @@ impl System { | |||
|         TreeNode { | ||||
|             id: self.id, | ||||
|             pos: self.pos, | ||||
|             mult: self.mult, | ||||
|             flags: self.get_flags(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -229,3 +745,118 @@ impl PartialOrd for System { | |||
|         Some(self.cmp(other)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct DQueue<T>(Vec<VecDeque<T>>); | ||||
| 
 | ||||
| impl<T> DQueue<T> { | ||||
|     pub fn new() -> Self { | ||||
|         Self(vec![]) | ||||
|     } | ||||
| 
 | ||||
|     pub fn enqueue(&mut self, depth: usize, item: T) { | ||||
|         self.0.resize_with(depth, VecDeque::new); | ||||
|         self.0[depth].push_back(item); | ||||
|     } | ||||
| 
 | ||||
|     pub fn dequeue(&mut self, depth: usize) -> Option<T> { | ||||
|         self.0.resize_with(depth, VecDeque::new); | ||||
|         self.0[depth].pop_back() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T> Default for DQueue<T> { | ||||
|     fn default() -> Self { | ||||
|         Self::new() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Debug, Default, Serialize, Deserialize)] | ||||
| struct BKTreeNode { | ||||
|     ids: HashSet<u32,BuildHasherDefault<NoHashHasher<u32>>>, | ||||
|     children: HashMap<u8,Self,BuildHasherDefault<NoHashHasher<u8>>> | ||||
| } | ||||
| 
 | ||||
| impl BKTreeNode { | ||||
|     fn new(data: &[String], dist: &eddie::str::Levenshtein) -> Self { | ||||
|         let mut tree= Self::default(); | ||||
|         let mut max_depth=0; | ||||
|         (0..data.len()).map(|id| { | ||||
|             max_depth=max_depth.max(tree.insert(data,id as u32, dist,0)); | ||||
|             if (id>0) && (id%100_000 == 0) { | ||||
|                 println!("Inserting ID {}, Max Depth: {}",id,max_depth); | ||||
|             } | ||||
|         }).max(); | ||||
|         println!("Max Depth: {}",max_depth); | ||||
|         tree | ||||
|     } | ||||
| 
 | ||||
|     fn from_id(id: u32) -> Self { | ||||
|         let mut ret=Self::default(); | ||||
|         ret.ids.insert(id); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     fn insert(&mut self, data: &[String],id: u32, dist: &eddie::str::Levenshtein, depth: usize) -> usize { | ||||
|         if self.is_empty() { | ||||
|             self.ids.insert(id); | ||||
|             return depth; | ||||
|         } | ||||
|         let idx = self.get_id().unwrap() as usize; | ||||
|         let self_key = data.get(idx).unwrap(); | ||||
|         let ins_key = data.get(id as usize).unwrap(); | ||||
|         let dist_key = dist.distance(self_key,ins_key) as u8; | ||||
|         if dist_key==0 { | ||||
|             self.ids.insert(id); | ||||
|             return depth; | ||||
|         } | ||||
|         if let Some(child) = self.children.get_mut(&dist_key) { | ||||
|             return child.insert(data,id,dist,depth+1); | ||||
|         } else { | ||||
|             self.children.insert(dist_key,Self::from_id(id)); | ||||
|             return depth; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn get_id(&self) -> Option<u32> { | ||||
|         self.ids.iter().copied().next() | ||||
|     } | ||||
| 
 | ||||
|     fn is_empty(&self) -> bool { | ||||
|         return self.ids.is_empty(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct BKTree { | ||||
|     base_id: u32, | ||||
|     root: BKTreeNode, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| impl BKTree { | ||||
|     pub fn new(data: &[String], base_id: u32) -> Self { | ||||
|         let dist = eddie::str::Levenshtein::new(); | ||||
|         let root = BKTreeNode::new(data, &dist); | ||||
|         Self {base_id,root} | ||||
|     } | ||||
| 
 | ||||
|     pub fn id(&self) -> u32 { | ||||
|         self.base_id | ||||
|     } | ||||
| 
 | ||||
|     pub fn dump(&self, fh: &mut BufWriter<File>) -> EdLrrResult<()> { | ||||
|         let options = bincode::DefaultOptions::new(); | ||||
|         let amt = options.serialized_size(self)?; | ||||
|         println!("Writing {}",amt); | ||||
|         options.serialize_into(fh,self)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn lookup(&self, name: &str) -> u32 { | ||||
|         todo!(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										0
									
								
								rust/src/dot_impls.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								rust/src/dot_impls.rs
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										196
									
								
								rust/src/edsm.rs
									
										
									
									
									
								
							
							
						
						
									
										196
									
								
								rust/src/edsm.rs
									
										
									
									
									
								
							|  | @ -1,196 +0,0 @@ | |||
| use crate::common::get_mult; | ||||
| use crate::common::SystemSerde; | ||||
| use fnv::FnvHashMap; | ||||
| use pyo3::prelude::*; | ||||
| use serde::Deserialize; | ||||
| use serde_json::Result; | ||||
| use std::fs::File; | ||||
| use std::io::Seek; | ||||
| use std::io::{BufRead, BufReader, BufWriter, SeekFrom}; | ||||
| use std::path::PathBuf; | ||||
| use std::str; | ||||
| use std::time::Instant; | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| #[allow(non_snake_case)] | ||||
| struct Body { | ||||
|     name: String, | ||||
|     subType: String, | ||||
|     #[serde(rename = "type")] | ||||
|     body_type: String, | ||||
|     systemId: i32, | ||||
|     systemId64: i64, | ||||
|     #[serde(rename = "distanceToArrival")] | ||||
|     distance: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Coords { | ||||
|     x: f32, | ||||
|     y: f32, | ||||
|     z: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct System { | ||||
|     id: i32, | ||||
|     id64: i64, | ||||
|     name: String, | ||||
|     coords: Coords, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct PreprocessState { | ||||
|     pub file: String, | ||||
|     pub message: String, | ||||
|     pub total: u64, | ||||
|     pub done: u64, | ||||
|     pub count: usize, | ||||
| } | ||||
| 
 | ||||
| fn process( | ||||
|     path: &PathBuf, | ||||
|     func: &mut dyn for<'r> FnMut(&'r str) -> (), | ||||
|     callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>, | ||||
| ) -> std::io::Result<()> { | ||||
|     let mut buffer = String::new(); | ||||
|     let fh = File::open(path)?; | ||||
|     let total_size = fh.metadata()?.len(); | ||||
|     let mut t_last = Instant::now(); | ||||
|     let mut reader = BufReader::new(fh); | ||||
|     let mut state = PreprocessState { | ||||
|         file: path.to_str().unwrap().to_owned(), | ||||
|         total: total_size, | ||||
|         done: 0, | ||||
|         count: 0, | ||||
|         message: format!("Processing {} ...", path.to_str().unwrap()), | ||||
|     }; | ||||
|     println!("Loading {} ...", path.to_str().unwrap()); | ||||
|     while let Ok(n) = reader.read_line(&mut buffer) { | ||||
|         if n == 0 { | ||||
|             break; | ||||
|         } | ||||
|         buffer = buffer.trim_end().trim_end_matches(|c| c == ',').to_string(); | ||||
|         if !buffer.is_empty() { | ||||
|             func(&buffer); | ||||
|         } | ||||
|         let pos = reader.seek(SeekFrom::Current(0)).unwrap(); | ||||
|         state.done = pos; | ||||
|         state.count += 1; | ||||
|         if t_last.elapsed().as_millis() > 100 { | ||||
|             callback(&state)?; | ||||
|             t_last = Instant::now(); | ||||
|         } | ||||
|         buffer.clear(); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn process_systems( | ||||
|     path: &PathBuf, | ||||
|     callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>, | ||||
| ) -> FnvHashMap<i32, System> { | ||||
|     let mut ret = FnvHashMap::default(); | ||||
|     process( | ||||
|         path, | ||||
|         &mut |line| { | ||||
|             let sys_res: Result<System> = serde_json::from_str(&line); | ||||
|             if let Ok(sys) = sys_res { | ||||
|                 ret.insert(sys.id, sys); | ||||
|             } else { | ||||
|                 eprintln!("\nError parsing: {}\n\t{:?}\n", line, sys_res.unwrap_err()); | ||||
|             } | ||||
|         }, | ||||
|         callback, | ||||
|     ) | ||||
|     .unwrap(); | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| pub fn build_index(path: &PathBuf) -> std::io::Result<()> { | ||||
|     let mut wtr = BufWriter::new(File::create(path.with_extension("idx"))?); | ||||
|     let mut idx: Vec<u64> = Vec::new(); | ||||
|     let mut records = (csv::Reader::from_path(path)?).into_deserialize::<SystemSerde>(); | ||||
|     loop { | ||||
|         idx.push(records.reader().position().byte()); | ||||
|         if records.next().is_none() { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     bincode::serialize_into(&mut wtr, &idx).unwrap(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn process_bodies( | ||||
|     path: &PathBuf, | ||||
|     out_path: &PathBuf, | ||||
|     systems: &mut FnvHashMap<i32, System>, | ||||
|     callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>, | ||||
| ) -> std::io::Result<()> { | ||||
|     println!( | ||||
|         "Processing {} into {} ...", | ||||
|         path.to_str().unwrap(), | ||||
|         out_path.to_str().unwrap(), | ||||
|     ); | ||||
|     let mut n: u32 = 0; | ||||
|     let mut wtr = csv::Writer::from_path(out_path)?; | ||||
|     process( | ||||
|         path, | ||||
|         &mut |line| { | ||||
|             if !line.contains("Star") { | ||||
|                 return; | ||||
|             } | ||||
|             let body_res: Result<Body> = serde_json::from_str(&line); | ||||
|             if let Ok(body) = body_res { | ||||
|                 if !body.body_type.contains("Star") { | ||||
|                     return; | ||||
|                 } | ||||
|                 if let Some(sys) = systems.get(&body.systemId) { | ||||
|                     let sub_type = body.subType; | ||||
|                     let mult = get_mult(&sub_type); | ||||
|                     let sys_name = sys.name.clone(); | ||||
|                     let rec = SystemSerde { | ||||
|                         id: n, | ||||
|                         star_type: sub_type, | ||||
|                         system: sys_name, | ||||
|                         body: body.name, | ||||
|                         mult, | ||||
|                         distance: body.distance, | ||||
|                         x: sys.coords.x, | ||||
|                         y: sys.coords.y, | ||||
|                         z: sys.coords.z, | ||||
|                     }; | ||||
|                     wtr.serialize(rec).unwrap(); | ||||
|                     n += 1; | ||||
|                 }; | ||||
|             } else { | ||||
|                 eprintln!("\nError parsing: {}\n\t{:?}\n", line, body_res.unwrap_err()); | ||||
|             } | ||||
|         }, | ||||
|         callback, | ||||
|     ) | ||||
|     .unwrap(); | ||||
|     println!("Total Systems: {}", n); | ||||
|     systems.clear(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| pub fn preprocess_files( | ||||
|     bodies: &PathBuf, | ||||
|     systems: &PathBuf, | ||||
|     out_path: &PathBuf, | ||||
|     callback: &dyn Fn(&PreprocessState) -> PyResult<PyObject>, | ||||
| ) -> std::io::Result<()> { | ||||
|     if !out_path.exists() { | ||||
|         let mut systems = process_systems(systems, &callback); | ||||
|         process_bodies(bodies, out_path, &mut systems, &callback)?; | ||||
|     } else { | ||||
|         println!( | ||||
|             "File '{}' exists, not overwriting it", | ||||
|             out_path.to_str().unwrap() | ||||
|         ); | ||||
|     } | ||||
|     println!("Building index..."); | ||||
|     println!("Index result: {:?}", build_index(&out_path)); | ||||
|     Ok(()) | ||||
| } | ||||
|  | @ -1,40 +1,25 @@ | |||
| use serde::{Deserialize, Serialize}; | ||||
| //! Spansh galaxy.json to csv converter
 | ||||
| use crate::common::{get_mult, System}; | ||||
| use eyre::Result; | ||||
| 
 | ||||
| use flate2::bufread::GzDecoder; | ||||
| 
 | ||||
| use log::*; | ||||
| use serde::Deserialize; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, BufWriter}; | ||||
| use std::io::{BufRead, BufReader, BufWriter, Seek}; | ||||
| use std::path::Path; | ||||
| use std::str; | ||||
| 
 | ||||
| #[derive(Debug, Clone, Serialize)] | ||||
| pub struct SystemSerde { | ||||
|     pub id: u32, | ||||
|     pub star_type: String, | ||||
|     pub system: String, | ||||
|     pub body: String, | ||||
|     pub mult: f32, | ||||
|     pub distance: f32, | ||||
|     pub x: f32, | ||||
|     pub y: f32, | ||||
|     pub z: f32, | ||||
| } | ||||
| 
 | ||||
| fn get_mult(star_type: &str) -> f32 { | ||||
|     if star_type.contains("White Dwarf") { | ||||
|         return 1.5; | ||||
|     } | ||||
|     if star_type.contains("Neutron") { | ||||
|         return 4.0; | ||||
|     } | ||||
|     1.0 | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Coords { | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| struct GalaxyCoords { | ||||
|     x: f32, | ||||
|     y: f32, | ||||
|     z: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Body { | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| struct GalaxyBody { | ||||
|     name: String, | ||||
|     #[serde(rename = "type")] | ||||
|     body_type: String, | ||||
|  | @ -44,70 +29,73 @@ struct Body { | |||
|     distance: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct System { | ||||
|     coords: Coords, | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| struct GalaxySystem { | ||||
|     coords: GalaxyCoords, | ||||
|     name: String, | ||||
|     bodies: Vec<Body>, | ||||
|     bodies: Vec<GalaxyBody>, | ||||
| } | ||||
| 
 | ||||
| pub fn process_galaxy_dump(path: &str) -> std::io::Result<()> { | ||||
|     let fh = File::create("stars.csv")?; | ||||
|     let mut wtr = csv::Writer::from_writer(BufWriter::new(fh)); | ||||
| /// Load compressed galaxy.json from `path` and write `stars.csv` to `out_path`
 | ||||
| pub fn process_galaxy_dump(path: &Path, out_path: &Path) -> Result<()> { | ||||
|     let out_path = out_path.with_extension("csv"); | ||||
|     let mut wtr = csv::WriterBuilder::new() | ||||
|         .has_headers(false) | ||||
|         .from_writer(BufWriter::new(File::create(out_path)?)); | ||||
|     let mut buffer = String::new(); | ||||
|     let mut bz2_reader = std::process::Command::new("7z") | ||||
|         .args(&["x", "-so", path]) | ||||
|         .stdout(std::process::Stdio::piped()) | ||||
|         .spawn() | ||||
|         .unwrap_or_else(|err| { | ||||
|             eprintln!("Failed to run 7z: {}", err); | ||||
|             eprintln!("Falling back to bzip2"); | ||||
|             std::process::Command::new("bzip2") | ||||
|                 .args(&["-d", "-c", path]) | ||||
|                 .stdout(std::process::Stdio::piped()) | ||||
|                 .spawn() | ||||
|                 .expect("Failed to execute bzip2!") | ||||
|         }); | ||||
|     let mut reader = BufReader::new( | ||||
|         bz2_reader | ||||
|             .stdout | ||||
|             .as_mut() | ||||
|             .expect("Failed to open stdout of child process"), | ||||
|     ); | ||||
|     let mut count = 0; | ||||
|     let rdr = BufReader::new(File::open(path)?); | ||||
|     let mut reader = BufReader::new(GzDecoder::new(rdr)); | ||||
|     let mut count: usize = 0; | ||||
|     let mut total: usize = 0; | ||||
|     let mut errors: usize = 0; | ||||
|     let mut bodies: usize = 0; | ||||
|     let mut systems = 0; | ||||
|     let max_len = File::metadata(reader.get_ref().get_ref().get_ref())?.len(); | ||||
|     while let Ok(n) = reader.read_line(&mut buffer) { | ||||
|         if n == 0 { | ||||
|             break; | ||||
|         } | ||||
|         buffer = buffer | ||||
|             .trim() | ||||
|             .trim_end_matches(|c| c == ',') | ||||
|             .trim() | ||||
|             .trim_end_matches(|c: char| c == ',' || c.is_whitespace()) | ||||
|             .to_string(); | ||||
|         if !buffer.contains("Star") { | ||||
|             continue; | ||||
|         total += 1; | ||||
|         if let Ok(sys) = serde_json::from_str::<GalaxySystem>(&buffer) { | ||||
|             let mut sys_rec = System { | ||||
|                 id: systems, | ||||
|                 mult: 1.0, | ||||
|                 name: sys.name, | ||||
|                 num_bodies: 0, | ||||
|                 pos: [sys.coords.x, sys.coords.y, sys.coords.z], | ||||
|                 has_scoopable: false, | ||||
|             }; | ||||
|         if let Ok(sys) = serde_json::from_str::<System>(&buffer) { | ||||
|             for b in &sys.bodies { | ||||
|                 if b.body_type == "Star" { | ||||
|                     let s = SystemSerde { | ||||
|                         id: count, | ||||
|                         star_type: b.sub_type.clone(), | ||||
|                         distance: b.distance, | ||||
|                         mult: get_mult(&b.sub_type), | ||||
|                         body: b.name.clone(), | ||||
|                         system: sys.name.clone(), | ||||
|                         x: sys.coords.x, | ||||
|                         y: sys.coords.y, | ||||
|                         z: sys.coords.z, | ||||
|                     }; | ||||
|                     wtr.serialize(s)?; | ||||
|             for b in sys.bodies.iter().filter(|b| b.body_type == "Star").cloned() { | ||||
|                 sys_rec.mult = sys_rec.mult.max(get_mult(&b.sub_type)); | ||||
|                 sys_rec.num_bodies += 1; | ||||
|                 for c in "KGBFOAM".chars() { | ||||
|                     if b.sub_type.starts_with(c) { | ||||
|                         sys_rec.has_scoopable |= true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             bodies += sys_rec.num_bodies as usize; | ||||
|             systems += 1; | ||||
|             count += 1; | ||||
|             wtr.serialize(sys_rec)?; | ||||
|             if count % 100_000 == 0 { | ||||
|                 let cur_pos = reader.get_ref().get_ref().get_ref().stream_position()?; | ||||
|                 let prc: f64 = ((cur_pos as f64) / (max_len as f64)) * 100.0; | ||||
|                 info!("[{:.2} %] {} systems written", prc, count); | ||||
|             } | ||||
|             } | ||||
|         } else { | ||||
|             errors += 1; | ||||
|         } | ||||
|         buffer.clear(); | ||||
|     } | ||||
|     println!("Total: {}", count); | ||||
|     info!("Total: {}", total); | ||||
|     info!("Bodies: {}", bodies); | ||||
|     info!("Systems: {}", systems); | ||||
|     info!("Processed: {}", count); | ||||
|     info!("Errors: {}", errors); | ||||
|     Ok(()) | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| //! Elite: Dangerous Journal Loadout even parser
 | ||||
| use crate::common::get_fsd_info; | ||||
| use crate::ship::Ship; | ||||
| 
 | ||||
| use eyre::Result; | ||||
| use regex::Regex; | ||||
| use serde::Deserialize; | ||||
| use std::collections::HashMap; | ||||
|  | @ -11,23 +13,23 @@ pub struct Event { | |||
|     pub event: EventData, | ||||
| } | ||||
| 
 | ||||
| #[serde(tag = "event")] | ||||
| #[derive(Clone, Debug, PartialEq, Deserialize)] | ||||
| #[serde(tag = "event")] | ||||
| pub enum EventData { | ||||
|     Loadout(Loadout), | ||||
|     #[serde(other)] | ||||
|     Unknown, | ||||
| } | ||||
| 
 | ||||
| #[serde(rename_all = "PascalCase")] | ||||
| #[derive(Clone, Debug, PartialEq, Deserialize)] | ||||
| #[serde(rename_all = "PascalCase")] | ||||
| pub struct Modifier { | ||||
|     label: String, | ||||
|     value: f32, | ||||
| } | ||||
| 
 | ||||
| #[serde(rename_all = "PascalCase")] | ||||
| #[derive(Clone, Debug, PartialEq, Deserialize)] | ||||
| #[serde(rename_all = "PascalCase")] | ||||
| pub struct Engineering { | ||||
|     modifiers: Vec<Modifier>, | ||||
| } | ||||
|  | @ -72,10 +74,7 @@ impl Engineering { | |||
| 
 | ||||
| impl Loadout { | ||||
|     fn get_booster(&self) -> Option<usize> { | ||||
|         self.modules | ||||
|             .iter() | ||||
|             .cloned() | ||||
|             .filter_map(|m| { | ||||
|         self.modules.iter().cloned().find_map(|m| { | ||||
|             let Module { item, .. } = m; | ||||
|             if item.starts_with("int_guardianfsdbooster") { | ||||
|                 return item | ||||
|  | @ -87,14 +86,10 @@ impl Loadout { | |||
|             } | ||||
|             return None; | ||||
|         }) | ||||
|             .next() | ||||
|     } | ||||
| 
 | ||||
|     fn get_fsd(&self) -> Option<(String, Option<Engineering>)> { | ||||
|         self.modules | ||||
|             .iter() | ||||
|             .cloned() | ||||
|             .filter_map(|m| { | ||||
|         self.modules.iter().cloned().find_map(|m| { | ||||
|             let Module { | ||||
|                 slot, | ||||
|                 engineering, | ||||
|  | @ -105,22 +100,20 @@ impl Loadout { | |||
|             } | ||||
|             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 booster = self.get_booster().unwrap_or(0); | ||||
|         let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$") | ||||
|             .unwrap() | ||||
|             .captures(&fsd.0); | ||||
|         let fsd_type: (usize, usize) = fsd_type | ||||
|             .map(|m| { | ||||
|             .and_then(|m| { | ||||
|                 let s = m.get(1)?.as_str().to_owned().parse().ok()?; | ||||
|                 let c = m.get(2)?.as_str().to_owned().parse().ok()?; | ||||
|                 return Some((c, s)); | ||||
|             }) | ||||
|             .flatten() | ||||
|             .ok_or(format!("Invalid FSD found: {}", &fsd.0))?; | ||||
|         let eng = fsd | ||||
|             .1 | ||||
|  | @ -141,10 +134,19 @@ impl Loadout { | |||
|         let opt_mass = fsd_info | ||||
|             .get("FSDOptimalMass") | ||||
|             .ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?; | ||||
|         return Ship::new( | ||||
|             self.ship_name, | ||||
|         let key = format!( | ||||
|             "[{}] {} ({})", | ||||
|             if !self.ship_name.is_empty() { | ||||
|                 self.ship_name | ||||
|             } else { | ||||
|                 "<NO NAME>".to_owned() | ||||
|             }, | ||||
|             self.ship_ident, | ||||
|             self.ship, | ||||
|             self.ship | ||||
|         ); | ||||
|         return Ok(( | ||||
|             key, | ||||
|             Ship::new( | ||||
|                 self.unladen_mass, | ||||
|                 self.fuel_capacity.main, | ||||
|                 self.fuel_capacity.main, | ||||
|  | @ -152,7 +154,8 @@ impl Loadout { | |||
|                 *max_fuel, | ||||
|                 *opt_mass, | ||||
|                 booster, | ||||
|         ); | ||||
|             )?, | ||||
|         )); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										818
									
								
								rust/src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										818
									
								
								rust/src/lib.rs
									
										
									
									
									
								
							|  | @ -1,144 +1,379 @@ | |||
| // #![deny(warnings)]
 | ||||
| #![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)] | ||||
| //! # Elite: Danerous Long Range Router
 | ||||
| pub mod common; | ||||
| pub mod edsm; | ||||
| pub mod galaxy; | ||||
| pub mod journal; | ||||
| pub mod mmap_csv; | ||||
| #[cfg(feature = "profiling")] | ||||
| pub mod profiling; | ||||
| pub mod route; | ||||
| pub mod search_algos; | ||||
| pub mod ship; | ||||
| 
 | ||||
| use bincode::Options; | ||||
| use csv::{Position, StringRecord}; | ||||
| use eddie::Levenshtein; | ||||
| // =========================
 | ||||
| use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM}; | ||||
| use std::alloc::System as SystemAlloc; | ||||
| use std::cell::RefMut; | ||||
| use std::collections::BTreeMap; | ||||
| use std::io::{BufWriter, Write}; | ||||
| use std::path::Path; | ||||
| use std::time::Instant; | ||||
| #[cfg(not(feature = "profiling"))] | ||||
| #[global_allocator] | ||||
| static GLOBAL: &StatsAlloc<SystemAlloc> = &INSTRUMENTED_SYSTEM; | ||||
| // =========================
 | ||||
| #[cfg(not(feature = "profiling"))] | ||||
| mod profiling { | ||||
|     pub fn init() {} | ||||
| } | ||||
| 
 | ||||
| extern crate derivative; | ||||
| use crate::common::{find_matches, SysEntry}; | ||||
| use crate::common::{find_matches, grid_stats, EdLrrError, SysEntry, System}; | ||||
| #[cfg(feature = "profiling")] | ||||
| use crate::profiling::*; | ||||
| use crate::route::{Router, SearchState}; | ||||
| use crate::ship::Ship; | ||||
| use eyre::Result; | ||||
| #[cfg(not(feature = "profiling"))] | ||||
| use log::*; | ||||
| use pyo3::exceptions::*; | ||||
| use pyo3::prelude::*; | ||||
| use pyo3::types::{PyDict, PyList, PyTuple}; | ||||
| use pyo3::PyObjectProtocol; | ||||
| use std::path::PathBuf; | ||||
| use pyo3::types::{IntoPyDict, PyDict, PyTuple}; | ||||
| use pyo3::{create_exception, PyObjectProtocol}; | ||||
| use route::{LineCache, PyModeConfig}; | ||||
| use std::{ | ||||
|     cell::RefCell, collections::HashMap, convert::TryInto, fs::File, io::BufReader, path::PathBuf, | ||||
| }; | ||||
| 
 | ||||
| #[cfg(feature = "profiling")] | ||||
| #[global_allocator] | ||||
| static GLOBAL: ProfiledAllocator<std::alloc::System> = | ||||
|     ProfiledAllocator::new(std::alloc::System, 1024); | ||||
| 
 | ||||
| create_exception!(_ed_lrr, RoutingError, PyException); | ||||
| create_exception!(_ed_lrr, ProcessingError, PyException); | ||||
| create_exception!(_ed_lrr, ResolveError, PyException); | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| enum RangeOrShip { | ||||
|     Range(f32), | ||||
|     Ship(Ship), | ||||
| } | ||||
| 
 | ||||
| impl FromPyObject<'_> for RangeOrShip { | ||||
|     fn extract(ob: &PyAny) -> PyResult<Self> { | ||||
|         if let Ok(n) = ob.extract() { | ||||
|             return Ok(Self::Range(n)); | ||||
|         } | ||||
|         let s: PyShip = ob.extract()?; | ||||
|         return Ok(Self::Ship(s.ship)); | ||||
|     } | ||||
| } | ||||
| #[pyclass(dict)] | ||||
| #[derive(Debug)] | ||||
| #[text_signature = "(callback, /)"] | ||||
| #[pyo3(text_signature = "(callback, /)")] | ||||
| struct PyRouter { | ||||
|     router: Router, | ||||
|     primary_only: bool, | ||||
|     stars_path: String, | ||||
|     stars_path: Option<String>, | ||||
| } | ||||
| 
 | ||||
| impl PyRouter { | ||||
|     fn check_stars(&self) -> PyResult<PathBuf> { | ||||
|         self.stars_path | ||||
|             .as_ref() | ||||
|             .ok_or_else(|| PyErr::from(EdLrrError::RuntimeError("no stars.csv loaded".to_owned()))) | ||||
|             .map(PathBuf::from) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #[pymethods] | ||||
| impl PyRouter { | ||||
|     #[new] | ||||
|     #[args(callback = "None")] | ||||
|     fn new(callback: Option<PyObject>, py: Python<'static>) -> PyResult<Self> { | ||||
|         Ok(PyRouter { | ||||
|             router: Router::new(Box::new( | ||||
|                 move |state: &SearchState| { | ||||
|     fn new(callback: Option<PyObject>) -> Self { | ||||
|         let mut router = Router::new(); | ||||
|         if callback.is_some() { | ||||
|             router.set_callback(Box::new(move |state: &SearchState| { | ||||
|                 let gil_guard = Python::acquire_gil(); | ||||
|                 let py = gil_guard.python(); | ||||
|                 match callback.as_ref() { | ||||
|                     Some(cb) => cb.call(py, (state.clone(),), None), | ||||
|                     None => Ok(py.None()), | ||||
|                 } | ||||
|             })) | ||||
|         } | ||||
|         PyRouter { | ||||
|             router, | ||||
|             stars_path: None, | ||||
|         } | ||||
|             )), | ||||
|             primary_only: false, | ||||
|             stars_path: String::from(""), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(ship, /)"] | ||||
|     fn set_ship(&mut self, py: Python, ship: &PyShip) -> PyResult<PyObject> { | ||||
|         self.router.set_ship(ship.ship.clone()); | ||||
|     #[args(primary_only = "false", immediate = "false")] | ||||
|     #[pyo3(text_signature = "(path, primary_only, /)")] | ||||
|     fn load(&mut self, path: String, py: Python, immediate: bool) -> PyResult<PyObject> { | ||||
|         self.stars_path = Some(path); | ||||
|         if immediate { | ||||
|             let stars_path = self.check_stars()?; | ||||
|             let route_res = self.router.load(&stars_path); | ||||
|             if let Err(err_msg) = route_res { | ||||
|                 return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|             }; | ||||
|         } | ||||
|         Ok(py.None()) | ||||
|     } | ||||
| 
 | ||||
|     #[args(primary_only = "false")] | ||||
|     #[text_signature = "(path, primary_only, /)"] | ||||
|     fn load( | ||||
|         &mut self, | ||||
|         path: String, | ||||
|         primary_only: bool, | ||||
|         py: Python, | ||||
|     ) -> PyResult<PyObject> { | ||||
|         self.stars_path = path; | ||||
|         self.primary_only = primary_only; | ||||
|     #[pyo3(text_signature = "(/)")] | ||||
|     fn unload(&mut self, py: Python) -> PyObject { | ||||
|         self.router.unload(); | ||||
|         py.None() | ||||
|     } | ||||
| 
 | ||||
|     fn plot(&mut self, py: Python) -> PyResult<PyObject> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let route_res = self.router.load(&stars_path); | ||||
|         if let Err(err_msg) = route_res { | ||||
|             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|         }; | ||||
|         let mut max_v = [0f32, 0f32, 0f32]; | ||||
|         let mut min_v = [0f32, 0f32, 0f32]; | ||||
|         for node in self.router.get_tree().iter() { | ||||
|             for i in 0..3 { | ||||
|                 if node.pos[i] > max_v[i] { | ||||
|                     max_v[i] = node.pos[i]; | ||||
|                 } | ||||
|                 if node.pos[i] < min_v[i] { | ||||
|                     min_v[i] = node.pos[i]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         let plot_bbox: ((f32, f32), (f32, f32)) = ((min_v[0], max_v[0]), (min_v[2], max_v[2])); | ||||
|         Ok(plot_bbox.to_object(py)) | ||||
|     } | ||||
| 
 | ||||
|     fn run_bfs(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let route_res = self.router.load(&stars_path); | ||||
|         if let Err(err_msg) = route_res { | ||||
|             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|         }; | ||||
|         self.router | ||||
|             .precomp_bfs(range) | ||||
|             .map_err(PyErr::new::<RoutingError, _>) | ||||
|             .map(|_| py.None()) | ||||
|     } | ||||
| 
 | ||||
|     fn precompute_graph(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let route_res = self.router.load(&stars_path); | ||||
|         if let Err(err_msg) = route_res { | ||||
|             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|         }; | ||||
|         self.router | ||||
|             .precompute_graph(range) | ||||
|             .map_err(PyErr::new::<RoutingError, _>) | ||||
|             .map(|_| py.None()) | ||||
|     } | ||||
| 
 | ||||
|     fn nb_perf_test(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let route_res = self.router.load(&stars_path); | ||||
|         if let Err(err_msg) = route_res { | ||||
|             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|         }; | ||||
|         let mut nbmap = BTreeMap::new(); | ||||
|         let tree = self.router.get_tree(); | ||||
|         let total_nodes = tree.size(); | ||||
|         for (n, node) in tree.iter().enumerate() { | ||||
|             let nbs = self | ||||
|                 .router | ||||
|                 .neighbours(node, range) | ||||
|                 .map(|nb| nb.id) | ||||
|                 .collect::<Vec<_>>(); | ||||
|             nbmap.insert(node.id, nbs); | ||||
|             if n % 100_000 == 0 { | ||||
|                 println!("{}/{}", n, total_nodes); | ||||
|             } | ||||
|         } | ||||
|         println!("{}", nbmap.len()); | ||||
|         Ok(py.None()) | ||||
|     } | ||||
| 
 | ||||
|     #[args(greedyness = "0.5", num_workers = "0", beam_width = "0")] | ||||
|     #[text_signature = "(hops, range, greedyness, beam_width, num_workers, /)"] | ||||
|     fn precompute_neighbors(&mut self, range: f32, py: Python) -> PyResult<PyObject> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let route_res = self.router.load(&stars_path); | ||||
|         if let Err(err_msg) = route_res { | ||||
|             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|         }; | ||||
|         self.router | ||||
|             .precompute_all(range) | ||||
|             .map_err(PyErr::new::<RoutingError, _>) | ||||
|             .map(|_| py.None()) | ||||
|     } | ||||
| 
 | ||||
|     #[args(
 | ||||
|         greedyness = "0.5", | ||||
|         max_dist = "0.0", | ||||
|         num_workers = "0", | ||||
|         beam_width = "BeamWidth::Absolute(0)" | ||||
|     )] | ||||
|     #[pyo3(text_signature = "(hops, range, mode, num_workers, /)")] | ||||
|     fn route( | ||||
|         &mut self, | ||||
|         hops: &PyList, | ||||
|         range: Option<f32>, | ||||
|         greedyness: f32, | ||||
|         beam_width: usize, | ||||
|         hops: Vec<SysEntry>, | ||||
|         range: RangeOrShip, | ||||
|         mode: Option<PyModeConfig>, | ||||
|         num_workers: usize, | ||||
|         py: Python, | ||||
|     ) -> PyResult<PyObject> { | ||||
|         let route_res = self | ||||
|             .router | ||||
|             .load(&PathBuf::from(self.stars_path.clone()), self.primary_only); | ||||
|     ) -> PyResult<Vec<common::System>> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let route_res = self.router.load(&stars_path); | ||||
|         if let Err(err_msg) = route_res { | ||||
|             return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|             return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|         }; | ||||
|         let mut sys_entries: Vec<SysEntry> = Vec::new(); | ||||
|         for hop in hops { | ||||
|             if let Ok(id) = hop.extract() { | ||||
|                 sys_entries.push(SysEntry::ID(id)); | ||||
|             } else { | ||||
|                 sys_entries.push(SysEntry::parse(hop.extract()?)); | ||||
|             } | ||||
|         } | ||||
|         println!("Resolving systems..."); | ||||
|         let ids: Vec<u32> = match resolve(&sys_entries, &self.router.path) { | ||||
|             Ok(ids) => ids, | ||||
|         info!("Resolving systems..."); | ||||
|         let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) { | ||||
|             Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(), | ||||
|             Err(err_msg) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|                 return Err(EdLrrError::ResolveError(err_msg).into()); | ||||
|             } | ||||
|         }; | ||||
|         match self | ||||
|             .router | ||||
|             .compute_route(&ids, range, greedyness, beam_width, num_workers) | ||||
|         { | ||||
|             Ok(route) => { | ||||
|                 let py_route: Vec<_> = route.iter().map(|hop| hop.to_object(py)).collect(); | ||||
|                 Ok(py_route.to_object(py)) | ||||
|         let mut is_default = false; | ||||
|         let mut is_ship = false; | ||||
|         info!("{:?}", mode); | ||||
|         let mut mode = match mode { | ||||
|             Some(mode) => mode, | ||||
|             None => { | ||||
|                 let mode = PyModeConfig::default(); | ||||
|                 is_default = true; | ||||
|                 mode | ||||
|             } | ||||
|             Err(err_msg) => Err(PyErr::new::<RuntimeError, _>(err_msg)), | ||||
|         }; | ||||
|         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(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         let range = match range { | ||||
|             RangeOrShip::Range(r) => Some(r), | ||||
|             RangeOrShip::Ship(ship) => { | ||||
|                 mode.mode = "ship".into(); | ||||
|                 mode.ship = Some(ship); | ||||
|                 is_ship = true; | ||||
|                 None | ||||
|             } | ||||
|         }; | ||||
|         info!("{:?}", mode); | ||||
|         let mode = mode.try_into()?; | ||||
|         if is_default && !is_ship { | ||||
|             warn!("no mode specified, defaulting to {}", mode); | ||||
|         } | ||||
|         #[cfg(not(feature = "profiling"))] | ||||
|         let reg = Region::new(GLOBAL); | ||||
|         let res = match self.router.compute_route(&ids, range, mode, num_workers) { | ||||
|             Ok(route) => Ok(route), | ||||
|             Err(err_msg) => Err(PyErr::new::<RoutingError, _>(err_msg)), | ||||
|         }; | ||||
|         #[cfg(not(feature = "profiling"))] | ||||
|         println!("{:?}", reg.change()); | ||||
|         return res; | ||||
|     } | ||||
| 
 | ||||
|     fn perf_test(&self, callback: PyObject, py: Python) -> PyResult<PyObject> { | ||||
|         use common::TreeNode; | ||||
|         let node = TreeNode { | ||||
|             pos: [-65.21875, 7.75, -111.03125], | ||||
|             flags: 1, | ||||
|             id: 0, | ||||
|         }; | ||||
|         let goal = TreeNode { | ||||
|             pos: [-9530.5, -910.28125, 19808.125], | ||||
|             flags: 1, | ||||
|             id: 1, | ||||
|         }; | ||||
|         let kwargs = vec![("goal", goal), ("node", node)].into_py_dict(py); | ||||
|         let mut n: usize = 0; | ||||
|         let mut d: f64 = 0.0; | ||||
|         let num_loops = 10_000_000; | ||||
|         loop { | ||||
|             let pool = unsafe { Python::new_pool(py) }; | ||||
|             let t_start = std::time::Instant::now(); | ||||
|             for _ in 0..num_loops { | ||||
|                 let val: f64 = callback.call(py, (), Some(kwargs))?.extract(py)?; | ||||
|             } | ||||
|             d += t_start.elapsed().as_secs_f64(); | ||||
|             drop(pool); | ||||
|             n += num_loops; | ||||
|             let dt = std::time::Duration::from_secs_f64(d / (n as f64)); | ||||
|             println!("{}: {:?}", n, dt); | ||||
|         } | ||||
|         Ok(py.None()) | ||||
|     } | ||||
| 
 | ||||
|     #[args(grid_size = "1.0")] | ||||
|     #[pyo3(text_signature = "(grid_size)")] | ||||
|     fn get_grid(&self, grid_size: f32, py: Python) -> PyResult<PyObject> { | ||||
|         let stars_path = self.check_stars()?; | ||||
|         grid_stats(&stars_path, grid_size) | ||||
|             .map(|ret| ret.to_object(py)) | ||||
|             .map_err(PyErr::new::<PyRuntimeError, _>) | ||||
|     } | ||||
| 
 | ||||
|     #[args(hops = "*")] | ||||
|     #[text_signature = "(sys_1, sys_2, ..., /)"] | ||||
|     fn resolve_systems(&self, hops: &PyTuple, py: Python) -> PyResult<PyObject> { | ||||
|         let mut sys_entries: Vec<SysEntry> = Vec::new(); | ||||
|         for hop in hops { | ||||
|             if let Ok(id) = hop.extract() { | ||||
|                 sys_entries.push(SysEntry::ID(id)); | ||||
|             } else { | ||||
|                 sys_entries.push(SysEntry::parse(hop.extract()?)); | ||||
|             } | ||||
|         } | ||||
|         println!("Resolving systems..."); | ||||
|         let stars_path = PathBuf::from(self.stars_path.clone()); | ||||
|         let ids: Vec<u32> = match resolve(&sys_entries, &stars_path) { | ||||
|             Ok(ids) => ids, | ||||
|     #[pyo3(text_signature = "(sys_1, sys_2, ..., /)")] | ||||
|     fn resolve(&self, hops: Vec<SysEntry>, py: Python) -> PyResult<PyObject> { | ||||
|         info!("Resolving systems..."); | ||||
|         let stars_path = self.check_stars()?; | ||||
|         let systems: Vec<System> = match resolve(&hops, &stars_path, false) { | ||||
|             Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(), | ||||
|             Err(err_msg) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|                 return Err(EdLrrError::ResolveError(err_msg).into()); | ||||
|             } | ||||
|         }; | ||||
|         let ret: Vec<(_, u32)> = hops.into_iter().zip(ids.into_iter()).collect(); | ||||
|         let ret: Vec<(_, System)> = hops | ||||
|             .into_iter() | ||||
|             .zip(systems.iter()) | ||||
|             .map(|(id, sys)| (id, sys.clone())) | ||||
|             .collect(); | ||||
|         Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py)) | ||||
|     } | ||||
| 
 | ||||
|     #[staticmethod] | ||||
|     fn preprocess_edsm() -> PyResult<()> { | ||||
|         todo!("Implement EDSM Preprocessor") | ||||
|     fn str_tree_test(&self) -> common::EdLrrResult<()> { | ||||
|         use common::BKTree; | ||||
|         const CHUNK_SIZE: usize = 4_000_000; | ||||
|         let path = self.check_stars()?; | ||||
|         let reader: csv::Reader<File> = csv::ReaderBuilder::new() | ||||
|             .has_headers(false) | ||||
|             .from_path(path) | ||||
|             .map_err(EdLrrError::from)?; | ||||
|         let mut data: Vec<String> = Vec::with_capacity(CHUNK_SIZE); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut base_id=0; | ||||
|         let mut wr = BufWriter::new(File::create("test.bktree")?); | ||||
|         for sys in reader.into_deserialize::<System>() { | ||||
|             let sys = sys?; | ||||
|             data.push(sys.name); | ||||
|             if data.len()>CHUNK_SIZE { | ||||
|                 let tree = BKTree::new(&data, base_id); | ||||
|                 tree.dump(&mut wr)?; | ||||
|                 base_id=sys.id; | ||||
|             } | ||||
| 
 | ||||
|     #[staticmethod] | ||||
|     fn preprocess_galaxy() -> PyResult<()> { | ||||
|         todo!("Implement galaxy.json Preprocessor") | ||||
|         } | ||||
|         if !data.is_empty() { | ||||
|             let tree = BKTree::new(&data, base_id); | ||||
|             tree.dump(&mut wr)?; | ||||
|         } | ||||
|         wr.flush()?; | ||||
|         println!("Took: {:?}", t_start.elapsed()); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -153,52 +388,91 @@ impl PyObjectProtocol for PyRouter { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| fn resolve(entries: &[SysEntry], path: &PathBuf) -> Result<Vec<u32>, String> { | ||||
| enum ResolveResult { | ||||
|     System(System), | ||||
|     ID(u32), | ||||
| } | ||||
| 
 | ||||
| impl ResolveResult { | ||||
|     fn into_id(self) -> u32 { | ||||
|         match self { | ||||
|             Self::System(sys) => sys.id, | ||||
|             Self::ID(id) => id, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn into_system(self) -> System { | ||||
|         if let Self::System(sys) = self { | ||||
|             return sys; | ||||
|         } | ||||
|         panic!("Tried to unwrap ID into System"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<ResolveResult>, String> { | ||||
|     let mut names: Vec<String> = Vec::new(); | ||||
|     let mut ids: Vec<u32> = Vec::new(); | ||||
|     let mut ret: Vec<u32> = Vec::new(); | ||||
|     let mut needs_rtree = false; | ||||
|     for ent in entries { | ||||
|         match ent { | ||||
|             SysEntry::Name(name) => names.push(name.to_owned()), | ||||
|             SysEntry::ID(id) => ids.push(*id), | ||||
|             SysEntry::Pos(_) => { | ||||
|                 needs_rtree |= true; | ||||
|             } | ||||
|             _ => (), | ||||
|         } | ||||
|     } | ||||
|     if !path.exists() { | ||||
|         return Err(format!( | ||||
|             "Source file \"{:?}\" does not exist!", | ||||
|             path.display() | ||||
|         )); | ||||
|         return Err(format!("Source file {:?} does not exist!", path.display())); | ||||
|     } | ||||
| 
 | ||||
|     let name_ids = find_matches(path, names, false)?; | ||||
|     let name_ids = if !names.is_empty() { | ||||
|         mmap_csv::mmap_csv(path, names)? | ||||
|     } else { | ||||
|         HashMap::new() | ||||
|     }; | ||||
|     let tmp_r = needs_rtree | ||||
|         .then(|| { | ||||
|             let mut r = Router::new(); | ||||
|             r.load(path).map(|_| r) | ||||
|         }) | ||||
|         .transpose()?; | ||||
|     for ent in entries { | ||||
|         match ent { | ||||
|             SysEntry::Name(name) => { | ||||
|                 let ent_res = name_ids | ||||
|                     .get(&name.to_owned()) | ||||
|                     .get(name) | ||||
|                     .ok_or(format!("System {} not found", name))?; | ||||
|                 let sys = ent_res | ||||
|                     .1 | ||||
|                     .as_ref() | ||||
|                     .ok_or(format!("System {} not found", name))?; | ||||
|                 if ent_res.0 < 0.75 { | ||||
|                     println!( | ||||
|                         "WARNING: {} match to {} with low confidence ({:.2}%)", | ||||
|                         name, | ||||
|                         sys.system, | ||||
|                         ent_res.0 * 100.0 | ||||
|                     ); | ||||
|                 } | ||||
|                 ret.push(sys.id); | ||||
|                 ret.push(*sys); | ||||
|             } | ||||
|             SysEntry::ID(id) => ret.push(*id), | ||||
|             SysEntry::Pos((x, y, z)) => ret.push( | ||||
|                 tmp_r | ||||
|                     .as_ref() | ||||
|                     .unwrap() | ||||
|                     .closest(&[*x, *y, *z]) | ||||
|                     .ok_or("No systems loaded!")? | ||||
|                     .id, | ||||
|             ), | ||||
|         } | ||||
|     } | ||||
|     Ok(ret) | ||||
|     if id_only { | ||||
|         return Ok(ret.iter().map(|id| ResolveResult::ID(*id)).collect()); | ||||
|     } else { | ||||
|         let mut lc = route::LineCache::create(path)?; | ||||
|         let mut systems = vec![]; | ||||
|         for id in ret { | ||||
|             let sys = ResolveResult::System(lc.get(id)?.unwrap()); | ||||
|             systems.push(sys) | ||||
|         } | ||||
|         return Ok(systems); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[pyclass(dict)] | ||||
| #[derive(Debug)] | ||||
| #[derive(Debug, Clone)] | ||||
| struct PyShip { | ||||
|     ship: Ship, | ||||
| } | ||||
|  | @ -219,8 +493,8 @@ impl PyShip { | |||
|     #[staticmethod] | ||||
|     fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> { | ||||
|         match Ship::new_from_json(loadout) { | ||||
|             Ok(ship) => Ok((PyShip { ship }).into_py(py)), | ||||
|             Err(err_msg) => Err(PyErr::new::<ValueError, _>(err_msg)), | ||||
|             Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)), | ||||
|             Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)), | ||||
|         } | ||||
|     } | ||||
|     #[staticmethod] | ||||
|  | @ -228,15 +502,15 @@ impl PyShip { | |||
|         let mut ship = match Ship::new_from_journal() { | ||||
|             Ok(ship) => ship, | ||||
|             Err(err_msg) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|                 return Err(PyErr::new::<PyValueError, _>(err_msg)); | ||||
|             } | ||||
|         }; | ||||
|         let ships: Vec<(PyObject, PyObject)> = ship | ||||
|             .drain() | ||||
|             .map(|(k, v)| { | ||||
|                 let k_py = k.to_object(py); | ||||
|                 let v_py = (PyShip { ship: v }).into_py(py); | ||||
|                 (k_py, v_py) | ||||
|             .map(|(ship_name, ship)| { | ||||
|                 let ship_name_py = ship_name.to_object(py); | ||||
|                 let ship_py = (PyShip { ship }).into_py(py); | ||||
|                 (ship_name_py, ship_py) | ||||
|             }) | ||||
|             .collect(); | ||||
|         Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) | ||||
|  | @ -246,239 +520,141 @@ impl PyShip { | |||
|         self.ship.to_object(py) | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(dist, /)"] | ||||
|     fn fuel_cost(&self, _py: Python, dist: f32) -> PyResult<f32> { | ||||
|         Ok(self.ship.fuel_cost(dist)) | ||||
|     #[pyo3(text_signature = "(dist, /)")] | ||||
|     fn fuel_cost(&self, _py: Python, dist: f32) -> f32 { | ||||
|         self.ship.fuel_cost(dist) | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(/)"] | ||||
|     fn range(&self, _py: Python) -> PyResult<f32> { | ||||
|         Ok(self.ship.range()) | ||||
|     #[getter] | ||||
|     fn range(&self, _py: Python) -> f32 { | ||||
|         self.ship.range() | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(/)"] | ||||
|     fn max_range(&self, _py: Python) -> PyResult<f32> { | ||||
|         Ok(self.ship.max_range()) | ||||
|     #[getter] | ||||
|     fn max_range(&self, _py: Python) -> f32 { | ||||
|         self.ship.max_range() | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(dist, /)"] | ||||
|     fn make_jump(&mut self, dist: f32, _py: Python) -> PyResult<Option<f32>> { | ||||
|         Ok(self.ship.make_jump(dist)) | ||||
|     #[pyo3(text_signature = "(dist, /)")] | ||||
|     fn make_jump(&mut self, dist: f32, _py: Python) -> Option<f32> { | ||||
|         self.ship.make_jump(dist) | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(dist, /)"] | ||||
|     fn can_jump(&self, dist: f32, _py: Python) -> PyResult<bool> { | ||||
|         Ok(self.ship.can_jump(dist)) | ||||
|     #[pyo3(text_signature = "(dist, /)")] | ||||
|     fn can_jump(&self, dist: f32, _py: Python) -> bool { | ||||
|         self.ship.can_jump(dist) | ||||
|     } | ||||
| 
 | ||||
|     #[args(fuel_amount = "None")] | ||||
|     #[text_signature = "(fuel_amount, /)"] | ||||
|     fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) -> PyResult<()> { | ||||
|     #[pyo3(text_signature = "(fuel_amount, /)")] | ||||
|     fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) { | ||||
|         if let Some(fuel) = fuel_amount { | ||||
|             self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity) | ||||
|         } else { | ||||
|             self.ship.fuel_mass = self.ship.fuel_capacity; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     #[text_signature = "(factor, /)"] | ||||
|     fn boost(&mut self, factor: f32, _py: Python) -> PyResult<()> { | ||||
|     #[pyo3(text_signature = "(factor, /)")] | ||||
|     fn boost(&mut self, factor: f32, _py: Python) { | ||||
|         self.ship.boost(factor); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl PyShip { | ||||
|     fn get_ship(&self) -> Ship { | ||||
|         self.ship.clone() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[pyfunction] | ||||
| fn preprocess_edsm( | ||||
|     _bodies_path: &str, | ||||
|     _systems_path: &str, | ||||
|     _out_path: &str, | ||||
|     _py: Python, | ||||
| ) -> PyResult<()> { | ||||
|     Err(pyo3::exceptions::PyNotImplementedError::new_err( | ||||
|         "please use Spansh's Galaxy dump and preprocess_galaxy()", | ||||
|     )) | ||||
| } | ||||
| 
 | ||||
| fn to_py_value(value: eval::Value, py: Python) -> PyResult<PyObject> { | ||||
|     type Value = eval::Value; | ||||
|     match value { | ||||
|         Value::String(s) => Ok(s.to_object(py)), | ||||
|         Value::Number(n) => { | ||||
|             if let Some(n) = n.as_u64() { | ||||
|                 return Ok(n.to_object(py)); | ||||
|             } | ||||
|             if let Some(n) = n.as_i64() { | ||||
|                 return Ok(n.to_object(py)); | ||||
|             } | ||||
|             return Ok(n.as_f64().unwrap().to_object(py)); | ||||
|         } | ||||
|         Value::Bool(b) => Ok(b.to_object(py)), | ||||
|         Value::Array(mut t) => { | ||||
|             let mut res: Vec<PyObject> = vec![]; | ||||
|             for v in t.drain(..) { | ||||
|                 res.push(to_py_value(v, py)?); | ||||
|             } | ||||
|             Ok(PyTuple::new(py, &res).to_object(py)) | ||||
|         } | ||||
|         Value::Object(o) => { | ||||
|             let res = PyDict::new(py); | ||||
|             for (k, v) in o.iter() { | ||||
|                 res.set_item(k, to_py_value(v.clone(), py)?)?; | ||||
|             } | ||||
|             Ok(res.to_object(py)) | ||||
|         } | ||||
|         Value::Null => Ok(py.None()), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn to_py(res: Result<eval::Value, eval::Error>, py: Python) -> PyResult<PyObject> { | ||||
|     res.map_err(|e| PyErr::from(EdLrrError::EvalError(e))) | ||||
|         .and_then(|r| to_py_value(r, py)) | ||||
| } | ||||
| 
 | ||||
| #[pyfunction] | ||||
| #[pyo3(text_signature = "(expr)")] | ||||
| fn expr_test(expr: &str, py: Python) -> PyResult<PyObject> { | ||||
|     use eval::{to_value, Expr, Value}; | ||||
|     let mut res = Expr::new(expr) | ||||
|         .compile() | ||||
|         .map_err(|e| PyErr::from(EdLrrError::EvalError(e)))?; | ||||
|     let mut hm: HashMap<&str, Value> = HashMap::new(); | ||||
|     hm.insert("foo", to_value(42)); | ||||
|     hm.insert("bar", to_value((-1, -2, -3))); | ||||
|     res = res.value("x", vec!["Hello", "world", "!"]); | ||||
|     res = res.value("y", 42); | ||||
|     res = res.value("p", (2.17, 5.14, 1.62)); | ||||
|     res = res.value("hw", "Hello World!"); | ||||
|     res = res.value("hm", hm); | ||||
|     to_py(res.exec(), py) | ||||
| } | ||||
| 
 | ||||
| #[pyfunction] | ||||
| #[pyo3(text_signature = "(path, out_path, /)")] | ||||
| fn preprocess_galaxy(path: &str, out_path: &str) -> PyResult<()> { | ||||
|     use common::build_index; | ||||
|     use galaxy::process_galaxy_dump; | ||||
|     let path = PathBuf::from(path); | ||||
|     let out_path = PathBuf::from(out_path); | ||||
|     process_galaxy_dump(&path, &out_path).unwrap(); | ||||
|     build_index(&out_path)?; | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| #[pymodule] | ||||
| pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> { | ||||
|     better_panic::install(); | ||||
| 
 | ||||
|     pyo3_log::init(); | ||||
|     profiling::init(); | ||||
|     m.add_class::<PyRouter>()?; | ||||
|     m.add_class::<PyShip>()?; | ||||
|     /* | ||||
|     #[pyfn(m, "get_ships_from_journal")] | ||||
|     fn get_ships_from_journal(py: Python) -> PyResult<PyObject> { | ||||
|         let ship = match Ship::new_from_journal() { | ||||
|             Ok(ship) => ship, | ||||
|             Err(err_msg) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|             } | ||||
|         }; | ||||
|         let ships: Vec<(_,_)> = ship.iter().map(|(k,v)| (k.to_object(py),v.to_object(py))).collect(); | ||||
|         Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     #[pyfn(m, "get_ships_from_loadout")] | ||||
|     fn get_ship_from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> { | ||||
|         let ship = match Ship::new_from_json(loadout) { | ||||
|             Ok(ship) => ship, | ||||
|             Err(err_msg) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|             } | ||||
|         }; | ||||
|         Ok(ship.to_object(py)) | ||||
|     } | ||||
|     */ | ||||
|     m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_galaxy))?; | ||||
|     m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_edsm))?; | ||||
|     m.add_wrapped(pyo3::wrap_pyfunction!(expr_test))?; | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| 
 | ||||
| /// Preprocess bodies.json and systemsWithCoordinates.json into stars.csv
 | ||||
|     #[pyfn(m, "preprocess")] | ||||
|     #[text_signature = "(infile_systems, infile_bodies, outfile, callback, /)"] | ||||
|     fn ed_lrr_preprocess( | ||||
|         py: Python<'static>, | ||||
|         infile_systems: String, | ||||
|         infile_bodies: String, | ||||
|         outfile: String, | ||||
|         callback: PyObject, | ||||
|     ) -> PyResult<PyObject> { | ||||
|         use preprocess::*; | ||||
|         let state = PyDict::new(py); | ||||
|         let state_dict = PyDict::new(py); | ||||
|         callback.call(py, (state_dict,), None).unwrap(); | ||||
|         let callback_wrapped = move |state: &PreprocessState| { | ||||
|             // println!("SEND: {:?}",state);
 | ||||
|             state_dict.set_item("file", state.file.clone())?; | ||||
|             state_dict.set_item("total", state.total)?; | ||||
|             state_dict.set_item("count", state.count)?; | ||||
|             state_dict.set_item("done", state.done)?; | ||||
|             state_dict.set_item("message", state.message.clone())?; | ||||
|             callback.call(py, (state_dict,), None) | ||||
|         }; | ||||
|         preprocess_files( | ||||
|             &PathBuf::from(infile_bodies), | ||||
|             &PathBuf::from(infile_systems), | ||||
|             &PathBuf::from(outfile), | ||||
|             &callback_wrapped, | ||||
|         ) | ||||
|         .unwrap(); | ||||
|         Ok(state.to_object(py)) | ||||
|     } | ||||
| 
 | ||||
|     /// Find system by name
 | ||||
|     #[pyfn(m, "find_sys")] | ||||
|     #[text_signature = "(sys_names, sys_list_path, /)"] | ||||
|     fn find_sys(py: Python, sys_names: Vec<String>, sys_list: String) -> PyResult<PyObject> { | ||||
|         let path = PathBuf::from(sys_list); | ||||
|         match find_matches(&path, sys_names, false) { | ||||
|             Ok(vals) => { | ||||
|                 let ret = PyDict::new(py); | ||||
|                 for (key, (diff, sys)) in vals { | ||||
|                     let ret_dict = PyDict::new(py); | ||||
|                     if let Some(val) = sys { | ||||
|                         let pos = PyList::new(py, val.pos.iter()); | ||||
|                         ret_dict.set_item("star_type", val.star_type.clone())?; | ||||
|                         ret_dict.set_item("system", val.system.clone())?; | ||||
|                         ret_dict.set_item("body", val.body.clone())?; | ||||
|                         ret_dict.set_item("distance", val.distance)?; | ||||
|                         ret_dict.set_item("pos", pos)?; | ||||
|                         ret_dict.set_item("id", val.id)?; | ||||
|                         ret.set_item(key, (diff, ret_dict).to_object(py))?; | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(ret.to_object(py)) | ||||
|             } | ||||
|             Err(e) => Err(PyErr::new::<ValueError, _>(e)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Compute a Route using the suplied parameters
 | ||||
|     #[pyfn(m, "route")] | ||||
|     #[text_signature = "(hops, range, mode, primary, permute, keep_first, keep_last, greedyness, precomp, path, num_workers, callback, /)"] | ||||
|     #[allow(clippy::too_many_arguments)] | ||||
|     fn py_route( | ||||
|         py: Python<'static>, | ||||
|         hops: Vec<&str>, | ||||
|         range: f32, | ||||
|         mode: String, | ||||
|         primary: bool, | ||||
|         permute: bool, | ||||
|         keep_first: bool, | ||||
|         keep_last: bool, | ||||
|         greedyness: Option<f32>, | ||||
|         precomp: Option<String>, | ||||
|         path: String, | ||||
|         num_workers: Option<usize>, | ||||
|         callback: PyObject, | ||||
|     ) -> PyResult<PyObject> { | ||||
|         use route::*; | ||||
|         let num_workers = num_workers.unwrap_or(1); | ||||
|         let mode = match Mode::parse(&mode) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(e)); | ||||
|             } | ||||
|         }; | ||||
|         let state_dict = PyDict::new(py); | ||||
|         { | ||||
|             let cb_res = callback.call(py, (state_dict,), None); | ||||
|             if cb_res.is_err() { | ||||
|                 println!("Error: {:?}", cb_res); | ||||
|             } | ||||
|         } | ||||
|         let callback_wrapped = move |state: &SearchState| { | ||||
|             state_dict.set_item("mode", state.mode.clone())?; | ||||
|             state_dict.set_item("system", state.system.clone())?; | ||||
|             state_dict.set_item("body", state.body.clone())?; | ||||
|             state_dict.set_item("depth", state.depth)?; | ||||
|             state_dict.set_item("queue_size", state.queue_size)?; | ||||
|             state_dict.set_item("d_rem", state.d_rem)?; | ||||
|             state_dict.set_item("d_total", state.d_total)?; | ||||
|             state_dict.set_item("prc_done", state.prc_done)?; | ||||
|             state_dict.set_item("n_seen", state.n_seen)?; | ||||
|             state_dict.set_item("prc_seen", state.prc_seen)?; | ||||
|             state_dict.set_item("from", state.from.clone())?; | ||||
|             state_dict.set_item("to", state.to.clone())?; | ||||
|             let cb_res = callback.call(py, (state_dict,), None); | ||||
|             if cb_res.is_err() { | ||||
|                 println!("Error: {:?}", cb_res); | ||||
|             } | ||||
|             cb_res | ||||
|         }; | ||||
|         let hops: Vec<SysEntry> = (hops.iter().map(|v| SysEntry::from_str(&v)).collect::<Result<Vec<SysEntry>,_>>())?; | ||||
|         println!("Resolving systems..."); | ||||
|         let hops: Vec<u32> = match resolve(&hops, &PathBuf::from(&path)) { | ||||
|             Ok(ids) => ids, | ||||
|             Err(err_msg) => { | ||||
|                 return Err(PyErr::new::<ValueError, _>(err_msg)); | ||||
|             } | ||||
|         }; | ||||
|         let opts = RouteOpts { | ||||
|             systems: hops, | ||||
|             range: Some(range), | ||||
|             file_path: PathBuf::from(path), | ||||
|             precomp_file: precomp.map(PathBuf::from), | ||||
|             callback: Box::new(callback_wrapped), | ||||
|             mode, | ||||
|             factor: greedyness, | ||||
|             precompute: false, | ||||
|             permute, | ||||
|             keep_first, | ||||
|             keep_last, | ||||
|             primary, | ||||
|             workers: num_workers, | ||||
|         }; | ||||
|         match route(opts) { | ||||
|             Ok(Some(route)) => { | ||||
|                 let hops = route.iter().map(|hop| { | ||||
|                     let pos = PyList::new(py, hop.pos.iter()); | ||||
|                     let elem = PyDict::new(py); | ||||
|                     elem.set_item("star_type", hop.star_type.clone()).unwrap(); | ||||
|                     elem.set_item("system", hop.system.clone()).unwrap(); | ||||
|                     elem.set_item("body", hop.body.clone()).unwrap(); | ||||
|                     elem.set_item("distance", hop.distance).unwrap(); | ||||
|                     elem.set_item("pos", pos).unwrap(); | ||||
|                     elem | ||||
|                 }); | ||||
|                 let lst = PyList::new(py, hops); | ||||
|                 Ok(lst.to_object(py)) | ||||
|             } | ||||
|             Ok(None) => Ok(py.None()), | ||||
|             Err(e) => Err(PyErr::new::<ValueError, _>(e)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| */ | ||||
|  |  | |||
							
								
								
									
										69
									
								
								rust/src/mmap_csv.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								rust/src/mmap_csv.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | |||
| use crate::common::{EdLrrError, EdLrrResult, System}; | ||||
| use crate::info; | ||||
| use csv_core::{ReadFieldResult, Reader}; | ||||
| use memmap::Mmap; | ||||
| use std::collections::HashMap; | ||||
| use std::fs::File; | ||||
| use std::path::Path; | ||||
| 
 | ||||
| pub fn mmap_csv(path: &Path, query: Vec<String>) -> Result<HashMap<String, Option<u32>>, String> { | ||||
|     let file = File::open(path).map_err(|e| e.to_string())?; | ||||
|     let mm = unsafe { Mmap::map(&file) }.map_err(|e| e.to_string())?; | ||||
|     let mut best = query | ||||
|         .iter() | ||||
|         .map(|s| (s, (s.as_bytes(), usize::MAX, u32::MAX))) | ||||
|         .collect::<Vec<(&String, (_, usize, u32))>>(); | ||||
|     let t_start = std::time::Instant::now(); | ||||
|     let dist = eddie::slice::DamerauLevenshtein::new(); | ||||
|     let mut row = 0; | ||||
|     { | ||||
|         let mut data = &mm[..]; | ||||
|         let mut rdr = Reader::new(); | ||||
|         let mut field = [0; 1024]; | ||||
|         let mut fieldidx = 0; | ||||
|         loop { | ||||
|             let (result, nread, nwrite) = rdr.read_field(data, &mut field); | ||||
|             data = &data[nread..]; | ||||
|             let field = &field[..nwrite]; | ||||
|             match result { | ||||
|                 ReadFieldResult::InputEmpty => {} | ||||
|                 ReadFieldResult::OutputFull => { | ||||
|                     return Err("Encountered field larget than 1024 bytes!".to_string()); | ||||
|                 } | ||||
|                 ReadFieldResult::Field { record_end } => { | ||||
|                     if fieldidx == 1 { | ||||
|                         for (_, (name_b, best_dist, id)) in best.iter_mut() { | ||||
|                             let d = dist.distance(name_b, field); | ||||
|                             if d < *best_dist { | ||||
|                                 *best_dist = d; | ||||
|                                 *id = row; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     if record_end { | ||||
|                         fieldidx = 0; | ||||
|                         row += 1; | ||||
|                     } else { | ||||
|                         fieldidx += 1; | ||||
|                     } | ||||
|                 } | ||||
|                 // This case happens when the CSV reader has successfully exhausted
 | ||||
|                 // all input.
 | ||||
|                 ReadFieldResult::End => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     let search_result = best | ||||
|         .drain(..) | ||||
|         .map(|(query_name, (_, _, idx))| (query_name.clone(), Some(idx))) | ||||
|         .collect::<HashMap<String, Option<u32>>>(); | ||||
|     let rate = (row as f64) / t_start.elapsed().as_secs_f64(); | ||||
|     info!( | ||||
|         "Took: {:.2?}, {:.2} systems/second", | ||||
|         t_start.elapsed(), | ||||
|         rate | ||||
|     ); | ||||
|     Ok(search_result) | ||||
| } | ||||
							
								
								
									
										15
									
								
								rust/src/profiling.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								rust/src/profiling.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| #![cfg(feature = "profiling")] | ||||
| use tracing::subscriber::set_global_default; | ||||
| pub use tracing::{debug, error, info, span, trace, warn, Level}; | ||||
| pub use tracing::{debug_span, error_span, info_span, trace_span, warn_span}; | ||||
| use tracing_chrome::ChromeLayerBuilder; | ||||
| use tracing_subscriber::layer::SubscriberExt; | ||||
| use tracing_subscriber::Registry; | ||||
| use tracing_tracy::TracyLayer; | ||||
| pub use tracy_client::ProfiledAllocator; | ||||
| 
 | ||||
| pub fn init() { | ||||
|     let (chrome_layer, _guard) = ChromeLayerBuilder::new().build(); | ||||
|     let subscriber = Registry::default().with(chrome_layer); | ||||
|     set_global_default(subscriber).expect("setting default subscriber failed"); | ||||
| } | ||||
							
								
								
									
										1916
									
								
								rust/src/route.rs
									
										
									
									
									
								
							
							
						
						
									
										1916
									
								
								rust/src/route.rs
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										56
									
								
								rust/src/search_algos/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								rust/src/search_algos/mod.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| use crate::common::TreeNode; | ||||
| use crate::route::{Router, SearchState}; | ||||
| use fnv::FnvHashMap; | ||||
| 
 | ||||
| trait SearchAlgoImpl<State = (), Weight: Ord = ()> { | ||||
|     fn get_weight(&mut self, systems: &TreeNode, router: &Router) -> Option<Weight>; | ||||
| 
 | ||||
|     fn get_neighbors( | ||||
|         &mut self, | ||||
|         system: &TreeNode, | ||||
|         router: &Router, | ||||
|         range: f32, | ||||
|     ) -> Vec<(Weight, TreeNode)> { | ||||
|         let mut ret = vec![]; | ||||
|         for nb in router.neighbours(system, range) { | ||||
|             if let Some(w) = self.get_weight(nb, router) { | ||||
|                 ret.push((w, *nb)); | ||||
|             } | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct SearchAlgo<'a> { | ||||
|     algo: Box<dyn SearchAlgoImpl>, | ||||
|     prev: FnvHashMap<u32, u32>, | ||||
|     state: Option<SearchState>, | ||||
|     router: &'a Router, | ||||
| } | ||||
| 
 | ||||
| struct BFS(usize); | ||||
| 
 | ||||
| impl SearchAlgoImpl for BFS { | ||||
|     fn get_weight(&mut self, _system: &TreeNode, _router: &Router) -> Option<()> { | ||||
|         return Some(()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> SearchAlgo<'a> { | ||||
|     fn new(router: &'a Router, algo: Box<dyn SearchAlgoImpl>) -> Self { | ||||
|         Self { | ||||
|             algo, | ||||
|             prev: FnvHashMap::default(), | ||||
|             state: None, | ||||
|             router, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn test(&mut self) { | ||||
|         // self.algo.get_neighbors
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| a = 1 - acos(dot(u/Length(u),v/Length(v)))/PI | ||||
| */ | ||||
|  | @ -1,5 +1,7 @@ | |||
| //! Ship fuel consumption and jump range calculations
 | ||||
| use crate::common::get_fsd_booster_info; | ||||
| use crate::journal::*; | ||||
| use eyre::Result; | ||||
| use pyo3::conversion::ToPyObject; | ||||
| use pyo3::prelude::*; | ||||
| use pyo3::types::PyDict; | ||||
|  | @ -10,21 +12,25 @@ use std::fs::File; | |||
| use std::io::{BufRead, BufReader}; | ||||
| use std::path::PathBuf; | ||||
| 
 | ||||
| /// Frame Shift Drive information
 | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct FSD { | ||||
|     /// Rating
 | ||||
|     pub rating_val: f32, | ||||
|     /// Class
 | ||||
|     pub class_val: f32, | ||||
|     /// Optimized Mass
 | ||||
|     pub opt_mass: f32, | ||||
|     /// Max fuel per jump
 | ||||
|     pub max_fuel: f32, | ||||
|     /// Boost factor
 | ||||
|     pub boost: f32, | ||||
|     /// Guardian booster bonus range
 | ||||
|     pub guardian_booster: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct Ship { | ||||
|     pub name: String, | ||||
|     pub ident: String, | ||||
|     pub ship_type: String, | ||||
|     pub base_mass: f32, | ||||
|     pub fuel_mass: f32, | ||||
|     pub fuel_capacity: f32, | ||||
|  | @ -33,9 +39,6 @@ pub struct Ship { | |||
| 
 | ||||
| impl Ship { | ||||
|     pub fn new( | ||||
|         name: String, | ||||
|         ident: String, | ||||
|         ship_type: String, | ||||
|         base_mass: f32, | ||||
|         fuel_mass: f32, | ||||
|         fuel_capacity: f32, | ||||
|  | @ -59,13 +62,10 @@ impl Ship { | |||
|         }; | ||||
| 
 | ||||
|         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 { | ||||
|             name, | ||||
|             ident, | ||||
|             ship_type, | ||||
|             fuel_capacity, | ||||
|             fuel_mass, | ||||
|             base_mass, | ||||
|  | @ -81,8 +81,8 @@ impl Ship { | |||
|         Ok(ret) | ||||
|     } | ||||
| 
 | ||||
|     pub fn new_from_json(data: &str) -> Result<Self, String> { | ||||
|         match serde_json::from_str::<Event>(&data) { | ||||
|     pub fn new_from_json(data: &str) -> Result<(String, Ship), String> { | ||||
|         match serde_json::from_str::<Event>(data) { | ||||
|             Ok(Event { | ||||
|                 event: EventData::Unknown, | ||||
|             }) => { | ||||
|  | @ -101,7 +101,7 @@ impl Ship { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn new_from_journal() -> Result<HashMap<String, Self>, String> { | ||||
|     pub fn new_from_journal() -> Result<HashMap<String, Ship>, String> { | ||||
|         let mut ret = HashMap::new(); | ||||
|         let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap(); | ||||
|         let mut journals: Vec<PathBuf> = Vec::new(); | ||||
|  | @ -110,14 +110,12 @@ impl Ship { | |||
|         userprofile.push("Frontier Developments"); | ||||
|         userprofile.push("Elite Dangerous"); | ||||
|         if let Ok(iter) = userprofile.read_dir() { | ||||
|             for entry in iter { | ||||
|                 if let Ok(entry) = entry { | ||||
|             for entry in iter.flatten() { | ||||
|                 if re.is_match(&entry.file_name().to_string_lossy()) { | ||||
|                     journals.push(entry.path()); | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|         } | ||||
|         journals.sort(); | ||||
| 
 | ||||
|         for journal in &journals { | ||||
|  | @ -133,16 +131,7 @@ impl Ship { | |||
|                     }) => {} | ||||
|                     Ok(ev) => { | ||||
|                         if let Some(loadout) = ev.get_loadout() { | ||||
|                             let mut ship = loadout.try_into_ship()?; | ||||
|                             if ship.name == "" { | ||||
|                                 ship.name = "<NO NAME>".to_owned(); | ||||
|                             } | ||||
|                             let key = format!( | ||||
|                                 "[{}] {} ({})", | ||||
|                                 ship.ident, | ||||
|                                 ship.name, | ||||
|                                 ship.ship_type.to_ascii_lowercase() | ||||
|                             ); | ||||
|                             let (key, ship) = loadout.try_into_ship()?; | ||||
|                             ret.insert(key, ship); | ||||
|                         } | ||||
|                     } | ||||
|  | @ -179,7 +168,7 @@ impl Ship { | |||
|         Some(cost) | ||||
|     } | ||||
| 
 | ||||
|     fn jump_range(&self, fuel: f32, booster: bool) -> f32 { | ||||
|     pub fn jump_range(&self, fuel: f32, booster: bool) -> f32 { | ||||
|         let mass = self.base_mass + fuel; | ||||
|         let mut fuel = self.fsd.max_fuel.min(fuel); | ||||
|         if booster { | ||||
|  | @ -198,6 +187,10 @@ impl Ship { | |||
|         return self.jump_range(self.fuel_mass, true); | ||||
|     } | ||||
| 
 | ||||
|     pub fn full_range(&self) -> f32 { | ||||
|         return self.jump_range(self.fuel_capacity, true); | ||||
|     } | ||||
| 
 | ||||
|     fn boost_fuel_mult(&self) -> f32 { | ||||
|         if self.fsd.guardian_booster == 0.0 { | ||||
|             return 1.0; | ||||
|  | @ -208,6 +201,21 @@ impl Ship { | |||
|         return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val); | ||||
|     } | ||||
| 
 | ||||
|     pub fn fuel_cost_for_jump(&self, fuel_mass: f32, dist: f32, boost: f32) -> Option<(f32, f32)> { | ||||
|         if dist == 0.0 { | ||||
|             return Some((0.0, 0.0)); | ||||
|         } | ||||
|         let mass = self.base_mass + fuel_mass; | ||||
|         let opt_mass = self.fsd.opt_mass * boost; | ||||
|         let base_cost = (dist * mass) / opt_mass; | ||||
|         let fuel_cost = (self.fsd.rating_val * 0.001 * base_cost.powf(self.fsd.class_val)) | ||||
|             / self.boost_fuel_mult(); | ||||
|         if fuel_cost > self.fsd.max_fuel || fuel_cost > fuel_mass { | ||||
|             return None; | ||||
|         }; | ||||
|         return Some((fuel_cost, fuel_mass - fuel_cost)); | ||||
|     } | ||||
| 
 | ||||
|     pub fn fuel_cost(&self, d: f32) -> f32 { | ||||
|         if d == 0.0 { | ||||
|             return 0.0; | ||||
|  | @ -220,29 +228,6 @@ impl Ship { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| #[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)] | ||||
| pub struct FSD { | ||||
|     pub rating_val: f32, | ||||
|     pub class_val: f32, | ||||
|     pub opt_mass: f32, | ||||
|     pub max_fuel: f32, | ||||
|     pub boost: f32, | ||||
|     pub guardian_booster: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)] | ||||
| pub struct Ship { | ||||
|     pub name: String, | ||||
|     pub ident: String, | ||||
|     pub ship_type: String, | ||||
|     pub base_mass: f32, | ||||
|     pub fuel_mass: f32, | ||||
|     pub fuel_capacity: f32, | ||||
|     pub fsd: FSD, | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| impl FSD { | ||||
|     pub fn to_object(&self, py: Python) -> PyResult<PyObject> { | ||||
|         let elem = PyDict::new(py); | ||||
|  | @ -259,9 +244,6 @@ impl FSD { | |||
| impl Ship { | ||||
|     pub fn to_object(&self, py: Python) -> PyResult<PyObject> { | ||||
|         let elem = PyDict::new(py); | ||||
|         elem.set_item("name", self.name.clone())?; | ||||
|         elem.set_item("ident", self.ident.clone())?; | ||||
|         elem.set_item("ship_type", self.ship_type.clone())?; | ||||
|         elem.set_item("base_mass", self.base_mass)?; | ||||
|         elem.set_item("fuel_mass", self.fuel_mass)?; | ||||
|         elem.set_item("fuel_capacity", self.fuel_capacity)?; | ||||
|  |  | |||
							
								
								
									
										69
									
								
								rust/tests/dot_impls.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								rust/tests/dot_impls.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | |||
| #[inline(always)] | ||||
| fn veclen(v: &[f32; 3]) -> f32 { | ||||
|     (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt() | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     let dx = p1[0] - p2[0]; | ||||
|     let dy = p1[1] - p2[1]; | ||||
|     let dz = p1[2] - p2[2]; | ||||
| 
 | ||||
|     dx * dx + dy * dy + dz * dz | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     dist2(p1, p2).sqrt() | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn distm(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     let dx = (p1[0] - p2[0]).abs(); | ||||
|     let dy = (p1[1] - p2[1]).abs(); | ||||
|     let dz = (p1[2] - p2[2]).abs(); | ||||
|     dx + dy + dz | ||||
| } | ||||
| 
 | ||||
| /// Dot product (cosine of angle) between two 3D vectors
 | ||||
| #[inline(always)] | ||||
| pub fn ndot_vec_dist(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let z: [f32; 3] = [0.0; 3]; | ||||
|     let lm = dist(u, &z) * dist(v, &z); | ||||
|     ((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm | ||||
| } | ||||
| 
 | ||||
| /// Dot product (cosine of angle) between two 3D vectors
 | ||||
| #[inline(always)] | ||||
| pub fn ndot_vec_len(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let lm = veclen(u) * veclen(v); | ||||
|     ((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| pub fn ndot_iter(u: &[f32; 3], v: &[f32; 3]) -> f32 { | ||||
|     let l_1: f32 = u.iter().map(|e| e * e).sum(); | ||||
|     let l_2: f32 = v.iter().map(|e| e * e).sum(); | ||||
|     let lm = (l_1 * l_2).sqrt(); | ||||
|     let mut ret = 0.0; | ||||
|     for (a, b) in u.iter().zip(v.iter()) { | ||||
|         ret += a * b; | ||||
|     } | ||||
|     ret / lm | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod dot_impl_tests { | ||||
|     #[test] | ||||
|     fn test_dot_impls() { | ||||
|         use super::*; | ||||
|         let v1 = [1.0, 2.0, 3.0]; | ||||
|         let v2 = [4.0, 5.0, 6.0]; | ||||
|         let d1 = ndot_vec_dist(&v1, &v2); | ||||
|         let d2 = ndot_vec_len(&v1, &v2); | ||||
|         let d3 = ndot_iter(&v1, &v2); | ||||
|         assert!((d1 - d2) < 0.01); | ||||
|         assert!((d2 - d3) < 0.01); | ||||
|         assert!((d3 - d1) < 0.01); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										170
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										170
									
								
								setup.py
									
										
									
									
									
								
							|  | @ -1,76 +1,82 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| from setuptools import find_packages, setup | ||||
| from setuptools_rust import Binding, RustExtension, Strip | ||||
| with open('README.md', 'r') as fh: | ||||
| import os | ||||
| 
 | ||||
| with open("README.md", "r") as fh: | ||||
|     long_description = fh.read() | ||||
| 
 | ||||
| extras_require = { | ||||
|     'build': ['pyinstaller', 'pywin32'], | ||||
|     'test': [ | ||||
|         'pytest', | ||||
|         'pytest-cov', | ||||
|         'pytest-dependency', | ||||
|         'pytest-benchmark[histogram]', | ||||
|         'pytest-metadata', | ||||
|         'pytest-flake8', | ||||
|         'pytest-flask', | ||||
|         'pytest-mock', | ||||
|         'pytest-flask-sqlalchemy', | ||||
|         'pytest-steps', | ||||
|         'pytest-xdist', | ||||
|         'flake8-bugbear', | ||||
|         'flake8-comprehensions', | ||||
|         'cohesion', | ||||
|         'hypothesis', | ||||
|         'flaky' | ||||
|     "build": ["pyinstaller", "pywin32"], | ||||
|     "test": [ | ||||
|         "pytest", | ||||
|         "pytest-cov", | ||||
|         "pytest-dependency", | ||||
|         "pytest-benchmark[histogram]", | ||||
|         "pytest-metadata", | ||||
|         "pytest-flake8", | ||||
|         "pytest-flask", | ||||
|         "pytest-mock", | ||||
|         "pytest-flask-sqlalchemy", | ||||
|         "pytest-steps", | ||||
|         "pytest-xdist", | ||||
|         "flake8-bugbear", | ||||
|         "flake8-comprehensions", | ||||
|         "cohesion", | ||||
|         "hypothesis", | ||||
|         "flaky", | ||||
|     ], | ||||
|     'dev': [ | ||||
|     "dev": [ | ||||
|         'black; python_version >= "3.6"', | ||||
|         'jinja2', | ||||
|         'tsp', | ||||
|         'flake8', | ||||
|         'flake8-bugbear', | ||||
|         'flake8-comprehensions', | ||||
|         'cohesion', | ||||
|         'pre-commit', | ||||
|         'ipython', | ||||
|         'flask-konch', | ||||
|         'setuptools_rust' | ||||
|         "jinja2", | ||||
|         "tsp", | ||||
|         "flake8", | ||||
|         "flake8-bugbear", | ||||
|         "flake8-comprehensions", | ||||
|         "cohesion", | ||||
|         "pre-commit", | ||||
|         "ipython", | ||||
|         "flask-konch", | ||||
|         "setuptools_rust", | ||||
|     ], | ||||
|     'gui': ['PyQt5', 'pyperclip'], | ||||
|     'web': [ | ||||
|         'flask', | ||||
|         'gevent', | ||||
|         'webargs', | ||||
|         'flask-executor', | ||||
|         'flask-wtf', | ||||
|         'flask-user', | ||||
|         'flask-debugtoolbar', | ||||
|         'flask-bootstrap4', | ||||
|         'flask-sqlalchemy', | ||||
|         'flask-nav', | ||||
|         'flask-admin', | ||||
|         'sqlalchemy_utils[password]', | ||||
|         'python-dotenv', | ||||
|     "gui": ["PyQt5", "pyperclip"], | ||||
|     "web": [ | ||||
|         "flask", | ||||
|         "gevent", | ||||
|         "webargs", | ||||
|         "flask-executor", | ||||
|         "flask-wtf", | ||||
|         "flask-user", | ||||
|         "flask-debugtoolbar", | ||||
|         "flask-bootstrap4", | ||||
|         "flask-sqlalchemy", | ||||
|         "flask-nav", | ||||
|         "flask-admin", | ||||
|         "sqlalchemy_utils[password]", | ||||
|         "python-dotenv", | ||||
|     ], | ||||
| } | ||||
| extras_require['all'] = sorted(set(sum(extras_require.values(), []))) | ||||
| extras_require["all"] = sorted(set(sum(extras_require.values(), []))) | ||||
| 
 | ||||
| # os.environ["RUSTC_WRAPPER"]='"{}" /c echo'.format(os.environ['COMSPEC']) | ||||
| 
 | ||||
| setup( | ||||
|     use_scm_version={'write_to': '__version__.py'}, | ||||
|     name='ed_lrr_gui', | ||||
|     author='Daniel Seiller', | ||||
|     author_email='earthnuker@gmail.com', | ||||
|     description='Elite: Dangerous long range route plotter', | ||||
|     use_scm_version={"write_to": "__version__.py"}, | ||||
|     name="ed_lrr_gui", | ||||
|     author="Daniel Seiller", | ||||
|     author_email="earthnuker@gmail.com", | ||||
|     description="Elite: Dangerous long range route plotter", | ||||
|     long_description=long_description, | ||||
|     long_description_content_type='text/markdown', | ||||
|     url='https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui', | ||||
|     long_description_content_type="text/markdown", | ||||
|     url="https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui", | ||||
|     rust_extensions=[ | ||||
|         RustExtension( | ||||
|             '_ed_lrr', | ||||
|             path='rust/Cargo.toml', | ||||
|             "_ed_lrr", | ||||
|             path="rust/Cargo.toml", | ||||
|             binding=Binding.PyO3, | ||||
|             strip=Strip.No, | ||||
|             rustc_flags=["--emit=asm"], | ||||
|             # features=["profiling"], | ||||
|             debug=False, | ||||
|             native=True, | ||||
|             quiet=True, | ||||
|  | @ -78,38 +84,38 @@ setup( | |||
|     ], | ||||
|     packages=find_packages(), | ||||
|     entry_points={ | ||||
|         'console_scripts': ['ed_lrr = ed_lrr_gui.__main__:main'], | ||||
|         'gui_scripts': ['ed_lrr_gui = ed_lrr_gui.__main__:gui_main'] | ||||
|         "console_scripts": ["ed_lrr = ed_lrr_gui.__main__:main"], | ||||
|         "gui_scripts": ["ed_lrr_gui = ed_lrr_gui.__main__:gui_main"], | ||||
|     }, | ||||
|     install_requires=[ | ||||
|         'appdirs', | ||||
|         'PyYAML', | ||||
|         'requests', | ||||
|         'python-dateutil', | ||||
|         'click', | ||||
|         'tqdm', | ||||
|         'click-default-group', | ||||
|         'profig', | ||||
|         'ujson', | ||||
|         'colorama', | ||||
|         'svgwrite', | ||||
|         "appdirs", | ||||
|         "PyYAML", | ||||
|         "requests", | ||||
|         "python-dateutil", | ||||
|         "click", | ||||
|         "tqdm", | ||||
|         "click-default-group", | ||||
|         "profig", | ||||
|         "ujson", | ||||
|         "colorama", | ||||
|         "svgwrite", | ||||
|         "coloredlogs", | ||||
|     ], | ||||
|     setup_requires=['setuptools', 'setuptools-rust', | ||||
|                     'setuptools-scm', 'wheel'], | ||||
|     dependency_links=['https://github.com/Nuitka/Nuitka/archive/develop.zip'], | ||||
|     setup_requires=["setuptools", "setuptools-rust", "setuptools-scm", "wheel"], | ||||
|     dependency_links=["https://github.com/Nuitka/Nuitka/archive/develop.zip"], | ||||
|     extras_require=extras_require, | ||||
|     classifiers=[ | ||||
|         'License :: OSI Approved :: MIT License', | ||||
|         'Programming Language :: Rust', | ||||
|         'Programming Language :: Python', | ||||
|         'Programming Language :: Python :: 3', | ||||
|         'Programming Language :: Python :: 3.5', | ||||
|         'Programming Language :: Python :: 3.6', | ||||
|         'Programming Language :: Python :: 3.7', | ||||
|         'Programming Language :: Python :: 3.8', | ||||
|         'Programming Language :: Python :: Implementation :: CPython', | ||||
|         'Operating System :: Windows', | ||||
|         'Operating System :: Linux', | ||||
|         "License :: OSI Approved :: MIT License", | ||||
|         "Programming Language :: Rust", | ||||
|         "Programming Language :: Python", | ||||
|         "Programming Language :: Python :: 3", | ||||
|         "Programming Language :: Python :: 3.5", | ||||
|         "Programming Language :: Python :: 3.6", | ||||
|         "Programming Language :: Python :: 3.7", | ||||
|         "Programming Language :: Python :: 3.8", | ||||
|         "Programming Language :: Python :: Implementation :: CPython", | ||||
|         "Operating System :: Windows", | ||||
|         "Operating System :: Linux", | ||||
|     ], | ||||
|     include_package_data=True, | ||||
|     zip_safe=False, | ||||
|  |  | |||
							
								
								
									
										45
									
								
								test_route.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								test_route.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| import sys | ||||
| import logging | ||||
| import coloredlogs | ||||
| from datetime import timedelta | ||||
| 
 | ||||
| coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"} | ||||
| coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"} | ||||
| 
 | ||||
| 
 | ||||
| class DeltaTimeFormatter(coloredlogs.ColoredFormatter): | ||||
|     def format(self, record): | ||||
|         seconds = record.relativeCreated / 1000 | ||||
|         duration = timedelta(seconds=seconds) | ||||
|         record.delta = str(duration) | ||||
|         return super().format(record) | ||||
| 
 | ||||
| 
 | ||||
| coloredlogs.ColoredFormatter = DeltaTimeFormatter | ||||
| logfmt = " | ".join( | ||||
|     ["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"] | ||||
| ) | ||||
| loglevel = "info" | ||||
| numeric_level = getattr(logging, loglevel.upper(), None) | ||||
| if not isinstance(numeric_level, int): | ||||
|     raise ValueError("Invalid log level: %s" % loglevel) | ||||
| coloredlogs.install(level=numeric_level, fmt=logfmt) | ||||
| sys.path.append(".") | ||||
| import _ed_lrr | ||||
| 
 | ||||
| ships = _ed_lrr.PyShip.from_journal() | ||||
| r = _ed_lrr.PyRouter(print) | ||||
| r.load("stars.csv") | ||||
| ship = max(ships.values(), key=lambda s: s.max_range) | ||||
| system_names = ["Sol", "Colonia"] | ||||
| systems = r.resolve(*system_names) | ||||
| sys_ids = {k: v["id"] for k, v in systems.items()} | ||||
| 
 | ||||
| route = r.route( | ||||
|     [sys_ids[system_names[0]], sys_ids[system_names[1]]], | ||||
|     7.84, | ||||
|     {"mode": "bfs","greedyness":0.0}, | ||||
| ) | ||||
| 
 | ||||
| for n,s in enumerate(route,1): | ||||
|     print(n,s) | ||||
							
								
								
									
										289
									
								
								tests/data/ships/base.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								tests/data/ships/base.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,289 @@ | |||
| { | ||||
|     "timestamp": "2019-09-25T21:29:51Z", | ||||
|     "event": "Loadout", | ||||
|     "Ship": "asp", | ||||
|     "ShipID": 0, | ||||
|     "ShipName": "Nightmaregreen_N", | ||||
|     "ShipIdent": "NMGR_N", | ||||
|     "HullValue": 6144793, | ||||
|     "ModulesValue": 33042643, | ||||
|     "HullHealth": 1.000000, | ||||
|     "UnladenMass": 347.200012, | ||||
|     "CargoCapacity": 0, | ||||
|     "MaxJumpRange": 56.372398, | ||||
|     "FuelCapacity": { | ||||
|         "Main": 64.000000, | ||||
|         "Reserve": 0.630000 | ||||
|     }, | ||||
|     "Rebuy": 1959374, | ||||
|     "Modules": [ | ||||
|         { | ||||
|             "Slot": "ShipCockpit", | ||||
|             "Item": "asp_cockpit", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "CargoHatch", | ||||
|             "Item": "modularcargobaydoor", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint1", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "AmmoInClip": 1, | ||||
|             "AmmoInHopper": 2, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint2", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "AmmoInClip": 1, | ||||
|             "AmmoInHopper": 2, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint3", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "AmmoInClip": 1, | ||||
|             "AmmoInHopper": 2, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint4", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "AmmoInClip": 1, | ||||
|             "AmmoInHopper": 2, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "PaintJob", | ||||
|             "Item": "paintjob_asp_operator_red", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Armour", | ||||
|             "Item": "asp_armour_grade1", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "PowerPlant", | ||||
|             "Item": "int_powerplant_size5_class2", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "MainEngines", | ||||
|             "Item": "int_engine_size4_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "FrameShiftDrive", | ||||
|             "Item": "int_hyperdrive_size5_class5", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000, | ||||
|             "Engineering": { | ||||
|                 "Engineer": "Felicity Farseer", | ||||
|                 "EngineerID": 300100, | ||||
|                 "BlueprintID": 128673694, | ||||
|                 "BlueprintName": "FSD_LongRange", | ||||
|                 "Level": 5, | ||||
|                 "Quality": 1.000000, | ||||
|                 "ExperimentalEffect": "special_fsd_heavy", | ||||
|                 "ExperimentalEffect_Localised": "Mass Manager", | ||||
|                 "Modifiers": [ | ||||
|                     { | ||||
|                         "Label": "Mass", | ||||
|                         "Value": 26.000000, | ||||
|                         "OriginalValue": 20.000000, | ||||
|                         "LessIsGood": 1 | ||||
|                     }, | ||||
|                     { | ||||
|                         "Label": "Integrity", | ||||
|                         "Value": 93.840004, | ||||
|                         "OriginalValue": 120.000000, | ||||
|                         "LessIsGood": 0 | ||||
|                     }, | ||||
|                     { | ||||
|                         "Label": "PowerDraw", | ||||
|                         "Value": 0.690000, | ||||
|                         "OriginalValue": 0.600000, | ||||
|                         "LessIsGood": 1 | ||||
|                     }, | ||||
|                     { | ||||
|                         "Label": "FSDOptimalMass", | ||||
|                         "Value": 1692.599976, | ||||
|                         "OriginalValue": 1050.000000, | ||||
|                         "LessIsGood": 0 | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "LifeSupport", | ||||
|             "Item": "int_lifesupport_size4_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "PowerDistributor", | ||||
|             "Item": "int_powerdistributor_size4_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Radar", | ||||
|             "Item": "int_sensors_size5_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "FuelTank", | ||||
|             "Item": "int_fueltank_size5_class3", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Decal1", | ||||
|             "Item": "decal_explorer_starblazer", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Decal2", | ||||
|             "Item": "decal_explorer_starblazer", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Decal3", | ||||
|             "Item": "decal_explorer_starblazer", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "ShipName0", | ||||
|             "Item": "nameplate_shipname_white", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "ShipName1", | ||||
|             "Item": "nameplate_shipname_white", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "ShipID0", | ||||
|             "Item": "nameplate_shipid_white", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "ShipID1", | ||||
|             "Item": "nameplate_shipid_white", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot01_Size6", | ||||
|             "Item": "int_fuelscoop_size6_class5", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot02_Size5", | ||||
|             "Item": "int_fueltank_size5_class3", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot03_Size3", | ||||
|             "Item": "int_repairer_size3_class5", | ||||
|             "On": false, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot04_Size3", | ||||
|             "Item": "int_shieldgenerator_size3_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot05_Size3", | ||||
|             "Item": "int_buggybay_size2_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot06_Size2", | ||||
|             "Item": "int_detailedsurfacescanner_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot07_Size2", | ||||
|             "Item": "int_dockingcomputer_standard", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot08_Size1", | ||||
|             "Item": "int_supercruiseassist", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "PlanetaryApproachSuite", | ||||
|             "Item": "int_planetapproachsuite", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "VesselVoice", | ||||
|             "Item": "voicepack_eden", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Health": 1.000000 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										193
									
								
								tests/data/ships/guardian.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								tests/data/ships/guardian.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,193 @@ | |||
| { | ||||
|     "timestamp": "2019-09-25T21:29:51Z", | ||||
|     "event": "Loadout", | ||||
|     "Ship": "asp", | ||||
|     "ShipName": "Nightmaregreen_G", | ||||
|     "ShipIdent": "NMGR_G", | ||||
|     "HullValue": 6144793, | ||||
|     "ModulesValue": 33181682, | ||||
|     "UnladenMass": 348.500061, | ||||
|     "CargoCapacity": 0, | ||||
|     "MaxJumpRange": 60.164637, | ||||
|     "FuelCapacity": { | ||||
|         "Main": 64, | ||||
|         "Reserve": 0.63 | ||||
|     }, | ||||
|     "Rebuy": 1966323, | ||||
|     "Modules": [ | ||||
|         { | ||||
|             "Slot": "CargoHatch", | ||||
|             "Item": "modularcargobaydoor", | ||||
|             "On": true, | ||||
|             "Priority": 0 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint1", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 3071 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint2", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 3071 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint3", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 3071 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "TinyHardpoint4", | ||||
|             "Item": "hpt_heatsinklauncher_turret_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 3071 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Armour", | ||||
|             "Item": "asp_armour_grade1", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Value": 0 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "PowerPlant", | ||||
|             "Item": "int_powerplant_size5_class2", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Value": 140523 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "MainEngines", | ||||
|             "Item": "int_engine_size4_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 52325 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "FrameShiftDrive", | ||||
|             "Item": "int_hyperdrive_size5_class5", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 4478716, | ||||
|             "Engineering": { | ||||
|                 "BlueprintName": "FSD_LongRange", | ||||
|                 "Level": 5, | ||||
|                 "Quality": 1, | ||||
|                 "ExperimentalEffect": "special_fsd_heavy", | ||||
|                 "Modifiers": [ | ||||
|                     { | ||||
|                         "Label": "Mass", | ||||
|                         "Value": 26.000061, | ||||
|                         "OriginalValue": 20 | ||||
|                     }, | ||||
|                     { | ||||
|                         "Label": "Integrity", | ||||
|                         "Value": 93.839832, | ||||
|                         "OriginalValue": 120 | ||||
|                     }, | ||||
|                     { | ||||
|                         "Label": "PowerDraw", | ||||
|                         "Value": 0.690001, | ||||
|                         "OriginalValue": 0.6 | ||||
|                     }, | ||||
|                     { | ||||
|                         "Label": "FSDOptimalMass", | ||||
|                         "Value": 1692.58667, | ||||
|                         "OriginalValue": 1050 | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "LifeSupport", | ||||
|             "Item": "int_lifesupport_size4_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 24895 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "PowerDistributor", | ||||
|             "Item": "int_powerdistributor_size4_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 24895 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Radar", | ||||
|             "Item": "int_sensors_size5_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 69709 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "FuelTank", | ||||
|             "Item": "int_fueltank_size5_class3", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Value": 85776 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot01_Size6", | ||||
|             "Item": "int_fuelscoop_size6_class5", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 25240068 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot02_Size5", | ||||
|             "Item": "int_fueltank_size5_class3", | ||||
|             "On": true, | ||||
|             "Priority": 1, | ||||
|             "Value": 85776 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot03_Size3", | ||||
|             "Item": "int_repairer_size3_class5", | ||||
|             "On": false, | ||||
|             "Priority": 0, | ||||
|             "Value": 2302911 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot04_Size3", | ||||
|             "Item": "int_shieldgenerator_size3_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 16506 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot05_Size3", | ||||
|             "Item": "int_buggybay_size2_class2", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 18954 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot06_Size2", | ||||
|             "Item": "int_detailedsurfacescanner_tiny", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 219375 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot07_Size2", | ||||
|             "Item": "int_dockingcomputer_standard", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 3949 | ||||
|         }, | ||||
|         { | ||||
|             "Slot": "Slot08_Size1", | ||||
|             "Item": "int_guardianfsdbooster_size1", | ||||
|             "On": true, | ||||
|             "Priority": 0, | ||||
|             "Value": 405020 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
|  | @ -79,18 +79,18 @@ class Test_PyRouter(object):  # noqa: H601 | |||
|             err = "Failed to resolve {}".format(name) | ||||
|             assert name in resolved_systems, err | ||||
| 
 | ||||
|     @pytest.mark.dependency(depends=["Test_ED_LRR::test_load_and_resolve"]) | ||||
|     @pytest.mark.dependency(depends=["Test_PyRouter::test_load_and_resolve"]) | ||||
|     @flaky(max_runs=10, min_passes=5) | ||||
|     @pytest.mark.parametrize( | ||||
|         "greedyness", greedyness, ids=lambda v: "greedyness:{}".format(v) | ||||
|     ) | ||||
|     @flaky(max_runs=10, min_passes=5) | ||||
|     def test_zero_range_fails(self, py_router, greedyness): | ||||
|         r, resolved_systems = py_router | ||||
|         waypoints = random.sample(list(resolved_systems.values()), k=2) | ||||
|         err = pytest.raises(RuntimeError, r.route, waypoints, 0, greedyness) | ||||
|         err.match(r"No route from .* to .* found!") | ||||
| 
 | ||||
|     @pytest.mark.dependency(depends=["Test_ED_LRR::test_load_and_resolve"]) | ||||
|     @pytest.mark.dependency(depends=["Test_PyRouter::test_load_and_resolve"]) | ||||
|     @flaky(max_runs=10, min_passes=2) | ||||
|     @pytest.mark.parametrize("workers", n_workers, ids=idf("workers")) | ||||
|     @pytest.mark.parametrize("jump_range", ranges, ids=idf("range")) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue