diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1189a81..25f841e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,7 +1,7 @@ name: Docs on: - # Runs on pushes targeting the default branch + # Runs on pushes targeting the default branch and pull requests. push: branches: [main] pull_request: diff --git a/docs/getting_started/naive_value_iteration.ipynb b/docs/getting_started/naive_value_iteration.ipynb index d6ce1d3..a9c5a42 100644 --- a/docs/getting_started/naive_value_iteration.ipynb +++ b/docs/getting_started/naive_value_iteration.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 119, "id": "7f3692d1-ad64-449d-809c-01ceb84e6bb4", "metadata": { "execution": { @@ -25,7 +25,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6c0870de174f4ed69adc6b138d5373f2", + "model_id": "552f68f4f9b24fe0bd2c2c493c314a8a", "version_major": 2, "version_minor": 0 }, @@ -665,12 +665,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c2b9fda9e1354d6ca57827715ca601ed", + "model_id": "e8dbbb357f754f9986e1beec03e884a0", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Output()" + "HBox(children=(Output(), Output()))" ] }, "metadata": {}, @@ -702,7 +702,7 @@ "\n", "from stormvogel.show import show\n", "from stormvogel.layout import Layout\n", - "vis = show(lion, show_editor=False, separate_labels=[\"dead...\", \"hunt >:D\", \"desparate hunt!\"], layout=Layout(\"layouts/lion.json\"))" + "vis = show(lion, show_editor=False, layout=Layout(\"layouts/lion.json\"))" ] }, { diff --git a/notebooks/die.ipynb b/notebooks/die.ipynb index 1be5a02..d9c9650 100644 --- a/notebooks/die.ipynb +++ b/notebooks/die.ipynb @@ -15,33 +15,21 @@ "import stormvogel.model\n", "import stormvogel.visualization\n", "import stormvogel.show\n", - "from stormvogel.layout import Layout, DEFAULT" + "from stormvogel.layout import Layout, DEFAULT\n", + "import stormvogel.communication_server\n", + "stormvogel.communication_server.enable_server = False" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "0ec3541e-3907-4cf8-bc0d-29ca09d919b6", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1a427e88a494479dbd209855fcf00a64", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "84e69956b02641e1a36e33dca667abef", + "model_id": "e474efd2c4d741d7bb54ae89c3fbc78c", "version_major": 2, "version_minor": 0 }, diff --git a/notebooks/layouts/NAME.jso b/notebooks/layouts/NAME.jso new file mode 100644 index 0000000..15edb91 --- /dev/null +++ b/notebooks/layouts/NAME.jso @@ -0,0 +1,114 @@ +{ + "__fake_macros": { + "__group_macro": { + "borderWidth": 1, + "color": { + "background": "white", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "ellipse", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + } + }, + "groups": { + "states": { + "borderWidth": 1, + "color": { + "background": "white", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "ellipse", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + }, + "actions": { + "borderWidth": 1, + "color": { + "background": "lightblue", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "box", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + }, + "scheduled_actions": { + "borderWidth": 1, + "color": { + "background": "pink", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "box", + "mass": 1, + "font": { + "color": "black", + "size": 14 + }, + "schedColor": false + } + }, + "reload_button": false, + "edges": { + "arrows": "to", + "font": { + "color": "black", + "size": 14 + }, + "color": { + "color": "black" + } + }, + "numbers": { + "fractions": true, + "digits": 5 + }, + "results_and_rewards": { + "show_results": true, + "resultSymbol": "\u2606", + "show_rewards": true + }, + "layout": { + "randomSeed": 5 + }, + "misc": { + "enable_physics": true, + "width": 800, + "height": 600, + "explore": false + }, + "saving": { + "relative_path": true, + "filename": "layouts/NAME.jso", + "save_button": false, + "load_button": false + }, + "positions": {}, + "width": 800, + "height": 600, + "physics": true +} diff --git a/stormvogel/layout.py b/stormvogel/layout.py index 27f67fe..ed71a30 100644 --- a/stormvogel/layout.py +++ b/stormvogel/layout.py @@ -49,7 +49,7 @@ def load(self, path: str | None = None, path_relative: bool = True): schema_str = f.read() self.schema = json.loads(schema_str) - def set_groups(self, groups: list[str]): + def set_groups(self, groups: set[str]): """Add the specified groups to the layout and schema. They will use the specified __group_macro. Note that the changes to schema won't be saved to schema.json.""" @@ -67,13 +67,15 @@ def set_groups(self, groups: list[str]): } def save(self, path: str, path_relative: bool = True) -> None: - """Save this layout as a json file. + """Save this layout as a json file. Raises runtime error if a filename does not end in json, and OSError if file not found. Args: path (str): Path to your layout file. path_relative (bool, optional): If set to true, then stormvogel will create a custom layout file relative to the current working directory. Defaults to True. """ + if path[-5:] != ".json": + raise RuntimeError("File name should end in .json") if path_relative: complete_path = os.path.join(os.getcwd(), path) else: diff --git a/stormvogel/layout_editor.py b/stormvogel/layout_editor.py index 0394832..b57237b 100644 --- a/stormvogel/layout_editor.py +++ b/stormvogel/layout_editor.py @@ -39,11 +39,11 @@ def copy_settings(self): self.layout.layout["height"] = self.layout.layout["misc"]["height"] def __failed_positions_save(self): - return f"""Could not save the node positions of this graph in {self.layout.layout['saving']['filename']}.json + return f"""Could not save the node positions of this graph in {self.layout.layout['saving']['filename']} Sorry for the inconvenience. Here are some possible fixes. 1) Restart the kernel and re-run. 2) Is the port {stormvogel.communication_server.localhost_address}:{stormvogel.communication_server.server_port} (from the machine where jupyterlab runs) available? -If you are working remotely, it might help to forward this port. For example: 'ssh -N -L {stormvogel.communication_server.server_port}:{stormvogel.communication_server.localhost_address}{stormvogel.communication_server.server_port} YOUR_SSH_CONFIG_NAME'. +If you are working remotely, it might help to forward this port. For example: 'ssh -N -L {stormvogel.communication_server.server_port}:{stormvogel.communication_server.localhost_address}:{stormvogel.communication_server.server_port} YOUR_SSH_CONFIG_NAME'. 3) You might also want to consider changing stormvogel.communication_server.localhost_address to the IPv6 loopback address if you are using IPv6. If you cannot get the server to work, set stormvogel.communication_server.enable_server to false and re-run. This will speed up stormvogel and ignore this message, but it means that you cannot store positions in layout files. @@ -61,11 +61,11 @@ def process_save_button(self): if stormvogel.communication_server.server is None: with self.debug_output: logging.info( - "Did not save node positions because the server is disabled." + "Node positions won't be saved because the server is disabled." ) with self.output: print( - f"Did not save the node positions of this graph in {self.layout.layout['saving']['filename']}.json because the server is disabled." + "Node positions won't be saved because the server is disabled." ) else: try: @@ -80,11 +80,19 @@ def process_save_button(self): ) with self.output: print(self.__failed_positions_save()) - - self.layout.save( - self.layout.layout["saving"]["filename"], - path_relative=self.layout.layout["saving"]["relative_path"], - ) + try: + self.layout.save( + self.layout.layout["saving"]["filename"], + path_relative=self.layout.layout["saving"]["relative_path"], + ) + except RuntimeError: + with self.output: + print("Filename should end in .json") + except OSError: + with self.output: + print( + f'Bad or inaccessible path or filename: {self.layout.layout["saving"]["filename"]}' + ) def process_load_button(self): if self.layout.layout["saving"]["load_button"]: diff --git a/stormvogel/visualization.py b/stormvogel/visualization.py index ed1a6b1..1203452 100644 --- a/stormvogel/visualization.py +++ b/stormvogel/visualization.py @@ -66,9 +66,11 @@ def __init__( self.model: stormvogel.model.Model = model self.result: stormvogel.result.Result = result self.layout: stormvogel.layout.Layout = layout - self.separate_labels = list(map(und, separate_labels)) + self.separate_labels: set[str] = set(map(und, separate_labels)).union( + self.layout.layout["groups"].keys() + ) self.nt: stormvogel.visjs.Network | None = None - self.do_init_server = do_init_server + self.do_init_server: bool = do_init_server def show(self) -> None: """(Re-)load the Network and display if self.do_display is True."""