From fa3bd849ff5a688268cb8ea4bd0c379b465e7110 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 5 Oct 2026 00:33:30 -0400 Subject: [PATCH 01/10] Reorganize method reference docs by task instead of by class DGraph/UGraph and DVertex/UVertex share almost their entire interface via _BaseGraph/BaseVertex, so documenting each subclass with :inherited-members: duplicated the full method list on every page. Split the common interface into task-based pages instead (construction, adding/removing vertices and edges, analysis, representations, visualization, path planning), each pulling specific members via automethod/autoproperty to avoid duplicate object-description warnings. Subclass pages now cover only what's actually different (DGraph/UGraph have no public members of their own; DVertex/ UVertex override only connect()). Also merges edge.rst and the vertex-specifics pages under a combined "Edges & Vertices" heading, and removes PGraph.rst/modules.rst, orphaned sphinx-quickstart/apidoc leftovers that were never wired into the toctree and were the source of most duplicate-object warnings in a full build. Co-Authored-By: Claude Sonnet 5 --- docs/source/PGraph.rst | 7 ----- docs/source/analysis.rst | 30 +++++++++++++++++++ docs/source/common.rst | 19 ++++++++++++ docs/source/construction.rst | 18 +++++++++++ docs/source/dgraph.rst | 7 +++-- docs/source/directed.rst | 9 ------ docs/source/dvertex.rst | 7 +++-- .../{undirected.rst => edges_vertices.rst} | 6 ++-- docs/source/index.rst | 10 +++---- docs/source/matrices.rst | 7 ----- docs/source/modification.rst | 18 +++++++++++ docs/source/modules.rst | 7 ----- docs/source/planning.rst | 5 ++-- docs/source/representations.rst | 14 +++++++++ docs/source/ugraph.rst | 7 +++-- docs/source/uvertex.rst | 7 +++-- docs/source/visualization.rst | 12 ++++++++ 17 files changed, 138 insertions(+), 52 deletions(-) delete mode 100644 docs/source/PGraph.rst create mode 100644 docs/source/analysis.rst create mode 100644 docs/source/common.rst create mode 100644 docs/source/construction.rst delete mode 100644 docs/source/directed.rst rename docs/source/{undirected.rst => edges_vertices.rst} (62%) delete mode 100644 docs/source/matrices.rst create mode 100644 docs/source/modification.rst delete mode 100644 docs/source/modules.rst create mode 100644 docs/source/representations.rst create mode 100644 docs/source/visualization.rst diff --git a/docs/source/PGraph.rst b/docs/source/PGraph.rst deleted file mode 100644 index 23c88ad6..00000000 --- a/docs/source/PGraph.rst +++ /dev/null @@ -1,7 +0,0 @@ -PGraph module -============= - -.. automodule:: pgraph.PGraph - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/analysis.rst b/docs/source/analysis.rst new file mode 100644 index 00000000..04bff3d9 --- /dev/null +++ b/docs/source/analysis.rst @@ -0,0 +1,30 @@ +Graph analysis +=============== + +Querying structure -- counts, neighbours, connectivity, distance -- without +converting the graph into another representation. See :doc:`planning` for +finding paths between vertices, and :doc:`representations` for matrix/dot +forms of the whole graph. + +.. autoproperty:: pgraph.PGraph._BaseGraph.n +.. autoproperty:: pgraph.PGraph._BaseGraph.ne +.. autoproperty:: pgraph.PGraph._BaseGraph.nc +.. automethod:: pgraph.PGraph._BaseGraph.edges +.. automethod:: pgraph.PGraph._BaseGraph.average_degree +.. automethod:: pgraph.PGraph._BaseGraph.iscyclic +.. automethod:: pgraph.PGraph._BaseGraph.connectivity +.. automethod:: pgraph.PGraph._BaseGraph.component +.. automethod:: pgraph.PGraph._BaseGraph.samecomponent +.. automethod:: pgraph.PGraph._BaseGraph.closest + +.. automethod:: pgraph.PGraph.BaseVertex.neighbours +.. automethod:: pgraph.PGraph.BaseVertex.neighbors +.. automethod:: pgraph.PGraph.BaseVertex.adjacent +.. automethod:: pgraph.PGraph.BaseVertex.isneighbour +.. automethod:: pgraph.PGraph.BaseVertex.incidences +.. autoproperty:: pgraph.PGraph.BaseVertex.degree +.. automethod:: pgraph.PGraph.BaseVertex.heuristic_distance +.. automethod:: pgraph.PGraph.BaseVertex.distance +.. autoproperty:: pgraph.PGraph.BaseVertex.x +.. autoproperty:: pgraph.PGraph.BaseVertex.y +.. autoproperty:: pgraph.PGraph.BaseVertex.z diff --git a/docs/source/common.rst b/docs/source/common.rst new file mode 100644 index 00000000..ec0f36c2 --- /dev/null +++ b/docs/source/common.rst @@ -0,0 +1,19 @@ +Common methods +=============== + +:class:`DGraph`/:class:`UGraph` and :class:`DVertex`/:class:`UVertex` share +almost their entire interface, defined once on their common base classes +(:class:`_BaseGraph`, :class:`BaseVertex`). This section documents that +shared interface by task rather than by class; see :doc:`dgraph`/ +:doc:`ugraph` and :doc:`edges_vertices` for what's specific to directed vs. +undirected. + +.. toctree:: + :maxdepth: 2 + + construction + modification + analysis + representations + visualization + planning diff --git a/docs/source/construction.rst b/docs/source/construction.rst new file mode 100644 index 00000000..b9fa4d4f --- /dev/null +++ b/docs/source/construction.rst @@ -0,0 +1,18 @@ +Construction and basic usage +============================= + +How to build a :class:`DGraph`/:class:`UGraph` (a plain constructor, or from +a dictionary/adjacency matrix), copy one, configure its distance metric, and +the basic object protocol (``str``/``repr``, indexing, iteration, +membership). + +See :doc:`policy` for the rules around constructing vertices/edges +standalone, independent of a graph. + +.. autoclass:: pgraph.PGraph._BaseGraph + :members: Dict, Adjacency, copy, vertex_copy, metric, heuristic, show + :special-members: __init__, __str__, __repr__, __getitem__, __iter__, __contains__ + +.. autoclass:: pgraph.PGraph.BaseVertex + :members: copy + :special-members: __init__, __str__, __repr__ diff --git a/docs/source/dgraph.rst b/docs/source/dgraph.rst index 32a56832..9c317404 100644 --- a/docs/source/dgraph.rst +++ b/docs/source/dgraph.rst @@ -1,9 +1,10 @@ Directed graph -------------- +``DGraph`` adds no public methods of its own -- see :doc:`common` for the +full method list (construction, adding/removing vertices and edges, +analysis, representations, visualization, path planning). This page covers +only what's specific to directed graphs. .. autoclass:: pgraph.PGraph.DGraph - :undoc-members: :show-inheritance: - :inherited-members: - :special-members: __init__ \ No newline at end of file diff --git a/docs/source/directed.rst b/docs/source/directed.rst deleted file mode 100644 index f61bd161..00000000 --- a/docs/source/directed.rst +++ /dev/null @@ -1,9 +0,0 @@ -Directed graphs -=============== - - -.. toctree:: - :maxdepth: 2 - - dgraph - dvertex diff --git a/docs/source/dvertex.rst b/docs/source/dvertex.rst index 7100e7dc..732da277 100644 --- a/docs/source/dvertex.rst +++ b/docs/source/dvertex.rst @@ -1,8 +1,9 @@ Directed graph vertex --------------------- +``DVertex`` overrides only ``connect()`` -- see :doc:`common` for the full +method list (neighbours, edges, distance, degree, removal, ...). + .. autoclass:: pgraph.PGraph.DVertex - :undoc-members: + :members: connect :show-inheritance: - :inherited-members: - :special-members: __init__ \ No newline at end of file diff --git a/docs/source/undirected.rst b/docs/source/edges_vertices.rst similarity index 62% rename from docs/source/undirected.rst rename to docs/source/edges_vertices.rst index 75ee9487..80202a12 100644 --- a/docs/source/undirected.rst +++ b/docs/source/edges_vertices.rst @@ -1,9 +1,9 @@ -Undirected graphs +Edges & Vertices ================= - .. toctree:: :maxdepth: 2 - ugraph + dvertex uvertex + edge diff --git a/docs/source/index.rst b/docs/source/index.rst index 2efbb675..69c18a86 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,11 +12,11 @@ This package provides a set of classes for manipulating simple directed and undi .. toctree:: :maxdepth: 2 - directed - undirected - edge - planning - matrices + common + dgraph + ugraph + edges_vertices + policy Indices and tables diff --git a/docs/source/matrices.rst b/docs/source/matrices.rst deleted file mode 100644 index d323b756..00000000 --- a/docs/source/matrices.rst +++ /dev/null @@ -1,7 +0,0 @@ -Convert graph to matrix form -============================ - -These methods convert a graph to a matrix representation . - -.. autoclass:: pgraph.PGraph._BaseGraph - :members: distance, adjacency, degree, incidence, laplacian diff --git a/docs/source/modification.rst b/docs/source/modification.rst new file mode 100644 index 00000000..bdb80a88 --- /dev/null +++ b/docs/source/modification.rst @@ -0,0 +1,18 @@ +Adding and removing vertices and edges +======================================== + +Building up and tearing down a graph's structure. ``connect()`` is shown +here in its base-class form; :doc:`DVertex ` and +:doc:`UVertex ` override it with different edge-list bookkeeping +-- see those pages for the concrete behaviour. See :class:`Edge` for +constructing/removing an edge directly rather than via a vertex or graph. + +.. automethod:: pgraph.PGraph._BaseGraph.add_vertex +.. automethod:: pgraph.PGraph._BaseGraph.add_edge +.. automethod:: pgraph.PGraph._BaseGraph.remove_edge +.. automethod:: pgraph.PGraph._BaseGraph.remove_vertex +.. automethod:: pgraph.PGraph._BaseGraph.remove + +.. automethod:: pgraph.PGraph.BaseVertex.connect +.. automethod:: pgraph.PGraph.BaseVertex.edgeto +.. automethod:: pgraph.PGraph.BaseVertex.remove diff --git a/docs/source/modules.rst b/docs/source/modules.rst deleted file mode 100644 index 7de9dadc..00000000 --- a/docs/source/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -pgraph -====== - -.. toctree:: - :maxdepth: 4 - - PGraph diff --git a/docs/source/planning.rst b/docs/source/planning.rst index 76dd3326..dc0eade8 100644 --- a/docs/source/planning.rst +++ b/docs/source/planning.rst @@ -3,5 +3,6 @@ Path planning These methods find optimal paths from a start vertex to a goal vertex. -.. autoclass:: pgraph.PGraph._BaseGraph - :members: path_BFS, path_UCS, path_Astar +.. automethod:: pgraph.PGraph._BaseGraph.path_BFS +.. automethod:: pgraph.PGraph._BaseGraph.path_UCS +.. automethod:: pgraph.PGraph._BaseGraph.path_Astar diff --git a/docs/source/representations.rst b/docs/source/representations.rst new file mode 100644 index 00000000..defb9eab --- /dev/null +++ b/docs/source/representations.rst @@ -0,0 +1,14 @@ +Transformations to other representations +========================================== + +These methods convert the graph to another representation entirely: a +matrix, or a `GraphViz `_ dot file. See +:doc:`visualization` for rendering the graph itself rather than exporting +it. + +.. automethod:: pgraph.PGraph._BaseGraph.distance +.. automethod:: pgraph.PGraph._BaseGraph.adjacency +.. automethod:: pgraph.PGraph._BaseGraph.degree +.. automethod:: pgraph.PGraph._BaseGraph.incidence +.. automethod:: pgraph.PGraph._BaseGraph.Laplacian +.. automethod:: pgraph.PGraph._BaseGraph.dotfile diff --git a/docs/source/ugraph.rst b/docs/source/ugraph.rst index 0ba41fc4..43e65b03 100644 --- a/docs/source/ugraph.rst +++ b/docs/source/ugraph.rst @@ -1,9 +1,10 @@ Undirected graph ---------------- +``UGraph`` adds no public methods of its own -- see :doc:`common` for the +full method list (construction, adding/removing vertices and edges, +analysis, representations, visualization, path planning). This page covers +only what's specific to undirected graphs. .. autoclass:: pgraph.PGraph.UGraph - :undoc-members: :show-inheritance: - :inherited-members: - :special-members: __init__ \ No newline at end of file diff --git a/docs/source/uvertex.rst b/docs/source/uvertex.rst index 1f0d9ee8..92564eba 100644 --- a/docs/source/uvertex.rst +++ b/docs/source/uvertex.rst @@ -1,8 +1,9 @@ Undirected graph vertex ----------------------- +``UVertex`` overrides only ``connect()`` -- see :doc:`common` for the full +method list (neighbours, edges, distance, degree, removal, ...). + .. autoclass:: pgraph.PGraph.UVertex - :undoc-members: + :members: connect :show-inheritance: - :inherited-members: - :special-members: __init__ \ No newline at end of file diff --git a/docs/source/visualization.rst b/docs/source/visualization.rst new file mode 100644 index 00000000..b492d93c --- /dev/null +++ b/docs/source/visualization.rst @@ -0,0 +1,12 @@ +Visualization +============== + +Rendering the graph itself, with matplotlib or via GraphViz. See +:doc:`representations` for exporting the graph as data (a matrix or dot +file) rather than displaying it. + +.. automethod:: pgraph.PGraph._BaseGraph.plot +.. automethod:: pgraph.PGraph._BaseGraph.showgraph +.. automethod:: pgraph.PGraph._BaseGraph.highlight_path +.. automethod:: pgraph.PGraph._BaseGraph.highlight_edge +.. automethod:: pgraph.PGraph._BaseGraph.highlight_vertex From ea36a4f33da987387e96b77c2694703baff7e6df Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 06:33:37 +1000 Subject: [PATCH 02/10] docs: restructure the Sphinx site into Introduction and API details - Introduction and API details are documents, so each shows in the sidebar with its sub-headings indented (navigation_depth 3, collapse_navigation off) - every method page opens with an autosummary table of its methods, with the one-line summary and a link to the entry below it - add Introduction, API details and Exporting a graph pages, with inheritance diagrams for the graph, vertex and edge classes on the common page - fold DGraph/UGraph into the construction page and remove the dgraph and ugraph pages that documented the same classes a second time - drop the display_version theme option, which sphinx_rtd_theme no longer accepts The build has no warnings and no runblock errors. Co-Authored-By: Claude Sonnet 5.5 --- docs/source/analysis.rst | 48 ++++++- docs/source/api.rst | 9 ++ docs/source/common.rst | 34 ++++- docs/source/conf.py | 8 +- docs/source/construction.rst | 37 +++-- docs/source/dgraph.rst | 10 -- docs/source/edges_vertices.rst | 76 +++++++++++ docs/source/export.rst | 23 ++++ docs/source/index.rst | 15 +- docs/source/intro.rst | 235 ++++++++++++++++++++++++++++++++ docs/source/modification.rst | 24 ++++ docs/source/planning.rst | 10 ++ docs/source/representations.rst | 23 +++- docs/source/ugraph.rst | 10 -- docs/source/visualization.rst | 12 ++ 15 files changed, 519 insertions(+), 55 deletions(-) create mode 100644 docs/source/api.rst delete mode 100644 docs/source/dgraph.rst create mode 100644 docs/source/export.rst create mode 100644 docs/source/intro.rst delete mode 100644 docs/source/ugraph.rst diff --git a/docs/source/analysis.rst b/docs/source/analysis.rst index 04bff3d9..2967a120 100644 --- a/docs/source/analysis.rst +++ b/docs/source/analysis.rst @@ -1,10 +1,49 @@ Graph analysis =============== -Querying structure -- counts, neighbours, connectivity, distance -- without -converting the graph into another representation. See :doc:`planning` for -finding paths between vertices, and :doc:`representations` for matrix/dot -forms of the whole graph. +Querying structure -- counts, neighbours, connectivity, distance -- for directed and +undirected graphs. +See :doc:`planning` for +finding paths between vertices, and :doc:`representations` for matrix/dot forms of the +whole graph. + +Graph +----- + +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph._BaseGraph.n + ~pgraph.PGraph._BaseGraph.ne + ~pgraph.PGraph._BaseGraph.nc + ~pgraph.PGraph._BaseGraph.edges + ~pgraph.PGraph._BaseGraph.average_degree + ~pgraph.PGraph._BaseGraph.iscyclic + ~pgraph.PGraph._BaseGraph.connectivity + ~pgraph.PGraph._BaseGraph.component + ~pgraph.PGraph._BaseGraph.samecomponent + ~pgraph.PGraph._BaseGraph.closest + +Vertex +------ + +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph.BaseVertex.edges + ~pgraph.PGraph.BaseVertex.neighbours + ~pgraph.PGraph.BaseVertex.neighbors + ~pgraph.PGraph.BaseVertex.isneighbour + ~pgraph.PGraph.BaseVertex.incidences + ~pgraph.PGraph.BaseVertex.degree + ~pgraph.PGraph.BaseVertex.heuristic_distance + ~pgraph.PGraph.BaseVertex.distance + ~pgraph.PGraph.BaseVertex.x + ~pgraph.PGraph.BaseVertex.y + ~pgraph.PGraph.BaseVertex.z + +Details +------- .. autoproperty:: pgraph.PGraph._BaseGraph.n .. autoproperty:: pgraph.PGraph._BaseGraph.ne @@ -17,6 +56,7 @@ forms of the whole graph. .. automethod:: pgraph.PGraph._BaseGraph.samecomponent .. automethod:: pgraph.PGraph._BaseGraph.closest +.. automethod:: pgraph.PGraph.BaseVertex.edges .. automethod:: pgraph.PGraph.BaseVertex.neighbours .. automethod:: pgraph.PGraph.BaseVertex.neighbors .. automethod:: pgraph.PGraph.BaseVertex.adjacent diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 00000000..875c5819 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,9 @@ +API details +============ + +.. toctree:: + :maxdepth: 2 + + common + edges_vertices + policy diff --git a/docs/source/common.rst b/docs/source/common.rst index ec0f36c2..8944b1bd 100644 --- a/docs/source/common.rst +++ b/docs/source/common.rst @@ -1,12 +1,37 @@ Common methods =============== -:class:`DGraph`/:class:`UGraph` and :class:`DVertex`/:class:`UVertex` share +The package supports both directed (prefix ``D``) and undirected graphs (prefix ``U``), and the classes for these +share a common interface. + +The graph classes represent a single graph, either directed or undirected, and they share :class:`_BaseGraph`: + +.. inheritance-diagram:: pgraph.PGraph.UGraph pgraph.PGraph.DGraph + :parts: 1 + :private-bases: + :top-classes: pgraph.PGraph._BaseGraph + +The vertices in a directed graph (``DGraph``) inherit from :class:`DVertex` and the +vertices in an undirected graph (``UGraph``) inherit from :class:`UVertex`. + +.. inheritance-diagram:: pgraph.PGraph.UVertex pgraph.PGraph.DVertex + :parts: 1 + +The vertex classes share almost their entire interface, defined once on their common base classes (:class:`_BaseGraph`, :class:`BaseVertex`). This section documents that -shared interface by task rather than by class; see :doc:`dgraph`/ -:doc:`ugraph` and :doc:`edges_vertices` for what's specific to directed vs. -undirected. +shared interface by task rather than by class; see :doc:`construction` for +the :class:`DGraph` and :class:`UGraph` classes and :doc:`edges_vertices` for +what's specific to directed vs. undirected vertices. + +Edges are represented by the :class:`Edge` class, which is used by both directed and undirected graphs. + +.. inheritance-diagram:: pgraph.PGraph.Edge + :parts: 1 + + + +The following sections describe the common methods. .. toctree:: :maxdepth: 2 @@ -16,4 +41,5 @@ undirected. analysis representations visualization + export planning diff --git a/docs/source/conf.py b/docs/source/conf.py index 1e340a48..0f1d1680 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- -project = "Simple graph functionality for Python" +project = "Graphs for Python" copyright = "2020-present, Peter Corke" author = "Peter Corke" @@ -60,8 +60,12 @@ html_theme_options = { "logo_only": False, - "display_version": True, "prev_next_buttons_location": "both", + # show the sub-headings of every page in the sidebar, not just the current one + "collapse_navigation": False, + # Introduction / API details -> page -> sub-heading; individual methods + # are reached from the page, not listed in the sidebar + "navigation_depth": 3, "analytics_id": "G-11Q6WJM565", } diff --git a/docs/source/construction.rst b/docs/source/construction.rst index b9fa4d4f..02ff0598 100644 --- a/docs/source/construction.rst +++ b/docs/source/construction.rst @@ -1,18 +1,39 @@ Construction and basic usage ============================= -How to build a :class:`DGraph`/:class:`UGraph` (a plain constructor, or from -a dictionary/adjacency matrix), copy one, configure its distance metric, and -the basic object protocol (``str``/``repr``, indexing, iteration, -membership). +There are a number of ways to build a :class:`DGraph`/:class:`UGraph` (a plain constructor, or from +a dictionary/adjacency matrix). See :doc:`policy` for the rules around constructing vertices/edges standalone, independent of a graph. +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph.DGraph + ~pgraph.PGraph.UGraph + ~pgraph.PGraph._BaseGraph.Dict + ~pgraph.PGraph._BaseGraph.Adjacency + + +.. autoclass:: pgraph.PGraph.DGraph + :show-inheritance: + +.. autoclass:: pgraph.PGraph.UGraph + :show-inheritance: + +.. autoclass:: pgraph.PGraph._BaseGraph.Adjacency + :show-inheritance: + +.. autoclass:: pgraph.PGraph._BaseGraph.Dict + :show-inheritance: + + +Inherited methods for all graph types +-------------------------------------- + .. autoclass:: pgraph.PGraph._BaseGraph - :members: Dict, Adjacency, copy, vertex_copy, metric, heuristic, show + :members: copy, vertex_copy, metric, heuristic, show, vertex_class, edge_class :special-members: __init__, __str__, __repr__, __getitem__, __iter__, __contains__ -.. autoclass:: pgraph.PGraph.BaseVertex - :members: copy - :special-members: __init__, __str__, __repr__ + diff --git a/docs/source/dgraph.rst b/docs/source/dgraph.rst deleted file mode 100644 index 9c317404..00000000 --- a/docs/source/dgraph.rst +++ /dev/null @@ -1,10 +0,0 @@ -Directed graph --------------- - -``DGraph`` adds no public methods of its own -- see :doc:`common` for the -full method list (construction, adding/removing vertices and edges, -analysis, representations, visualization, path planning). This page covers -only what's specific to directed graphs. - -.. autoclass:: pgraph.PGraph.DGraph - :show-inheritance: diff --git a/docs/source/edges_vertices.rst b/docs/source/edges_vertices.rst index 80202a12..b72a49ef 100644 --- a/docs/source/edges_vertices.rst +++ b/docs/source/edges_vertices.rst @@ -1,6 +1,82 @@ Edges & Vertices ================= +In the earlier examples we did not explicitly create vertices or edges, but we +can do so if we want to. The graph classes provide methods to create vertices and edges, +and to add them to the graph. + + +.. runblock:: pycon + :session: + + >>> from pgraph import UGraph, UVertex, Edge + >>> import numpy as np + >>> g = UGraph() + +Next, we will explicitly create two vertices + +.. runblock:: pycon + :session: + + >>> v1 = UVertex(np.random.rand(2)) + >>> v2 = UVertex(np.random.rand(2)) + + +and then add them to the graph + +.. runblock:: pycon + :session: + + >>> g.add_vertex(v1) + >>> g.add_vertex(v2) + +Our graph now has two vertices, but no edges + +.. runblock:: pycon + :session: + + >>> print(g) + +We can explicitly create an edge between the two vertices + +.. runblock:: pycon + :session: + + >>> e = Edge(v1, v2) + +and then add it to the graph + +.. runblock:: pycon + :session: + + >>> g.add_edge(e) + >>> print(g) + +The ``Edge`` constuctor line above is equivalent to + +.. code-block:: python + + >>> e = Edge() + >>> e.connect(v1, v2) + +Alternatively, we can create a vertex and *connect* it to an existing vertex, which will implitically create an edge between the two vertices and add it to the graph. + +.. runblock:: pycon + :session: + + >>> v3 = UVertex(np.random.rand(2)) + >>> g.add_vertex(v3) + >>> v2.connect(v3) + >>> print(g) + +The ``connect`` method of the vertex class creates an edge between the two +vertices and adds it to the graph. The two vertices must have been added the same graph for the edge to be created. +The ``connect`` method is available on both the +directed and undirected vertex classes. + + +.. note:: Where ever a vertex or edge is required, a subclass of the appropriate base class can be used. For example, we can create a subclass of ``UVertex`` called ``MyVertex`` and a subclass of ``Edge`` called ``MyEdge`` and then create a new graph using these classes. + .. toctree:: :maxdepth: 2 diff --git a/docs/source/export.rst b/docs/source/export.rst new file mode 100644 index 00000000..1d86782e --- /dev/null +++ b/docs/source/export.rst @@ -0,0 +1,23 @@ +Exporting a graph +================= + +Graphs can be exported to a number of different formats, including dot (used by +`GraphViz `_) mermaid (a JavaScript-based graph +visualization tool that can be used in MarkDown documents), GraphML (an +XML-based format for representing graphs), ELK (a graph +layout engine), as well as various image formats (e.g., PNG, SVG, PDF). + +See :doc:`visualization` for rendering the graph using Matplotlib itself rather than exporting +it. + +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph._BaseGraph.export + ~pgraph.PGraph._BaseGraph.dotfile + +Details +------- + +.. automethod:: pgraph.PGraph._BaseGraph.export +.. automethod:: pgraph.PGraph._BaseGraph.dotfile diff --git a/docs/source/index.rst b/docs/source/index.rst index 69c18a86..e932f3a0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,21 +6,14 @@ Graphs for Python ================= -This package provides a set of classes for manipulating simple directed and undirected graphs in Python. - - .. toctree:: - :maxdepth: 2 - - common - dgraph - ugraph - edges_vertices - policy + :maxdepth: 3 + intro + api Indices and tables -================== +------------------ * :ref:`genindex` * :ref:`search` diff --git a/docs/source/intro.rst b/docs/source/intro.rst new file mode 100644 index 00000000..bd6565a0 --- /dev/null +++ b/docs/source/intro.rst @@ -0,0 +1,235 @@ +Introduction +================= + +This package provides classes for directed and undirected graphs: vertices +connected by edges, with embedded coordinates, edge costs and path planning. +Self-loops and parallel edges are permitted. It is a graph-theory package, not +a plotting library. + +What is a graph? +----------------- + +A graph is an abstract representation of a set of objects connected by links and +depicted visually as shown below. Graphs can be +used to represent transport, communications or social networks, and this branch of +mathematics is graph theory. + +Mathematically, a graph is denoted by +:math:`G(V, E)` where :math:`V` are the vertices or nodes, and :math:`E` are the links that connect pairs +of vertices and are called edges or arcs. Edges can be directed (shown as arrows) or +undirected (shown as line segments) as in this case. Edges can have an associated +weight or cost associated with moving from one vertex to another. A sequence of +edges from one vertex to another is a path, and a sequence that starts and ends at +the same vertex is a cycle. An edge from a vertex to itself is a loop. + +Creating a graph using the ``pgraph`` package +---------------------------------------------- + +To create a new graph using the ``pgraph`` package, we first import the package and create +a new instance of the ``UGraph`` class, which represents an undirected graph. By default, the vertices of the graph exist in 2-dimensional space. + +.. runblock:: pycon + :session: + + >>> import pgraph + >>> import numpy as np + >>> g = pgraph.UGraph() + +Next, we add some vertices to the graph, each with a random 2D coordinate which is +specified as a 2-element array. The coordinates can be used to compute the cost of +edges, and to plot the graph. + +.. runblock:: pycon + :session: + + >>> np.random.seed(0) # ensure repeatable results + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)) + + +The method ``add_vertex`` returns a reference to the newly created vertex object, but +we can obtain the reference by indexing the graph object. For example vertex 1 is + +.. runblock:: pycon + :session: + + >>> g[1] + +and is an instance of ``UVertex``, a vertex in an undirected graph; it has the coordinate +:math:`(0.6028, 0.5449)` and has been assigned a default name ``"#1"`` but we could specify +an explicit name to ``add_vertex``. +We can also reference a vertex by its name + +.. runblock:: pycon + :session: + + >>> g["#1"] + +A summary of the graph can be displayed + +.. runblock:: pycon + :session: + + >>> print(g) + +and we see that it has 5 vertices, no edges and 5 components, that is, each vertex is isolated. + +Next, we add edges to connect specific pairs of vertices + + .. runblock:: pycon + :session: + + >>> g.add_edge(g[0], g[1]) + >>> g.add_edge(g[0], g[2]) + >>> g.add_edge(g[0], g[3]) + >>> g.add_edge(g[1], g[2]) + >>> g.add_edge(g[1], g[3]) + >>> g.add_edge(g[3], g[4]) + + +The ``add_edge`` method returns a reference to the edge object just created. By +default, the edge cost is the Euclidean distance between the vertices, but this can +be overridden by a third argument to ``add_edge``. + +A summary of the graph can be displayed + + .. runblock:: pycon + :session: + + >>> print(g) + +and we see that it has 5 vertices, six edges and one component, that is, all the vertices are connected into one net- +work. + +The graph can be plotted by + +.. code-block:: python + + >>> g.plot() + +.. plot:: + + >>> import pgraph + >>> import numpy as np + >>> g = pgraph.UGraph() + >>> np.random.seed(0) # ensure repeatable results + >>> for i in range(5): + >>> g.add_vertex(np.random.rand(2)) + >>> + >>> g.add_edge(g[0], g[1]) + >>> g.add_edge(g[0], g[2]) + >>> g.add_edge(g[0], g[3]) + >>> g.add_edge(g[1], g[2]) + >>> g.add_edge(g[1], g[3]) + >>> g.add_edge(g[3], g[4]) + >>> g.plot() + +The vertices are shown as blue circles, and the edges are +shown as lines joining vertices. Many options exist to change default plotting be- +havior. Note that only graphs embedded in 2- and 3-dimensional space can be +plotted. + +The neighbours of vertex 1 are + + .. runblock:: pycon + :session: + + >>> g[1].adjacent() + + +which is a list of neighboring vertex objects, those connected to it by an edge. Each +edge is an object and the edges connecting to vertex 1 are + + .. runblock:: pycon + :session: + + >>> g[1].edges() + +and each edge has references to the vertices it connects + + .. runblock:: pycon + :session: + + >>> g[1].edges()[0].endpoints + + +The cost or length of an edge is + + .. runblock:: pycon + :session: + + >>> g[1].edges()[0].cost + +The vertex closest to the coordinate :math:`(0.5, 0.5)` is + + .. runblock:: pycon + :session: + + >>> g.closest((0.5, 0.5)) + +The minimum-distance path between any two vertices in the graph can be computed using well-known algorithms such as A (Hart et al. 1968) + + .. runblock:: pycon + :session: + + >>> path, length, _ = g.path_Astar(g[2], g[4]) + >>> path + + +which is a list of vertices representing a total path length of + + .. runblock:: pycon + :session: + + >>> length + +Graphs can also be represented in other ways, such as adjacency, incidence, degree and Laplacian matrices, and +methods exist to convert the graph to these. + +Arbitrary data can be attached to any vertex or edge by assigning values to attributes + + .. runblock:: pycon + :session: + + >>> g[1].data = "this is vertex 1" + >>> g[1].edges()[0].data = "this is the edge from vertex 0 to vertex 1" + +This exploits the flexible nature of Python, but care must be taken to avoid name clashes with existing attributes. + +A more robust and Pythonic approach is to create a subclass of the ``UVertex`` or ``DVertex`` classes class and +declare them at graph creation time. For example, we can create a subclass of ``UVertex`` called ``MyVertex`` and a subclass of ``Edge`` called ``MyEdge`` and then create a new graph using these classes. + +.. runblock:: pycon + :session: + + >>> import pgraph + >>> import numpy as np + >>> class MyVertex(pgraph.UVertex): + ... pass + >>> class MyEdge(pgraph.Edge): + ... pass + >>> g = pgraph.UGraph(vertex_class=MyVertex, edge_class=MyEdge) + >>> np.random.seed(0) # ensure repeatable results + >>> for i in range(5): + ... v = g.add_vertex(np.random.rand(2)) + >>> g.add_edge(g[0], g[1]) + >>> g.add_edge(g[0], g[2]) + >>> g.add_edge(g[0], g[3]) + >>> g.add_edge(g[1], g[2]) + >>> g.add_edge(g[1], g[3]) + >>> g.add_edge(g[3], g[4]) + >>> print(g) + +we can see that the type of the vertices and edges is now the user-defined classes + +.. runblock:: pycon + :session: + + >>> type(g[0]) + >>> type(g[0].edges()[0]) + +History +-------- + +``pgaph-python`` is based on ``PGraph.m`` which is part of the +earlier `Spatial Math Toolbox for MATLAB `_. diff --git a/docs/source/modification.rst b/docs/source/modification.rst index bdb80a88..1dd07a92 100644 --- a/docs/source/modification.rst +++ b/docs/source/modification.rst @@ -7,6 +7,30 @@ here in its base-class form; :doc:`DVertex ` and -- see those pages for the concrete behaviour. See :class:`Edge` for constructing/removing an edge directly rather than via a vertex or graph. +Graph +----- + +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph._BaseGraph.add_vertex + ~pgraph.PGraph._BaseGraph.add_edge + ~pgraph.PGraph._BaseGraph.remove_edge + ~pgraph.PGraph._BaseGraph.remove_vertex + +Vertex +------ + +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph.BaseVertex.connect + ~pgraph.PGraph.BaseVertex.edgeto + ~pgraph.PGraph.BaseVertex.remove + +Details +------- + .. automethod:: pgraph.PGraph._BaseGraph.add_vertex .. automethod:: pgraph.PGraph._BaseGraph.add_edge .. automethod:: pgraph.PGraph._BaseGraph.remove_edge diff --git a/docs/source/planning.rst b/docs/source/planning.rst index dc0eade8..3dbb0dd8 100644 --- a/docs/source/planning.rst +++ b/docs/source/planning.rst @@ -3,6 +3,16 @@ Path planning These methods find optimal paths from a start vertex to a goal vertex. +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph._BaseGraph.path_BFS + ~pgraph.PGraph._BaseGraph.path_UCS + ~pgraph.PGraph._BaseGraph.path_Astar + +Details +------- + .. automethod:: pgraph.PGraph._BaseGraph.path_BFS .. automethod:: pgraph.PGraph._BaseGraph.path_UCS .. automethod:: pgraph.PGraph._BaseGraph.path_Astar diff --git a/docs/source/representations.rst b/docs/source/representations.rst index defb9eab..a08db7db 100644 --- a/docs/source/representations.rst +++ b/docs/source/representations.rst @@ -1,14 +1,25 @@ -Transformations to other representations +Transformations to matrix representations ========================================== -These methods convert the graph to another representation entirely: a -matrix, or a `GraphViz `_ dot file. See -:doc:`visualization` for rendering the graph itself rather than exporting -it. +These methods convert the graph to a number of different matrix representations. + + +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph._BaseGraph.distance + ~pgraph.PGraph._BaseGraph.adjacency + ~pgraph.PGraph._BaseGraph.degree + ~pgraph.PGraph._BaseGraph.incidence + ~pgraph.PGraph._BaseGraph.Laplacian + +Details +------- .. automethod:: pgraph.PGraph._BaseGraph.distance .. automethod:: pgraph.PGraph._BaseGraph.adjacency .. automethod:: pgraph.PGraph._BaseGraph.degree .. automethod:: pgraph.PGraph._BaseGraph.incidence .. automethod:: pgraph.PGraph._BaseGraph.Laplacian -.. automethod:: pgraph.PGraph._BaseGraph.dotfile + + diff --git a/docs/source/ugraph.rst b/docs/source/ugraph.rst deleted file mode 100644 index 43e65b03..00000000 --- a/docs/source/ugraph.rst +++ /dev/null @@ -1,10 +0,0 @@ -Undirected graph ----------------- - -``UGraph`` adds no public methods of its own -- see :doc:`common` for the -full method list (construction, adding/removing vertices and edges, -analysis, representations, visualization, path planning). This page covers -only what's specific to undirected graphs. - -.. autoclass:: pgraph.PGraph.UGraph - :show-inheritance: diff --git a/docs/source/visualization.rst b/docs/source/visualization.rst index b492d93c..f9421981 100644 --- a/docs/source/visualization.rst +++ b/docs/source/visualization.rst @@ -5,6 +5,18 @@ Rendering the graph itself, with matplotlib or via GraphViz. See :doc:`representations` for exporting the graph as data (a matrix or dot file) rather than displaying it. +.. autosummary:: + :nosignatures: + + ~pgraph.PGraph._BaseGraph.plot + ~pgraph.PGraph._BaseGraph.showgraph + ~pgraph.PGraph._BaseGraph.highlight_path + ~pgraph.PGraph._BaseGraph.highlight_edge + ~pgraph.PGraph._BaseGraph.highlight_vertex + +Details +------- + .. automethod:: pgraph.PGraph._BaseGraph.plot .. automethod:: pgraph.PGraph._BaseGraph.showgraph .. automethod:: pgraph.PGraph._BaseGraph.highlight_path From c5df34bc16ff3a7cd2527736405f4d3b49b2d77a Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 06:33:37 +1000 Subject: [PATCH 03/10] docs: docstring pass over PGraph.py Docstrings only (the code is unchanged apart from the docstrings, checked by comparing the AST with docstrings removed). - document the behaviour with self-loops and parallel edges, and the exceptions each method raises - cross-reference the related methods: component/samecomponent/nc and the degree family (degree, connectivity, average_degree, BaseVertex.degree) - connectivity() and BaseVertex.edges() now say which edges they return for each graph kind, and that an undirected self-loop counts twice - vertex_class/edge_class properties say they are set by the constructor - examples that show a figure use block=None, so they no longer emit the non-interactive-backend warning that runblock reports as an error Co-Authored-By: Claude Sonnet 5.5 --- src/pgraph/PGraph.py | 441 +++++++++++++++++++++++++------------------ 1 file changed, 258 insertions(+), 183 deletions(-) diff --git a/src/pgraph/PGraph.py b/src/pgraph/PGraph.py index 2edbaac7..46898fa7 100644 --- a/src/pgraph/PGraph.py +++ b/src/pgraph/PGraph.py @@ -198,7 +198,7 @@ def Dict(cls, d: dict, reverse: bool = False, **kwargs: Any) -> BaseGraph: Behaves like a constructor for a ``DGraph`` or ``UGraph`` from a dictionary that maps vertices to parents. From this information it - can create a tree graph. + can create a graph that is a tree or forest. By default parent vertices are linked their children. If ``reverse`` is True then children are linked to their parents. @@ -268,13 +268,14 @@ def Adjacency( :return: graph :rtype: UGraph or DGraph - Create a directed or undirected graph where non-zero elements ``A[i,j]`` + Behaves like a constructor for a ``DGraph`` or ``UGraph`` creating the graph from an adjacency matrix ``A``. + Here non-zero elements ``A[i,j]`` correspond to edges from vertex ``i`` to vertex ``j``. A non-zero diagonal element ``A[i,i]`` is a self-loop on vertex ``i``, for both kinds of graph. - .. warning:: For undirected graph ``A`` should be symmetric but this + .. warning:: For an undirected graph ``A`` should be symmetric but this is not checked. Only the upper triangular part, including the diagonal, is used. Elements are costs, not edge counts: a non-zero element is a single edge with that element as its cost, so @@ -332,6 +333,18 @@ def vertex_class(self) -> type[BaseVertex]: :return: vertex class :rtype: subclass of ``UVertex`` or ``DVertex`` + This is read-only. It is set by the ``vertex_class`` argument of the + graph constructor, see :meth:`__init__`, and is ``UVertex`` for a + :class:`UGraph` and ``DVertex`` for a :class:`DGraph` if not given. + + .. runblock:: pycon + + >>> from pgraph import UGraph, UVertex + >>> class MyVertex(UVertex): + ... pass + >>> g = UGraph(vertex_class=MyVertex) + >>> print(g.vertex_class.__name__) + :seealso: :meth:`add_vertex` :attr:`edge_class` """ return self._vertex_class @@ -344,6 +357,18 @@ def edge_class(self) -> type[Edge]: :return: edge class :rtype: subclass of ``Edge`` + This is read-only. It is set by the ``edge_class`` argument of the + graph constructor, see :meth:`__init__`, and is :class:`Edge` if not + given. + + .. runblock:: pycon + + >>> from pgraph import UGraph, Edge + >>> class MyEdge(Edge): + ... pass + >>> g = UGraph(edge_class=MyEdge) + >>> print(g.edge_class.__name__) + :seealso: :meth:`add_edge` :attr:`vertex_class` """ return self._edge_class @@ -567,6 +592,10 @@ def copy(self) -> BaseGraph: ``RecursionError`` for a graph with a path of about a hundred vertices. + Copies the graph, its vertices and edges, but not any external data that may be + attached to them. The copy is a new graph of the same kind, with the same + vertices and edges, but the vertices and edges are new objects. + .. runblock:: pycon >>> from pgraph import UGraph @@ -688,6 +717,8 @@ def add_vertex( :return: the added vertex :rtype: BaseVertex subclass + This is a graph-centric way of creating a vertex. + - ``g.add_vertex()`` creates a new vertex with optional ``coord`` and ``name``. - ``g.add_vertex(v)`` takes an instance of this graph's @@ -708,21 +739,19 @@ def add_vertex( in the graph but unreachable by name. A default name could also clash with an existing one after a vertex had been removed. - The vertex is placed into a dictionary with a key equal to its name. - - This single implementation, shared by :class:`UGraph` and - :class:`DGraph`, is parameterized by the graph's :attr:`vertex_class` - rather than duplicated per subclass -- see :doc:`policy` for why that - matters. + The vertex is placed into the graph's vertex dictionary with a key equal to its name. .. runblock:: pycon >>> from pgraph import UGraph, UVertex >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0]) + >>> repr(v1) + >>> print(v1) >>> print(v1.name) >>> v2 = g.add_vertex(UVertex(coord=[1,1], name='v2')) >>> print(v2.name) + >>> g['v2'] If the graph was constructed with a required ``dim`` (see :meth:`BaseGraph.__init__`), every embedded vertex must have a @@ -736,6 +765,11 @@ def add_vertex( >>> v1 = g.add_vertex(coord=[0, 0, 0, 0, 0, 0], name='pose1') >>> print(v1) + .. note:: This single implementation, shared by :class:`UGraph` and + :class:`DGraph`, is parameterized by the graph's :attr:`vertex_class` + rather than duplicated per subclass -- see :doc:`policy` for why that + matters. + :seealso: :meth:`vertex_copy` """ if isinstance(coord, self._vertex_class): @@ -881,31 +915,32 @@ def add_edge( """ Add an edge to the graph (base class method) - :param v1: first vertex (start if a directed graph), or an existing - edge that already carries its two vertices + :param v1: first vertex (start if a directed graph), or an existing edge that + already carries its two vertices :type v1: BaseVertex subclass, str or Edge - :param v2: second vertex (end if a directed graph), must be omitted - if ``v1`` is an edge + :param v2: second vertex (end if a directed graph), must be omitted if ``v1`` is + an edge :type v2: BaseVertex subclass or str, optional - :param kwargs: optional arguments to pass to ``BaseVertex.connect``, - not allowed if ``v1`` is an edge - :raises TypeError: ``v1`` or ``v2`` is of the wrong type, or ``v2``, - ``cost`` or ``data`` is given along with an edge - :raises ValueError: the edge has no vertices, or the vertices do not - belong to this graph + :param kwargs: optional arguments to pass to ``BaseVertex.connect``, not allowed + if ``v1`` is an edge + :raises TypeError: ``v1`` or ``v2`` is of the wrong type, or ``v2``, ``cost`` or + ``data`` is given along with an edge + :raises ValueError: the edge has no vertices, or the vertices do not belong to + this graph :return: edge :rtype: Edge - - ``g.add_edge(v1, v2)`` creates an edge between a vertex pair and - adds it to the graph. - - ``g.add_edge(e)`` takes an edge, or an instance of an ``Edge`` - subclass, that was constructed with its two vertices and adds it to - the graph, just as ``g.add_vertex(v)`` takes an existing vertex. + This is a graph-centric way of creating an edge. The alternative is the + ``connect`` method of a vertex. - This is a graph centric way of creating an edge. The - alternative is the ``connect`` method of a vertex. + - ``g.add_edge(v1, v2)`` creates an edge between a vertex pair and adds it to + the graph. + - ``g.add_edge(e)`` takes an edge, or an instance of an ``Edge`` subclass, that + was constructed with its two vertices and adds it to the graph, just as + ``g.add_vertex(v)`` takes an existing vertex. .. runblock:: pycon + :linenos: >>> from pgraph import UGraph >>> g = UGraph() @@ -917,9 +952,12 @@ def add_edge( >>> e2 = g.add_edge('v2', 'v3', cost=99) >>> print(e2) + Note that at line 9 the vertices are specified by their string names. + Adding an instance of a user-defined ``Edge`` subclass: .. runblock:: pycon + :linenos: >>> from pgraph import UGraph, Edge >>> class MyEdge(Edge): @@ -930,6 +968,8 @@ def add_edge( >>> e = g.add_edge(MyEdge(v1, v2, data="a road")) >>> print(type(e).__name__, e.data) + .. note:: Vertices have coordinates and the edge ``cost`` defaults to the distance between them, but if the vertices have no coordinates the edge cost must be set explicitly, otherwise it will be ``None`` and path planning will fail with a ``ValueError``. See :meth:`_require_cost` for details. + .. note:: To supply an edge that does not yet have vertices use ``g.add_edge(v1, v2, edge=e)`` or ``e.connect(v1, v2)``. @@ -978,10 +1018,6 @@ def remove_edge(self, edge: Edge) -> None: .. warning:: The connectivity of the network may be changed. - .. note:: An undirected edge is held by both of its vertices and a - directed edge only by its source (see :meth:`BaseVertex.edges`). - Each vertex kind knows which, so this method does not. - .. runblock:: pycon >>> from pgraph import UGraph @@ -992,6 +1028,13 @@ def remove_edge(self, edge: Edge) -> None: >>> g.remove_edge(e) >>> print(g) + .. note:: A directed edge is tracked only by its source vertex's + edge list, not its target's (see :attr:`BaseVertex.edges`), so + membership is checked per endpoint rather than assumed for both + -- removing a directed edge from an undirected-only + implementation would otherwise raise ``ValueError`` on the + target side. + :seealso: :meth:`remove_vertex` :meth:`Edge.remove` """ if edge not in self._edges: @@ -1024,12 +1067,6 @@ def remove_vertex(self, vertex: BaseVertex) -> None: .. warning:: The connectivity of the network may be changed. - .. note:: This scans the graph's own edge set for edges touching - ``vertex``, rather than iterating ``vertex.edges()`` -- - for a ``DGraph`` vertex that only ever reports outgoing edges - (see :attr:`BaseVertex.edges`), so incoming edges would - otherwise be missed and left dangling. - .. runblock:: pycon >>> from pgraph import UGraph @@ -1040,6 +1077,12 @@ def remove_vertex(self, vertex: BaseVertex) -> None: >>> g.remove_vertex(v2) >>> print(g) + .. note:: This scans the graph's own edge set for edges touching + ``vertex``, rather than iterating ``vertex.edges()`` -- + for a ``DGraph`` vertex that only ever reports outgoing edges + (see :attr:`BaseVertex.edges`), so incoming edges would + otherwise be missed and left dangling. + :seealso: :meth:`remove_edge` :meth:`BaseVertex.remove` """ if vertex._graph is not self: @@ -1159,16 +1202,7 @@ def nc(self) -> int: :return: Number of components :rtype: int - .. note:: - - - Components are labeled from 0 to ``g.nc-1``. - - A graph coloring algorithm is run if the graph connectivity - has changed. - - .. note:: A lazy approach is used, and if a connectivity changing - operation has been performed since the last call, the graph - coloring algorithm is run which is potentially expensive for - a large graph. + Number of components in the graph. Components are labeled from 0 to ``g.nc-1``. .. runblock:: pycon @@ -1179,6 +1213,13 @@ def nc(self) -> int: >>> v3 = g.add_vertex(name='v3') >>> g.add_edge(v1, v2) >>> print(g.nc) + + .. note:: A lazy approach is used, and if a connectivity changing + operation has been performed since the last call, the graph + coloring algorithm is run which is potentially expensive for + a large graph. + + :seealso: :meth:`component` :meth:`samecomponent` """ n = self._graphcolor() if n is not None: @@ -1460,7 +1501,7 @@ def closest(self, coord: ArrayLike) -> tuple[BaseVertex | None, float]: def edges(self) -> list[Edge]: """ - Get all edges in graph (base class method) + Return a set of all edges in graph (base class method) :return: All edges in the graph :rtype: list of Edge references @@ -1513,7 +1554,7 @@ def plot( ax: Any = None, ) -> None: """ - Plot the graph + Plot graph as a vertex/edge diagram using Matplotlib :param vopt: vertex format, defaults to 12pt o-marker :type vopt: dict, optional @@ -1533,10 +1574,17 @@ def plot( :raises ValueError: the graph is empty, or a vertex has no coordinate or fewer than two coordinate values - The graph is plotted using matplotlib. + The graph is plotted using Matplotlib according to the dimensionality of the vertex + coordinates (set by the `dim` parameter to graph constructor). If the dimensionality is + + - 2, then a 2D plot is generated. + - 3, then a 3D plot is generated. + - 4 or more, then a 3D plot is generated using the first three coordinates. - If ``colorcomponents`` is True then each component is assigned a unique - color. ``vertex`` and ``edge`` cannot include a color keyword. + If a vertex has no coordinate or fewer than two values, a ValueError is raised. + + If ``colorcomponents`` is True then all edges and vertices within a component are assigned a unique + color. If ``text`` is a dict it is used to format text labels for the vertices which are the vertex names. If ``text`` is None or True default @@ -1549,20 +1597,26 @@ def plot( .. runblock:: pycon >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) >>> g = UGraph() - >>> v1 = g.add_vertex(coord=[0,0], name='v1') - >>> v2 = g.add_vertex(coord=[1,1], name='v2') - >>> g.add_edge(v1, v2) + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... g.add_edge(g[i], g[j]); >>> g.plot(block=None) .. plot:: - from pgraph import UGraph - g = UGraph() - v1 = g.add_vertex(coord=[0,0], name='v1') - v2 = g.add_vertex(coord=[1,1], name='v2') - g.add_edge(v1, v2) - g.plot(block=None) + >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) + >>> g = UGraph() + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... g.add_edge(g[i], g[j]); + >>> g.plot(block=None) :seealso: :meth:`highlight_path` """ @@ -1671,26 +1725,30 @@ def highlight_path( .. runblock:: pycon >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) >>> g = UGraph() - >>> v1 = g.add_vertex(coord=[0,0], name='v1') - >>> v2 = g.add_vertex(coord=[1,1], name='v2') - >>> v3 = g.add_vertex(coord=[2,2], name='v3') - >>> g.add_edge(v1, v2) - >>> g.add_edge(v2, v3) + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... g.add_edge(g[i], g[j]); >>> g.plot(block=None) - >>> g.highlight_path([v1, v2, v3], block=None) + >>> path, length, _ = g.path_Astar(g[2], g[4]) + >>> g.highlight_path(path, block=None) .. plot:: - from pgraph import UGraph - g = UGraph() - v1 = g.add_vertex(coord=[0,0], name='v1') - v2 = g.add_vertex(coord=[1,1], name='v2') - v3 = g.add_vertex(coord=[2,2], name='v3') - g.add_edge(v1, v2) - g.add_edge(v2, v3) - g.plot(block=None) - g.highlight_path([v1, v2, v3], block=None) + >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) + >>> g = UGraph() + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... g.add_edge(g[i], g[j]); + >>> g.plot(block=None) + >>> path, length, _ = g.path_Astar(g[2], g[4]) + >>> g.highlight_path(path, block=None) :seealso: :meth:`highlight_vertex` :meth:`highlight_edge` """ @@ -1706,11 +1764,11 @@ def highlight_edge( self, edge: Edge, scale: float = 2, color: str = "r", alpha: float = 0.5 ) -> None: """ - Highlight an edge in the graph + Highlight an edge in a plot of the graph :param edge: The edge to highlight :type edge: Edge subclass - :param scale: Overwrite with a line this much bigger than the original, + :param scale: Overwrite with a line that is thicker than the original, defaults to 1.5 :type scale: float, optional :param color: Overwrite with a line in this color, defaults to 'r' @@ -1724,26 +1782,33 @@ def highlight_edge( .. runblock:: pycon >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) + >>> g = UGraph() - >>> v1 = g.add_vertex(coord=[0,0], name='v1') - >>> v2 = g.add_vertex(coord=[1,1], name='v2') - >>> e = g.add_edge(v1, v2) + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> edges = [] + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... edges.append(g.add_edge(g[i], g[j])); >>> g.plot(block=None) - >>> g.highlight_edge(e) - - The plot produced looks like: + >>> g.highlight_edge(edges[2]) .. plot:: - from pgraph import UGraph - g = UGraph() - v1 = g.add_vertex(coord=[0,0], name='v1') - v2 = g.add_vertex(coord=[1,1], name='v2') - e = g.add_edge(v1, v2) - g.plot(block=None) - g.highlight_edge(e) + >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) + >>> g = UGraph() + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> edges = [] + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... edges.append(g.add_edge(g[i], g[j])); + >>> g.plot(block=None) + >>> g.highlight_edge(edges[2]) - :seealso: :meth:`highlight_vertex` :meth:`highlight_path` + :seealso: :meth:`plot` :meth:`highlight_vertex` :meth:`highlight_path` """ p1 = edge.v1 p2 = edge.v2 @@ -1765,7 +1830,7 @@ def highlight_vertex( :param vertex: The vertex (or vertices, or vertex names) to highlight :type vertex: BaseVertex subclass, or iterable of BaseVertex subclass or str - :param scale: Overwrite with a line this much bigger than the original, + :param scale: Overwrite with a marker that is larger than the original, defaults to 1.5 :type scale: float, optional :param color: Overwrite with a line in this color, defaults to 'r' @@ -1777,22 +1842,28 @@ def highlight_vertex( .. runblock:: pycon >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) >>> g = UGraph() - >>> v1 = g.add_vertex(coord=[0,0], name='v1') - >>> v2 = g.add_vertex(coord=[1,1], name='v2') - >>> g.add_edge(v1, v2) + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... g.add_edge(g[i], g[j]); >>> g.plot(block=None) - >>> g.highlight_vertex(v1) + >>> g.highlight_vertex(g[2]) .. plot:: - from pgraph import UGraph - g = UGraph() - v1 = g.add_vertex(coord=[0,0], name='v1') - v2 = g.add_vertex(coord=[1,1], name='v2') - g.add_edge(v1, v2) - g.plot(block=None) - g.highlight_vertex(v1) + >>> from pgraph import UGraph + >>> import numpy as np + >>> np.random.seed(0) + >>> g = UGraph() + >>> for i in range(5): + ... g.add_vertex(np.random.rand(2)); + >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: + ... g.add_edge(g[i], g[j]); + >>> g.plot(block=None) + >>> g.highlight_vertex(g[2]) """ if isinstance(vertex, Iterable): @@ -1812,7 +1883,7 @@ def export( **options: Any, ) -> str | None: """ - Export the graph as text or as a rendered image + Convert and save the graph in a graph export or image format :param filename: destination path or open file, defaults to None :type filename: str, path-like or file object, optional @@ -1950,13 +2021,10 @@ def dotfile( - The graph is undirected if it is a subclass of ``UGraph`` - The graph is directed if it is a subclass of ``DGraph`` - The ``graphviz`` formatters ``dot`` and ``neato`` can be used to render the - graph in various formats including PDF, PNG, JPG, GIF, SVG, etc. The formmatters are - similar, but ``dot`` is better suited for directed graphs while ``neato`` is better - suited for undirected graphs. - - .. note:: If ``filename`` is a file object then the file will *not* - be closed after the GraphViz model is written. + .. note:: The ``graphviz`` formatters ``dot`` and ``neato`` can be used to render the + graph in various formats including PDF, PNG, JPG, GIF, SVG, etc. The formmatters are + similar, but ``dot`` is better suited for directed graphs while ``neato`` is better + suited for undirected graphs. .. runblock:: pycon @@ -1967,6 +2035,12 @@ def dotfile( >>> g.add_edge(v1, v2) >>> g.dotfile() + .. note:: Edge costs are not included in the output. Use :meth:`export` with + ``costs=True`` to include them. + + .. note:: If ``filename`` is a file object then the file will *not* + be closed after the GraphViz model is written. Safe for use with ``sys.stdout`` or ``sys.stderr``. + :seealso: :meth:`export` :meth:`showgraph` """ from . import exporters @@ -1982,7 +2056,7 @@ def dotfile( def showgraph(self, display: bool = True, **options: Any) -> str | None: """ - Display graph in a browser tab + Display graph as a vertex/edge diagram in a browser tab :param display: open the rendered PDF in a browser tab, defaults to True :param options: ``engine``, ``direction``, ``colorcomponents`` and @@ -1996,6 +2070,8 @@ def showgraph(self, display: bool = True, **options: Any) -> str | None: ``display=False`` the PDF is rendered but not opened and its path is returned, which is useful for tests and notebooks. + .. note:: Vertices do not have to have coordinates for this to work, but if they do the coordinates are used to position the vertices in the output. If no coordinates are given, GraphViz will choose a layout. + :seealso: :meth:`export` :meth:`dotfile` """ pdf = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) @@ -2026,18 +2102,6 @@ def iscyclic(self) -> bool: vertices' outgoing edges via :meth:`BaseVertex.adjacent` the same way :meth:`path_BFS` and friends do. - .. note:: A self-loop is a cycle of length one, so it makes either kind - of graph cyclic. Two parallel edges between the same pair of - vertices are a cycle in an undirected graph, but a repeated - arrow in a directed graph is not. - - .. note:: A matrix-based test also exists in theory -- a digraph is - acyclic iff its adjacency matrix is nilpotent (all eigenvalues - zero) -- but that needs an O(N^3) eigendecomposition plus a - floating-point zero-tolerance judgement call, to answer a - question Kahn's algorithm answers exactly with integers in - O(V+E). Not used here for that reason. - .. runblock:: pycon >>> from pgraph import UGraph @@ -2051,6 +2115,18 @@ def iscyclic(self) -> bool: >>> g.add_edge(v3, v1) >>> print(g.iscyclic()) + .. note:: A self-loop is a cycle of length one, so it makes either kind + of graph cyclic. Two parallel edges between the same pair of + vertices are a cycle in an undirected graph, but a repeated + arrow in a directed graph is not. + + .. note:: A matrix-based test also exists in theory -- a digraph is + acyclic iff its adjacency matrix is nilpotent (all eigenvalues + zero) -- but that needs an O(N^3) eigendecomposition plus a + floating-point zero-tolerance judgement call, to answer a + question Kahn's algorithm answers exactly with integers in + O(V+E). Not used here for that reason. + :seealso: :meth:`adjacency` """ if isinstance(self, UGraph): @@ -2099,11 +2175,11 @@ def average_degree(self) -> float: >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') - >>> g.add_edge(v1, v2) - >>> g.add_edge(v2, v3) + >>> g.add_edge(v1, v2); + >>> g.add_edge(v2, v3); >>> print(g.average_degree()) - :seealso: :meth:`degree` + :seealso: :meth:`degree` :meth:`connectivity` :attr:`BaseVertex.degree` """ return 2 * self.ne / self.n @@ -2113,7 +2189,7 @@ def average_degree(self) -> float: def laplacian(self) -> NDArray: """ - Laplacian matrix for the graph + Convert the graph to a Laplacian matrix :return: Laplacian matrix :rtype: NumPy ndarray @@ -2127,11 +2203,9 @@ def laplacian(self) -> NDArray: >>> import numpy as np >>> g = UGraph() >>> for i in range(5): - ... g.add_vertex(np.random.rand(2)) - ... + ... g.add_vertex(np.random.rand(2)); >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: - ... g.add_edge(g[i], g[j]) - ... + ... g.add_edge(g[i], g[j]); >>> L = g.laplacian() >>> print(L) @@ -2145,12 +2219,11 @@ def laplacian(self) -> NDArray: zero-valued eigenvalues equals the number of connected components. - For a **directed** graph, this computes the out-degree - Laplacian (a real, recognized construction, e.g. in directed + Laplacian (used in directed consensus/synchronization literature) -- but it is generally *not* symmetric, its eigenvalues can be complex, and the zero-eigenvalue/component-count relationship above does not - hold. For example a simple weakly-connected out-tree (one - component) already has two zero eigenvalues, not one. + hold. .. rubric:: For graphs with parallel edges or loops @@ -2219,11 +2292,9 @@ def connectivity(self, vertices: Iterable[BaseVertex] | None = None) -> list[int >>> import numpy as np >>> g = UGraph() >>> for i in range(5): - ... g.add_vertex(np.random.rand(2)) - ... + ... g.add_vertex(np.random.rand(2)); >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: - ... g.add_edge(g[i], g[j]) - ... + ... g.add_edge(g[i], g[j]); >>> c = g.connectivity() >>> print(c) @@ -2231,7 +2302,7 @@ def connectivity(self, vertices: Iterable[BaseVertex] | None = None) -> list[int .. note:: This is the diagonal of :meth:`degree`. Parallel edges are counted individually. - :seealso: :meth:`degree` + :seealso: :meth:`degree` :meth:`average_degree` :attr:`BaseVertex.degree` """ c = [] @@ -2243,7 +2314,7 @@ def connectivity(self, vertices: Iterable[BaseVertex] | None = None) -> list[int def degree(self) -> NDArray: """ - Degree matrix of graph + Convert graph to a degree matrix :return: degree matrix :rtype: ndarray(N,N) @@ -2265,11 +2336,9 @@ def degree(self) -> NDArray: >>> import numpy as np >>> g = UGraph() >>> for i in range(5): - ... g.add_vertex(np.random.rand(2)) - ... + ... g.add_vertex(np.random.rand(2)); >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: - ... g.add_edge(g[i], g[j]) - ... + ... g.add_edge(g[i], g[j]); >>> d = g.degree() >>> print(d) @@ -2287,7 +2356,7 @@ def degree(self) -> NDArray: def adjacency(self) -> NDArray: """ - Adjacency matrix of graph + Convert graph to an adjacency matrix :returns: adjacency matrix :rtype: ndarray(N,N) @@ -2301,11 +2370,9 @@ def adjacency(self) -> NDArray: >>> import numpy as np >>> g = UGraph() >>> for i in range(5): - ... g.add_vertex(np.random.rand(2)) - ... + ... g.add_vertex(np.random.rand(2)); >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: - ... g.add_edge(g[i], g[j]) - ... + ... g.add_edge(g[i], g[j]); >>> A = g.adjacency() >>> print(A) @@ -2316,7 +2383,7 @@ def adjacency(self) -> NDArray: - for an undirected graph the matrix is symmetric - Eigenvalues of ``A`` are real and are known as the spectrum of the graph. - The element ``A[i,j]`` is the number of walks of length one - edge from vertex ``i`` to vertex ``j``, which is the number of + from vertex ``i`` to vertex ``j``, which is the number of edges between them (zero or one for a simple graph). - If ``Ak = A ** k`` the element ``Ak[i,j]`` is the number of walks of length ``k`` from vertex ``i`` to vertex ``j``. @@ -2344,7 +2411,7 @@ def adjacency(self) -> NDArray: def incidence(self) -> NDArray: """ - Incidence matrix of graph + Convert graph to an incidence matrix :returns: incidence matrix :rtype: ndarray(n,ne) @@ -2358,18 +2425,16 @@ def incidence(self) -> NDArray: >>> import numpy as np >>> g = UGraph() >>> for i in range(5): - ... g.add_vertex(np.random.rand(2)) - ... + ... g.add_vertex(np.random.rand(2)); >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: - ... g.add_edge(g[i], g[j]) - ... + ... g.add_edge(g[i], g[j]); >>> I = g.incidence() >>> print(I) .. note:: - - vertices are numbered in their order of creation. A vertex index + - Vertices are numbered in their order of creation. A vertex index can be resolved to a vertex reference by ``graph[i]``. - - edges are numbered in the order they appear in :meth:`edges`, which + - Edges are numbered in the order they appear in :meth:`edges`, which is the order they were added. - Both endpoints of every edge are marked, regardless of direction -- for a ``DGraph`` this means a vertex that is only @@ -2405,7 +2470,7 @@ def incidence(self) -> NDArray: def distance(self) -> NDArray: """ - Distance matrix of graph + Convert graph to a distance matrix :return: distance matrix :rtype: ndarray(n,n) @@ -2420,11 +2485,9 @@ def distance(self) -> NDArray: >>> import numpy as np >>> g = UGraph() >>> for i in range(5): - ... g.add_vertex(np.random.rand(2)) - ... + ... g.add_vertex(np.random.rand(2)); >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: - ... g.add_edge(g[i], g[j]) - ... + ... g.add_edge(g[i], g[j]); >>> d = g.distance() >>> print(d) @@ -2468,10 +2531,11 @@ def component(self, c: int) -> list[BaseVertex]: >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') - >>> g.add_edge(v1, v2) + >>> g.add_edge(v1, v2); + >>> print(g) >>> print(g.component(0)) - :seealso: :meth:`nc` :meth:`samecomponent` + :seealso: :attr:`nc` :meth:`samecomponent` """ self._graphcolor() # ensure labels are uptodate return [v for v in self if v.label == c] @@ -2503,7 +2567,7 @@ def samecomponent(self, v1: BaseVertex, v2: BaseVertex) -> bool: >>> print(g.samecomponent(v1, v2)) >>> print(g.samecomponent(v1, v3)) - :seealso: :meth:`component` + :seealso: :meth:`component` :attr:`nc` """ self._graphcolor() # ensure labels are uptodate @@ -2530,7 +2594,12 @@ def path_BFS( None if there is no path :rtype: (list of BaseVertex subclass, float) or None - Returns a list of vertices that form a path from vertex ``S`` to + Compute a path from vertex ``S`` to vertex ``G`` using breadth-first search. The + path is guaranteed to be the shortest in terms of number of edges, but not + necessarily the cheapest in terms of edge costs. This search is not suited + to graphs with edge weights, and is inefficient in that it explores all vertices. + + Returns a list of vertices that starts with vertex ``S`` and ends with vertex ``G`` if possible, otherwise returns None -- so check the result before unpacking it. @@ -2634,7 +2703,10 @@ def path_UCS( search tree, or None if there is no path :rtype: (list of BaseVertex subclass, float, dict) or None - Returns a list of vertices that form a path from vertex ``S`` to + Compute the lowest-cost path from vertex ``S`` to vertex ``G`` using uniform cost search. The + path is guaranteed to be the shortest in terms of traversed edge cost, but is inefficient in that it explores all vertices. + + Returns a list of vertices that starts with vertex ``S`` and ends with vertex ``G`` if possible, otherwise returns None -- so check the result before unpacking it. @@ -2756,7 +2828,11 @@ def path_Astar( search tree, or None if there is no path :rtype: (list of BaseVertex subclass, float, dict) or None - Returns a list of vertices that form a path from vertex ``S`` to + Compute the lowest-cost path from vertex ``S`` to vertex ``G`` using A*t search. The + path is guaranteed to be the shortest in terms of traversed edge cost, and the search is efficient in + terms of the number of vertices explored. + + Returns a list of vertices that starts with vertex ``S`` and ends with vertex ``G`` if possible, otherwise returns None -- so check the result before unpacking it. @@ -3676,7 +3752,7 @@ def connect( vertices must agree with them: in either order if undirected, exactly ``self`` then ``dest`` if directed. - Both vertices must already have been added to the same graph, - e.g. via :meth:`PGraph.add_vertex` -- since a graph only ever + e.g. via :meth:`_BaseGraph.add_vertex` -- since a graph only ever accepts its own vertex subclass (see :meth:`UGraph.add_vertex`, :meth:`DGraph.add_vertex`), this also rules out connecting a ``UVertex`` to a ``DVertex``. @@ -3825,21 +3901,20 @@ def edgeto(self, dest: BaseVertex) -> Edge: def edges(self) -> list[Edge]: """ - All outgoing edges of vertex - - :return: List of all edges leaving this vertex - :rtype: list of Edge + Return a list of the edges for vertex - The edges are in the order they were connected. This is a new list, - so it can be modified, and edges can be removed from the graph while - iterating over it. + :return: the edges at this vertex + :rtype: list of ``Edge`` - .. note:: + For a directed graph these are the edges leaving this vertex. For an + undirected graph they are the edges incident on it, whichever end it + is. A self-loop in an undirected graph is listed twice, once for each + end. - - For a directed graph the edges are those leaving this vertex - - For a non-directed graph the edges are those leaving or entering - this vertex, and a self-loop is listed twice, once for each of - its ends + The list is a new one on every call, in the order the edges were + connected, so changing it does not change the graph, and edges can be + removed from the graph while iterating over it. Use + :meth:`BaseGraph.remove_edge` to remove one. .. runblock:: pycon @@ -4193,7 +4268,7 @@ def z(self) -> float: def closest(self) -> tuple[BaseVertex | None, float]: """ - BaseVertex closest to this vertex + Return the closest vertex to this vertex :return: closest vertex and its distance :rtype: BaseVertex subclass or None, float From 812c240d6ca3fb6f0aa118d458c2aa34c1070e6b Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 06:33:37 +1000 Subject: [PATCH 04/10] docs: describe the package as Graphs for Python, not Mathematical graphs Co-Authored-By: Claude Sonnet 5.5 --- README.md | 7 +++++-- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b7f0dd0..86f1700d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
pgraph logo
- Mathematical graphs for Python + Graphs for Python

[![PyPI version](https://img.shields.io/pypi/v/pgraph-python?style=for-the-badge&color=blue)](https://pypi.org/project/pgraph-python/) @@ -35,7 +35,10 @@ [![powered by Matplotlib](https://img.shields.io/badge/powered_by-Matplotlib-11557c?logo=matplotlib&logoColor=white)](https://matplotlib.org) [![Powered by Spatial Maths](https://raw.githubusercontent.com/petercorke/spatialmath-python/master/.github/svg/sm_powered.min.svg)](https://github.com/petercorke/spatialmath-python) -This Python package allows the manipulation of directed and non-directed graphs. Also supports embedded graphs. It is suitable for graphs with thousands of nodes. +This package provides classes for directed and undirected graphs: vertices +connected by edges, with embedded coordinates, edge costs and path planning. +Self-loops and parallel edges are permitted. It is a graph-theory package, not +a plotting library. ![road network](https://github.com/petercorke/pgraph-python/raw/main/examples/roads.png) diff --git a/pyproject.toml b/pyproject.toml index 32cb28f3..f675c7ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "pgraph-python" version = "0.6.4" authors = [{ name = "Peter Corke", email = "rvc@petercorke.com" }] -description = "Mathematical graphs for Python" +description = "Graphs for Python" readme = "README.md" requires-python = ">=3.9" classifiers = [ From e1015517cedefcef5f6f35b694f797ce01a9c0c3 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 06:45:02 +1000 Subject: [PATCH 05/10] docs: show the data-structures diagram in the Sphinx docs, as SVG - add docs/figs/datastructures.svg, exported from the EazyDraw source, and move the source to docs/figs/ with the other figures - show it at the top of the Edges & Vertices page under 'Underlying data structures', followed by a short description, and split the page into that section and 'Working with vertices and edges directly' - remove the 'Under the hood' section from the README, which embedded the old PNG by URL docs/source/datastructures.png is kept for now because the wiki still links to it by URL. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 6 - docs/figs/datastructures.ezdraw | 2904 +++++++++++++++++++++++++++++ docs/figs/datastructures.svg | 551 ++++++ docs/source/datastructures.ezdraw | 2353 ----------------------- docs/source/edges_vertices.rst | 15 +- 5 files changed, 3469 insertions(+), 2360 deletions(-) create mode 100644 docs/figs/datastructures.ezdraw create mode 100644 docs/figs/datastructures.svg delete mode 100644 docs/source/datastructures.ezdraw diff --git a/README.md b/README.md index 86f1700d..1693b2ac 100644 --- a/README.md +++ b/README.md @@ -138,12 +138,6 @@ for f in f1.neighbours(): # say hi to the neighbours ``` -## Under the hood - -The key objects and their interactions are shown below. - -![data structures](https://github.com/petercorke/pgraph-python/raw/main/docs/source/datastructures.png) - ## MATLAB version This is a re-engineered version of [PGraph.m](https://github.com/petercorke/spatialmath-matlab/blob/main/PGraph.m) which ships as part of the [Spatial Math Toolbox for MATLAB](https://github.com/petercorke/spatialmath-matlab). This class is used to support bundle adjustment, pose-graph SLAM and various planners such as PRM, RRT and Lattice. diff --git a/docs/figs/datastructures.ezdraw b/docs/figs/datastructures.ezdraw new file mode 100644 index 00000000..1cb41848 --- /dev/null +++ b/docs/figs/datastructures.ezdraw @@ -0,0 +1,2904 @@ + + + + + AAAA_CurrentOSVersion + macOS Tahoe: 5026.500000 + AAAA_DKDDocumentVersion + 9.0.0 : Mobile Friendly + AAAA_EazyDrawVersion + 12.3.6 + AAAB_Build + 51059 + AAAB_Distribution + Free Market + ArchiveMatchStore + + Archive_Stores + + + Class_Store + DKDLock + Match_Hashes + + + BinIndex + 10 + BinMatches + + + CenterLock + YES + + + + + NHashBins + 16 + + + Class_Store + DKDGraphicStyle + Match_Hashes + + + BinIndex + 8 + BinMatches + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0 + OpacityPlus + 1 + RedPlus + 0 + + LineWidth + 1 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.149131 + OpacityPlus + 0.796019 + RedPlus + 1 + + LineWidth + 6.34521484375 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0 + OpacityPlus + 1 + RedPlus + 0 + + LineWidth + 2.53369140625 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0 + OpacityPlus + 1 + RedPlus + 0 + + LineWidth + 2.5 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0.570465 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.570472 + OpacityPlus + 1 + RedPlus + 0.570459 + + LineWidth + 1 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0 + ColorSpacePlus + DKDP3ColorSpace + GreenPlus + 0 + OpacityPlus + 1 + RedPlus + 0 + + LineWidth + 1 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 0.474303 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.981323 + OpacityPlus + 1 + RedPlus + 0.450094 + + LineWidth + 2.628856658936 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + DrawsFill + NO + DrawsLine + YES + LineCapStyle + Square + LineJoinStyle + Miter + LineRGB + + BluePlus + 1 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.991439 + OpacityPlus + 1 + RedPlus + 0 + + LineWidth + 2.628856658936 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + + + BinIndex + 10 + BinMatches + + + DrawsFill + NO + DrawsLine + NO + LineCapStyle + Square + LineJoinStyle + Miter + LineWidth + 1 + MiterLimit + 10 + StrokePosition + Front + WindingRule + Non-Zero + + + + + NHashBins + 16 + + + Class_Store + DKDArrow + Match_Hashes + + + BinIndex + 2 + BinMatches + + + ArrowAngle + 160 + ArrowArchiveVersion + 9 + ArrowBackEnd + YES + ArrowForEachSegment + NO + ArrowForm + solid + ArrowFrontEnd + NO + ArrowLineWidthFraction + 0.25 + ArrowOffset + 0 + ArrowSize + 8 + ReferenceArrow + Relief + UseCurveFillAndStroke + YES + + + + + BinIndex + 10 + BinMatches + + + ArrowAngle + 159.730010986 + ArrowArchiveVersion + 9 + ArrowBackEnd + YES + ArrowForEachSegment + NO + ArrowForm + solid + ArrowFrontEnd + NO + ArrowLineWidthFraction + 0.25 + ArrowOffset + 0 + ArrowSize + 10.7576407035 + ReferenceArrow + Relief + UseCurveFillAndStroke + YES + + + ArrowAngle + 160 + ArrowArchiveVersion + 9 + ArrowBackEnd + YES + ArrowForEachSegment + NO + ArrowForm + solid + ArrowFrontEnd + NO + ArrowLineWidthFraction + 0.25 + ArrowOffset + 0 + ArrowSize + 10.6271449412 + ReferenceArrow + Relief + UseCurveFillAndStroke + YES + + + ArrowAngle + 160 + ArrowArchiveVersion + 9 + ArrowBackEnd + YES + ArrowForEachSegment + NO + ArrowForm + solid + ArrowFrontEnd + NO + ArrowLineWidthFraction + 0.25 + ArrowOffset + 0 + ArrowSize + 37.5449518519 + ReferenceArrow + Relief + UseCurveFillAndStroke + YES + + + + + NHashBins + 16 + + + Class_Store + DKDParagraph + Match_Hashes + + + BinIndex + 0 + BinMatches + + + FirstLineHeadIndent + 0 + HeadIndent + 0 + LineFragmentPadding + 5 + LineSpacing + 0 + ParagraphSpacing + 0 + ParagraphSpacingBefore + 0 + TailIndent + 0 + TextAlignment + Left + + + + + NHashBins + 16 + + + Class_Store + DKDFont + Match_Hashes + + + BinIndex + 0 + BinMatches + + + Family + Courier + Name + Courier + Size + 12 + Traits + 400 + + + Family + Avenir + Name + Avenir-Book + Size + 12 + + + Family + Avenir + Name + Avenir-Heavy + Size + 22 + Traits + 2 + + + Family + Bradley Hand + Name + BradleyHandITCTT-Bold + Size + 12 + Traits + 22 + + + Family + Courier + Name + Courier + Size + 11 + Traits + 400 + + + + + NHashBins + 16 + + + Class_Store + DKDTextAttributes + Match_Hashes + + + BinIndex + 0 + BinMatches + + + Font + 0 + ForeColor + + ColorSpace + NSCalibratedWhiteColorSpace + Opacity + 1 + White + 0 + + Paragraph + 0 + + + Font + 0 + ForeColor + + BluePlus + 1 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0 + OpacityPlus + 1 + RedPlus + 0 + + Paragraph + 0 + + + Font + 16 + ForeColor + + BluePlus + 1 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0 + OpacityPlus + 1 + RedPlus + 0 + + Paragraph + 0 + + + Font + 32 + ForeColor + + BluePlus + 0 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.333 + OpacityPlus + 1 + RedPlus + 1 + + Paragraph + 0 + + + Font + 48 + ForeColor + + BluePlus + 0.570465 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.570472 + OpacityPlus + 1 + RedPlus + 0.570459 + + Paragraph + 0 + + + Font + 64 + ForeColor + + ColorSpace + NSCalibratedWhiteColorSpace + Opacity + 1 + White + 0 + + Paragraph + 0 + + + + + NHashBins + 16 + + + Class_Store + DKDDashPattern + Match_Hashes + + + BinIndex + 0 + BinMatches + + + DashOn + YES + DashSequence + + 4 + 5 + + + + DashOn + YES + DashSequence + + 4 + 5 + + + + DashOn + YES + DashSequence + + 4 + 5 + + + + DashOn + YES + DashSequence + + 4 + 5 + + + + DashOn + YES + DashSequence + + 4 + 5 + + + + DashOn + YES + DashSequence + + 4 + 5 + + + + + + NHashBins + 16 + + + + BBB_DKDQuicklookData + + /9j/4AAQSkZJRgABAQAASABIAAD/4QCARXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEA + AAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAA + AAAAAABIAAAAAQAAAEgAAAABAAKgAgAEAAAAAQAAA1KgAwAEAAAAAQAAAyYAAAAA/+0A + OFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY + 7PhCfv/iAihJQ0NfUFJPRklMRQABAQAAAhhhcHBsBAAAAG1udHJSR0IgWFlaIAfmAAEA + AQAAAAAAAGFjc3BBUFBMAAAAAEFQUEwAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMt + YXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + CmRlc2MAAAD8AAAAMGNwcnQAAAEsAAAAUHd0cHQAAAF8AAAAFHJYWVoAAAGQAAAAFGdY + WVoAAAGkAAAAFGJYWVoAAAG4AAAAFHJUUkMAAAHMAAAAIGNoYWQAAAHsAAAALGJUUkMA + AAHMAAAAIGdUUkMAAAHMAAAAIG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAFAAAABwARABp + AHMAcABsAGEAeQAgAFAAM21sdWMAAAAAAAAAAQAAAAxlblVTAAAANAAAABwAQwBvAHAA + eQByAGkAZwBoAHQAIABBAHAAcABsAGUAIABJAG4AYwAuACwAIAAyADAAMgAyWFlaIAAA + AAAAAPbVAAEAAAAA0yxYWVogAAAAAAAAg98AAD2/////u1hZWiAAAAAAAABKvwAAsTcA + AAq5WFlaIAAAAAAAACg4AAARCwAAyLlwYXJhAAAAAAADAAAAAmZmAADypwAADVkAABPQ + AAAKW3NmMzIAAAAAAAEMQgAABd7///MmAAAHkwAA/ZD///ui///9owAAA9wAAMBu/8AA + EQgDJgNSAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//E + ALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHw + JDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0 + dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS + 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAAB + AgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKB + CBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZX + WFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2 + t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAHBwcHBwc + MBwcMEQwMDBEXERERERcdFxcXFxcdIx0dHR0dHSMjIyMjIyMjKioqKioqMTExMTE3Nzc + 3Nzc3Nzc3P/bAEMBIiQkODQ4YDQ0YOacgJzm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm + 5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5v/dAAQANv/aAAwDAQACEQMRAD8A6SiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKAP/Q6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiigkDrQAUU + hZRyTSb1xnPFK4rjqKbvUgnPSgOp6Gi6C6HUU0Mp4Bpcg9DTuO4tFFFABRRRQAUUUUAF + FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFA + BRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRR + QB//0ekooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiikJAGTQAtFMD7gdooAc/eNK4rj8jpTBIpOBQq + KvI60/GOlGoake5yenFBVyfvYFSUUWCwxkDHJJpSinGe1Ooosgshu1cYx0pdoxjFLRTs + OwmB6UYHpS0UAIFUHIFN8tAcgU+ilZCsiMx85BIpT5mcrjFPoosFhjOVPI/Gnbhx70tN + ZVbqKNQ1HUUwqwACHpRv2gb+CaL9wv3H0UAg8iimMKKKKACiiigAooooAKKKKACiiigA + ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0ukooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopGYKMmgBaaW + AGetN+Zxz8tOVQowKVxXGjcwOflpyoF9806iiwWCiiimMKKKKACiiigAooooAKKKrSXl + rF/rJVHtnn8qALNFZTazaZxEHkP+yv8Ajim/b7yT/U2jfVjigDXorI36zJ0SKP6nP8s0 + fZtVf79yq/7q/wD6qANeisj+zbhv9ZdyH6cf1o/seE/fllb6t/8AWoA1iQOpxTfMj/vD + 86zBotj3DH6mn/2Pp/8AzzP/AH0f8aANHzE/vD86Xgj1rM/sbT/7h/M03+xbL+Hcv0NA + GmVIHyHGKN2AN/BNZf8AZCL/AKqeVD/vUGwvl+5dk+zDNK3YVuxr0VQhW+jVhMVb0Kjj + 8agbUpYGIuYGCj+JeR/Si4XNaiqMOpWU3CSAH0bj+dXqYwooooAKKKKACiiigAooooAK + KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKA + CiiigAooooAKKKKACiiigAooooAKKKKAP//T6SiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKjBL54+Wk2JsXduyE60qrt + 6nJNKAAMClot3C3cKKKKYwooooAKKKKACiiqtze21oP3zc/3RyfyoAtUySWOJd0rBR6k + 4rJ87Urz/UIIIz/E33vyqSPSYN3mXLNM/qx4/KgBG1aEtstUeZv9kcUmdXuOgSBffk/1 + rVREjXagCgdhxTqAMj+yjLzdzvL7ZwPy5qzHptjF92JT9ef51eooARVVRhQAPaloooAK + KKKACiiigAooooAKKKKACiiigApjJnkHBp9FJq4mrlOW1tJztmjUt64wfzqn/Zs9vzYz + sn+y3K1rsobrTAxDbWHXpRe24XtuZY1Ce2O3UIio/vpytakU0U674WDD2p5AIwRkGsub + TFDedYt5Mnt90/UUxmrRWTFqLxOINQTymPRv4TWt15FABRRRQAUUUUAFFFFABRRRQAUU + UUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF + FFFABRRRQAUUUUAFFFFAH//U6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAoJA5NFRkeYQc/LSbE2GGLZPAFSUUUJAkFFFFMYUUU + UAFFFFABTXdI1LyEKo6k0yeeO3jMspworJjgm1NhcXeVhHKR+vuaAHG6ur9ilgNkfQyt + /T/P5VattOt7c7yPMk6l25OavKqqAqjAHQCloAKKKKACiiigAooooAKKKKACiiigApGZ + VGWOBS1HJ/D/ALwoAPNi/vD86PNi/vD86V5AnXJ78egpvnLztBODj8aAF82L+8Pzo82L + +8PzqIT4UsRklsACplkVyQM8cGgBPNi/vD86PNi/vD86Z5uZAo4HP44oNwoGQDk9BQA/ + zYv7w/OjzYv7w/Ok80Zbjhepo80MG2ckfrQAvmxf3h+dHmxf3hRG5fORjBIqSgCGN+SM + ggd6mqIgGQg/3R/OlHyDDH6UthbBNDFOhjmUMp9ax/8ASNJPeW2/VP8A61blBAIweQaY + xkciTIJIzuU9CKfWHJHJpUnnwAtbsfnT+77itmORJUEkZyrDINAD6KKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigD/1ekooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAoopCQoyaAGsQ3yZ5pwAAwKaqgZb1p9JdxLuFFFFMYUUUU + AFFFFABQSAMnpRWXqsrLAtvH9+dgg+h60AV4lOqXJnk/494jhB/ePqa3KihiSCJYU6KM + VLQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVHJ/D/vCpOnJqAlmKt0XPHv70ASNGrHL + UGNCNpHBOabJLsIGMk5x+FMFwpUkdhx70ASGJD04+lPVQowO5zVZZnbGAD82OOh4zS+c + 4TO3LEkD86AJRDGO1J5KY7/XPNCyFmK46dT9RUtAERhjJyRnPHU07y0BDAcjgU+igBoV + QSR3p1FFAEf/AC1P+6P509lDDBpn/LU/7o/nUlADAwUhD+dPpCobrTVfJKtwaWwthxAY + FSMg1iITpVyImP8Ao0p+U/3W9K3KguIEuYWhk6N+h9aYyeisvTZ3Iazn/wBbDx9R2Nal + ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRR + RQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//9bpKKKKACiiigAooooARiFUsewzVVJ5 + 3UOsPDDI+Yd6sS/6tvoaZb/6iP8A3R/KgBnm3H/PH/x4Uebcf88f/HhVmigCt5tx/wA8 + f/HhR5tx/wA8f/HhVmigCt5tx/zx/wDHhR5tx/zx/wDHhVmigCt5tx/zx/8AHhR5tx/z + x/8AHhVmigCt5tx/zx/8eFHm3H/PH/x4VZooArebcf8APH/x4Uebcf8APH/x4VZooAre + bcf88f8Ax4Uebcf88f8Ax4VZooArebcf88f/AB4Uebcf88f/AB4VZooArebcf88f/HhT + Gknb5TFz6bhVymKASXBzmk+wn2IfNuP+eP8A48KPNuP+eP8A48Ks0Uxlbzbj/nj/AOPC + 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 + 8zIwTggj+nFTNbxMqpjAQYXBII/Ec0020Lbc7vl6YZh/I0Xh2AYZpxMkflj5lJPPcEf4 + 0x70LKYwmQrBSe+T6DH9asyQxy4Lg5HQgkdfpTTbQlt2CPXBIBx6jPNJOPVARfaX+0fZ + 9g3Zz1/h9en6VJLOY5Vj28N3Jx37cdaPssO7fg53bs5PX86e8MchDPnj3OOPUUXjfYCG + SfMXAxu3D6bQf8KYLlo41MyYyuRg5Jx2NT/ZoS24g557nHPXj3oW2hUYxkY24JJwPQZo + vECNnk8yMyALyehyOn0FRi9JVjs5GOhOMHPOce3pVkW8QGCCevUk9RjuaaLWIZxuycc7 + jnjpzn3p3j1QDBdZkRNoAYA5z6+nGD+dW6gFtCCpAPy4wMnHHTip6iVugwoooqQCiiig + AooooAKKKKACiiigAooooAKKKKACiiigAooooA//1ukooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAY6k4I6inAg9KWo8BCWzwaWwt + iSiiimMKKKKACiiigArH0/8A0e6uLI8fNvX6H/IrYrI1JHhePUIhkxcMPVTQBr0UyN1l + RZEOVYZFPoAKKKKACiiigAooooAKKKKACiiigAooooAKzb68aMi1thunfoPT3NLe3pgI + ggG+d/ur6e5p1lZC2BkkO+Z+Wb+goAdZWa2kZyd0jcu3qau0UUAFFFFABRR05NUJ7v8A + hi/P/Ck3YqMXLYsTXCQjHVvSswmW5f1P6CnQ27zHceB61qxxpEu1BUayNrxhtuQwWyxf + M3LVZooq0rGDberCiiimIKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK + KAP/1+kooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKQgEYNLRQAxSEwhp9NZQ3WkD/NtPBpbC2H0UUUxhRRRQAUjKrqUYZBGCKWig + DFs2awuTp8p+RuYif5VtVTvbRbuHZnDryrehqOwu2nUwzfLNHww9fegDQooooAKKKKAC + iiigAooooAKKKKACs+9vfIxBAN87/dX09zRe3vkEQQDfO/3V9Pc0tlZfZ8yynfM/3m/o + KACysvs4Msp3zP8Aeb+gq/RRQAUUUUAFMeRI13OcVDNcrFwOW9KzsS3L+p/QVLl0RrCn + fV7D5rh5jtHC+lTwWn8Uv5f41Yht0i56t60+OZZGZFzlTg0ku5Up6WgSgAcCiojMolEP + OSM+1S1dzFoKKKiml8lN+M0N2BK+iJaKhM6h1Qg5cZqalcGrBRRRTEFFFIGUkgEEjrQA + tFFFABRUU0vkpvxmpFO5QfWlfoO2lxaKKKYgooooAKKKKACioVm3TNDj7o61NSTuNq24 + UUUUxBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFF + ABRRRQB//9DpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigApCM896WigCNSwOG/OpKCARg1GFKZI59qWwtiSimq4bpTqYwoo + ooAKyr+2kDC+tf8AWx9R/eHpWrRQBXtbmO7hE0ffqPQ+lWKxLhW024+2RD9zIcSKOx9a + 0Tch8LbYkYjOf4QD6n+lAFkkKMscAVX+1I3+pVpfdRx+ZwKBbBjvuD5je/3R9B/k1ZoA + rbrtuiIv1Yk/kB/WjF5/ej/I/wCNWaKAK267XqqN9GI/oaPtIX/XI0fuRkfmM/rVmigB + qsrqGQgg9xzWDqWqS21yIoMYVfmz6n/CthrZNxeImNz3Xv8AUdDWdDaQxXBe8XdK7ZDH + 7p9h6H2P4UAS6ZbosX2piXll5Zj1+lalFFABRRUckqRLuc0AlckJAGT0rOnu8/LF+dQS + zyTnaOnYCrMFoB80vJ9Ki7eiN1BR1kV4bZ5fmbhfX1rVRFjXagwKdRTUbGc5uQVStf8A + XTfX+pq7VN4JVkMsDAE9QaJdGELaoRv+P5f93/GoxJcyM4RgApqeGB1kMsxyxp0MLRtI + WI+Y5FTZluSRALpxbeYfvZxUVwLkRZlIIP6VOtqfIMTEZzkEUx4LqRdjsMCpadtSk4p6 + EhkZZYkHQjmo1e5ld1RgAp6mpzCxljfIwgwaWCJo2ctj5jkYqrO5N1Yqtcu7ttcIB046 + 1It0fs5kI+YHFBtpEdmi24b+8OlS/Z90HlOeT3ApJSG3DQjVbw4YsOR+VQwLN574YZB+ + b3qxHHdKyhmG0frSCGZJy8ZG1jzmi2wX3WhC1y7u21wgHTjrTxdt9n3/AMWcUptpEdmi + 24b+8OlPNsWg8tiN3XOKLSBuGhWuBciLMpBB/StJPuL9BVF4LqRdjsMCr6jCgHsKqK1J + m9Eim0k8sxjhIUL1NEUk3nNFKR8q9qbIhWctDIFY9QaZbBjcOSd3HJqbu5VlYdbvczYY + sNoPPvTZZ5FJIlXIPAAq1bwtFGUYjJPaqwtZlVowVwe/eizsCcbsWS4lIiMfBft70pku + IHXzSGVjio5Y2XyY84b1/GphBNI6tOwIXsKNR+6kRESm7cREA46n8KsWsryBlk6qcU5I + mW4aU4wwogiaNnLY+Y5GKaTTJlJNFiiiitDEKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/R6SiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigBCoII6Zpg3 + IDu5FSUUrCsNVg3SnU0qCDjjNNG5Qd3NF+4X7klFMWRWOBUc8jIoWP77nC/4/QUJ3BO5 + HOTcFrWPp0duuAew9z+lULNmsLk6fKfkb5o2/pWtFGsSBF59SepPcmquoWv2qD5OJE+Z + D7imMvUVTsbr7XbiQ8MOGHuKuUAFFFFABRRRQAU10WRSjjIPUGnUUAVEZrdxFKco3CMf + /QT/AEP+Tbpkqo8bLJ90jmss3chTys5I43dyOxpN2LjBy2Ls90sfyry38qz1WW4fPU9z + 6VLBatJ8z8L+prTVFRdqjAqLN7mrkoaLcihgSEccn1qeiitLGDberCiiigQUUUUAFFFF + ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAEL28Mh3MvNPSNIxhBin0UrIfM9gooopiGNG + jsGYZK9KfRRQFwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK + KKKACiiigAooooAKKKKACiiigD//0ukooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigBnlpUEX72Z + 5j0X5F/D7x/Pj8Kmmk8qJ5P7oJ/Kkgj8qFIz1A5+velYViWiiimMxl/0LVCo4juRn/gQ + /wA/rWzWVq6H7MJ1+9CwYVpo4kRXXowBH40AOooooAKKKKACg9OOaKKAMaWWWd9pH0Wp + Db+QFnfnaRuHseP061phEDFwOT1NDqroUbkMMH8alR6s1lU0tEdRUFszPApb7wG0/UcH + 9RU9UZBRRRQAUVEGkbJAGMkcn0OKN0mcfLk89T/hQBLRUf730X8zR+99F/M0ASUVCXcE + KduT7n/CnfvfRfzNAElFR5k6fLz70fvfRfzNAElFR/vfRfzNH730X8zQBJRUTNKqliBw + M9aloAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooo + oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//0+kooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigCtd8w7f7zIPzYCrNVrr7in0kT/ANCFWaAKUcbTNIzSOMOQADgACpPs + 3/TWT86Lb/lp/wBdGqzQBnXltm1l/eOcITgnjgVHYRGSzifzHHy9AfTitKVPNiePONyk + Z+tR20H2aBIAd20YzQA37N/01k/Oj7N/01k/OrNFAFb7N/01k/Oj7N/01k/OrNFAFb7N + /wBNZPzo+zf9NZPzqzRQBW+zf9NZPzpbUsY2DEttdhk9cA1YqpbhyH2nA8xu3vQA624M + q+kh/UA/1qzVOBSzzfMfv9u/yirHlL3yfqTQA8kDqcUzzY/7wP0pRHGOij8qfQBFHzGc + erfzNRq7AABTgL6d6ejooIY4O4/zNO82P1oArgudrHdweePbtxU0rOCAnp6ZyfSnebH6 + 0ebH60ANQOZWLEcYHSoirea2RnJ44J4/PFTCSIEkHrS+bH60AV8yF9+CcZ49+1LvlXPJ + ORkZ9eO3pU/mx+tMVoU5B/M5oAjDuNoyzD2HXn6VbqPzY/WjzY/WgAl/1TfQ1JUEkiGN + gDkkGp6ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK + KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//U6SiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKAK92Cbd8dQNw+o5/pU4IYBh0PNL14qtanbGYT1iO38B0/TFACfZ3 + DMY5WUMc4wp5/EUvkzf892/Jf8Ks0UAVvJm/57t+S/4UeTN/z3b8l/wqzRQBW8mb/nu3 + 5L/hTCrhxGZ33MMj5V7f8BqoLWf7V5jA58zduGPu+mc56dsUkNvMssbGMqwDB3yDknvj + NAF/yZv+e7fkv+FHkzf892/Jf8KyYomd3jiAWQRFSQ275s9c9s/nUq2sqxyBY2G4KNvy + jODyepH59aANHyZv+e7fkv8AhR5M3/PdvyX/AApllE8UbKy7ctkDgdh2BIH4VcoAreTN + /wA92/Jf8Kkhi8pNuSxJJJPqTntUtQXLskJ2feb5V+p4FADbTmIyf32ZvwJ4/SrNNRFj + RY16KAB+FOoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKAP/V6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACqsv7mUXH8J + +V/6H8P61apGUMCrDIPBFAC0VUiYwMLeQ8H7jHuPQ+4/WrdABUBtrZiSYkJP+yKnooAO + nAooooAQADoKWiigAooooAKqr+/uN/8ABFkD3bufw6fnSzSMzfZ4T856n+6PX6+lToix + oEQYA4FADqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigA + ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigAooooAKKKKACiiigD/9bpKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAGSR + pKhRxkGq4le3+W4OU7Sf/Fen16VbooAAQRkUVV+zmPm2bZ/snlfy7fhS+dKnE0R+qfMP + y6/pQBZoquLu26GQKfRvlP64p/nwYz5i/mKAJaKrm7tgceYpPoDk/kKTz3f/AFMTH3b5 + R+vP6UAWaqmZpSUtuexfsPp6mjyHl/4+WyP7i8L+Pc/y9qsgBRgDAFAEcUSQrtTucknq + T6mpax7qMmeRkU7vlxhCckf7Xb61LvmF2Dhwu/BGGIxjg+n5Vr7PS9xXNOisuIzmcn5z + 97ruA9sg8flUcMc037ucthlIcEHGfx449qPZ92FzYorKCTmJ5lTbJhUGBzgdSP6fSm5l + EL/NJ1G3AbI9evJH14o9n5hc16Kz5YswoyNIQHBPXdjvweaZOZW8vZvEeDnhs57ZxzSU + L9QuadFZEn2jamDISF+fAIyMjp/tVduMmFdhYcjs2ce+OaHC1tQuWqajhxlexI/I4rP8 + yYRkFX3MFwOTj15pEDqxC+YH80467dpbn26fjR7MLmi7iNS7dBTqzcM0TBt5kIO4HOOv + bt9MUmZjOSN6g7gepx6Hnj6Yo9mFzTorIJm8v+P5Sf7/AM3A/EfjkVrDkAnilKNhi0UU + VABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABR + RRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH/1+kooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiijIHWgAIB4NR+VF12L+VOLqBk96TflcqM0roV0OAAGAMU + tMy5B4waAH53Gi4XH0UxVIOSxNAjAOcmi7C7H0VH5a5zSmNScmjUNR9FMKAnOTQUOAAS + MUXYXY+imYcDAPNGXC5Iyfai4XH0UwOCCSMYpwYHoaLhcWiiimMKKKKACiiigAooooAK + KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKA + CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//Q6SiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiml1Bx3pp8wn0FK4rjyQOpprOQcAZNGxc5Ip9GoajCHPQ4FBQHG7nF + PoosFhAAOBS0UUxhRRRQAUUUUAFFFFABRRRQAUUUUAHWm7F5xxmnUUWCwwKy98ihXydp + GDT6KVuwrdg69KKYECnK/lQrknawwaL9wv3H0UUUxhRRRQAUUUUAFFFFABRRRQAUUUUA + FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFF + ABRRRQAUUUUAFFFFABRRRQAUUUUAf//R6SiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooJA61GS5bA4A70mx + Nji6g4PWm4ctycAU8KoOQOaWi3cLdxAoByBS0UUxhRRRQAUUUUAFFFFABRRRQAUUUUAF + FFFABRRRQAUUUUAFFFFABRRRQAUUUUARhShyDx6U5XDfWnU0r1I4PrStbYVrbDqKYpYc + P270+hMEwooopjCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigA + ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0ukooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAprMFHNDNtFIFzhmHNJvohN9EIU3HcenpUlFFCQJBRRRTGFFFFABRRRQAUUUUA + FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFR7nLELjA9aP3v+z+tAElFR/vf9n9aQGRhk + FSPxoAloqP8Ae/7P60fvemVoAkoqP97/ALP60fvf9n9aAHkBhg0zPljk5FH73/Z/WgiU + 8Hb+tJoTRJ15FFQEyRjtipEYtnPY44+maEwTH0UUUxhRRRQAUUUUAFFFFABRRRQAUUUU + AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFF + FABRRRQAUUUUAf/T6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKQnA45pajCkvuPQdKTExUDYy3en0UUIEgooopjCiiigA + ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCNPvv9R/IVHmQ8NkbepA6 + /hUifff6j+Qp+5eOevSgCKEnZg5z3zURkk2rkN05wOTVuigCCYvgBAec9KYC4ycHJUHp + 6VaBBGRR05NAFVmmIPGMc8e/QUK0ij5t2N31OMVapjpuwQcEcigCuZJMHOR83brjH0q0 + DkA/zpqqEHJ5J5PvT6ACoVLB2wMjOP0FTVGn3n/3v6Ck0JokoqMDyyeflqShMEwooopj + CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii + gAooooAKKKKACiiigAooooAKKKKACiiigD//1OkooooAKKKKACiiigAooooAKKKhknii + ba7YOM460ATUVW+12/8Ae/Q/4Ufa7f8Avfof8KALNFVvtdv/AHv0P+FH2u3/AL36H/Cg + CzRVb7Xb/wB79D/hR9rt/wC9+h/woAs0VW+12/8Ae/Q/4Ufa7f8Avfof8KALNFVvtdv/ + AHv0P+FH2u3/AL36H/CgCzRVb7Xb/wB79D/hR9rt/wC9+h/woAs0VW+12/8Ae/Q/4Ufa + 7f8Avfof8KALNFVvtdv/AHv0P+FH2u3/AL36H/CgCzRVb7Xb/wB79D/hR9rt/wC9+h/w + oAmcFvlH408DAwKppcwAks3J9j/hT/tdv/e/Q/4Ul3Eu5Zoqt9rt/wC9+h/wo+2Ww5LY + HuDTGWaKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACo2JY7E/E + +n/16VmOdidT+lOVQowKAI41Cl1HTP8AQVD5cgUKMjDdSe3PIqUOiu4ZgOR1PsKd5sX9 + 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 + 8KOaPYCD7TO0DTCPACkjJ7j+lPe5aOESSJgkgAZ65qWOCONDGoO09iSf5mmi1hClMEg4 + 6kk8dMHPFK8ewEP2w+UZSnCthueg9RxzUyTO1v55TnGQoOSR2/Ol+zRYAIJw27lj19+e + ackMaRmJQQpzxk96G49EBHFceYVGB8xPQ56fgKhSaV5i0aZyinBOMct7VY+zRYxz1znJ + znGOuc9KT7JBxgEYG3gkcenWneIDY7hpiDEmVwMknB556VAt0YYY8jd8oJ55/LB/pVv7 + PFuDAYxgcEgcdOBTTawMMEHGAMZPQdM80Jx7AR/a2zINnKZ4zycHGcY6d+M1Yhk82MPx + z6HIpn2eIksQcn3PGTnjnj8KkSNY12p06+vWpk420GPoooqACiiigAooooAKKKKACiii + gAooooAKKKKACiiigAooooA//9XpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigApjg8MvUU+ik0JoBRUeCrEjoetSUIEFFFF + MYUUUUAFRTwrcQtC/RhipaKAMvTJmKNZzf6yA7fqOxrUrI1CJ4ZF1G3HzR/fHqtacMyT + xLLGcqwzQBJRRRQAUUUUAFFFFABRRRQAUUUUAFFFQXFxFaxGWU4A/Mn0FABcXEVrEZZT + gD8yfQVnW1vLeSi+vRgD/Vp6D1NJb28t7KL29GFH+rj7AeprZoAKKKKACiigkAZPAoAK + rT3KxfKOW9Krz3ZPyxcD1qKG2aX5m4X+dQ5dEbRp21mRgS3L+p/QVpQ26QjPVvWpkRY1 + 2oMCnU1Gwp1L6LYKKKKoyCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igD/1ukooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAIBGDUY/d8MfpUlIQGGDSaE0LRUefLwD09ak680JgmFFFFMYUU + UUABAIwawxnSrnaf+PaY8f7LVuVFPDHcRNDKMq1AEtFY9lNJbS/2ddHkf6tvUelbFABR + RVdrmPJWPMjDsoz+Z6D8TQBYoqtuu2+6ioP9o5P5D/GjZdnrKo+if/ZUAWaKrbLsdJEP + 1U//ABVHmXKffjDD/YPP5HH86ALNFQJcRO2zO1v7rDB/WquqXJtrRihw7fKtAFq4uIrW + IyynAH5k+grNt7eW9lF7ejCj/Vx9gPU1U0/dqM/nXeW8sfKMfLn/ABrpKACiiigAooqp + PdLH8qct/Kk3Yai3oieSVIly5/CsqWaSdto6dgKRUluHz1PcmtSGBIRxyfWo1kb+7D1K + 8FoF+aXk+lXqKzEhWaaUMSMHt9ae2iM/iu2zTorO+e1lVd25G9amUxrcSMN24DJ9KfMD + gW6KqpdxyMFUHJoa7iBIAJA6kDijmQuSXYtUVE08aRiQng9Kh+2RkHAbI9qbkhKDfQt0 + VStrkyDbJkt69qebyIHgEj1xxS5la43Td7FqkLKpAYgZ6VFJPHGgcnIPTHeqUkyzSxbQ + Rhuc/hQ5JBGDZp0UVVa7iVivJx1IptpbkqLexaoqutzGyNIM4WkS6jkOFB4Gc0cyHyPs + WaKqi8jyMhgD3I4p0lzHE2xs5xmlzIOR7WLFIrK33SD9KgiuY5W2jIPoapwXCQhgwJye + 1JzRSpvU1KKajrIodehp1WZhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFF + FFABRRRQAUUUUAFFFFABRRRQAUUUUAf/1+kooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAOvBqNg4O5fyqSik0 + JoTcM7e9LTWRW600l1PTIov3C/ckopMgnFLTGFFFFAFK+tBdxYB2yLyjehqC11APGUmB + 89DtKDqT6ir00vlAADc7HCr6n/D1rJu7ea1I1GM7pF/1g7FfYegoA0vJkm5uTx/cXp+J + 7/yqyqqoCqAAOgFMhlSeJZYzlWGRUlABRRRQAUUUUAMeNJV2yKGHvWbcWO51d8zRp/yz + J5H0Pf6GtWigCKFomjBhxtHGBxj2xUtVpYnVvPg+/wDxL2Yf4+hqaORZUDp0P5g+hoAf + SEhRljgCo5ZkiGW69hWXJLJcNj8gKlysaQpuWpNPdlvli4HrTYLVpPmfhf1NWILQJ80n + J9Owq7SUb6st1FHSA1VVBtUYFOooqzAKy0nSGaUsCcnt9a1KKlq5UZWvczxvupVbbtRe + eaP+Xmb/AHP8K0KKXKV7TyKdoubbA4JzVFf3atG7Mp/ujvW1RQ4bDVTVmZJGUiicAlVO + SD71ajuI5mKoDnHWrNFNRtsJzutTMtmBia36Oc/yqAEKhjdmB/u4rawOtFTyFe112M2R + DF5UhBKr1zTZJlmmjKA4BHP41qUU+QSqd0FZkMotg0cinOe3etOqZjuwSFcEH1ol3QoN + aplWP/j3m4xzVtSyWgaMc7algh8lNuck8k1NRGI5TTZiSPvjGXZj3HYVZmcR3SuwyAta + VVzExuBNxgDFTyMpVEysGFxcq8YOF6mi3A8mX8f5Vo0VXL1Ic+hVs/8AUD6mrVFFUlZW + Ik7u4UUUUxBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU + AFFFFABRRRQB/9DpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAYUUnPQ0hZ1PTIqSilYVhNyjg + mhmCqWY4AGSaY8YY5HFV5QXZLYnIblv91e34nAou7hd3HwKXJuZBhm+6D2Xt+J6mrJAI + welFFMZi2ubC9ayb/VyfNH7eorarM1WEyW/nR8PCd6n6dau28wuIEmX+IZoAmooooAKK + KKACiiigArMmnEExaE53feHbPY/41JeSSqdg4U9/WoobQyDdLwvp3NQ29kbRgkuaREkc + tw2fzJrUihSEYXr3NR2pPleW33oyVP4d/wARzVmmo2JnUctAoooqjMKKKKACiiigAooo + oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKK + KKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//R6SiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAqtF888snphB+Ayf1NWarWvMbN6u5/8eNAFmiqs675 + ooySFO4nBI6D2xS/ZIvWT/v4/wDjQBYIBBB5BrJ0klEltT/yxcgfQ1e+yResn/fx/wDG + su3gQancRZbGFPDMD0HU5yevegDdoqt9ki9ZP+/j/wCNH2SL1k/7+P8A40AWaKrfZIvW + T/v4/wDjR9ki9ZP+/j/40AWaKrfZIvWT/v4/+NH2SL1k/wC/j/40AWCobGRnFLVb7JF6 + yf8Afx/8aYIxDcoELYZWyCzN0x6k0APHyXbDtImfxU4P8xVmq0vFxC3qWX8xn+lWaACi + iigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigA + ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKAP/0ukooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKrWn+px6M4/JjVmq1v8AK8sf + o+R9GGf55oAJf+PmH/gX8qs1FLCJSpyVK9CPeo/s5/56yfmP8KALNVktUS6e6BO5wAR2 + 4o+zn/nrJ+Y/wo+zn/nrJ+Y/woAs0VTeNIhukndR6lgP6UyURxJvad+RkDcOfpQBfoqi + VjVQz3DLuGRlgP6UpSMMENw249BuGT+lAF2iqWyMv5QuG3/3dwz+WKk+zn/nrJ+Y/wAK + ALNVpP8Aj6i/3X/pR9nP/PWT8x/hSpbhJBIXZiAQNx9f/wBVACTczQD/AGif/HT/AI1Z + qsfmu1H9xCT/AMCIA/kas0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU + UUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF + FFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9PpKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACqz/u7lJO0g2H6jkf1qzUc0YljMZOM9D6EdD+BoAkoqGCUyplhhlOGHoRU1ABUbyxRY + 8x1XPTJAqSigDPnT7UyS27q3l5BAbHX3GahNjKqbEVDuTZ8xPy8k5HHPX2rWooAxbqGR + VC4VmdVXGCxBX0OOn1xU0llI0zsMMrsG5YjGMdgOenqK1KKAM6O2mW48w7QoZm4JPXPY + jg+uDWjRRQAUUVWuGLYt0PzSdT6L3P8AQe9ABbfPvn/56Hj/AHRwPz6/jVmkUBQFUYA4 + FLQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA + UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU + AFFFFABRRRQB/9TpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCtKjI/2iIZOMMo + /iH+IqaORJUEkZyp6Gn1WeJ42MtvjJ5ZD0PuPQ0AWaKiimSXIXhh1U8EfhUtABRRRQAU + UUUAFFFQSTqjeWg3yf3R/U9hQA6WVYVyeSeAB1J9BTYYmXMknMj9fb0A9hRFCQ3myndI + ePYD0FT0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA + UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU + AFFFFABRRRQAUUUUAFFFFAH/1ekooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igCKSGOXBYcjoRwR9DUWLqLoRKPf5W/McH8hVqigCt9pC/61HT/gOf1XNH2y17yKPqcf + zqzRQBW+2W3Zw305/lR9oLf6qN29yNo/8ewf0qzRQBV8u4l/1rhB/dTr/wB9H+gFSqkc + CHYMAcnHJ/xNS01wShC9SOM0ICOO4ik+6ccbuQRx6804SxFDIHUqOpyMfnVCOznRWxtQ + mPZwS2T68jigWs4VxhfmKkfMxIx74rVwj0Yi/wCdDs8zeu31yMfnSefFuCA5LAsMAkED + 3FUjaTGNckblctjPqPXHX3xTvskghAjIRwxOck/e68/r9aOWPcC0LiEorlgobpu4z+dO + aaJCQ7qCOuSBVOa0ZnBjwV2bNpJGB+Rp/kSCWNtiMqLt5Jz256H+dHLHuBZE0TOYww3D + tS+bFuKbhuHUZ5qv5LrOZI1QhsZJ4Ix1xxUCWciSA8MFYsCWPfPbH9aXLHuBeWaJiArq + SeRgjmlMkYbYWAbGcZ5xVO3t5opTIwXDZyAT8vP8PHT196dJbyPMWXAVupz14x0x1980 + csb2uBPLKI13Ag8r+RIGacJoihkDqVHU5GKpvbzyKQwT7qgDJIODnninNBK7+cQobK4X + PB256nHv6UcsbbgT+em/qNm3duzx1xT/ADoQocuu09DkYNV5IZn+YbVbGMA++euPTvio + TazbDjG7cxB3HjIHtz78U+WPcC+JIyxQMCw6jPNPqpFDKkxc4CnOec5J9scfnVuokkth + hRRRUgFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU + UAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//W6SiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/X + 6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKAP/Q6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/R6SiiigAooooAKKKKACiiigAooooA + KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo + oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/Z + + DKDChangeTimeStamp + 2026-10-12 02:31:53 +0600 + DKDCreateTimeStamp + 2020-09-22 04:51:46 +0600 + DKDDisplayGraphicDetails + + AngleFormatDisplayDetails + + AngleDirection + Right + AngleForm + degrees + AngleRotation + Counter Clockwise + PrecisionAngles + 1 + + AnglesDisplaySpec + + FormDisplaySpec + Decimal + PrecisionDisplaySpec + 2 + TextAlignDisplaySpec + Left + UnitsDisplaySpec + Punctuation + + AreaForm + Natural + HelpTipDisplaySpec + + FormDisplaySpec + Decimal + PrecisionDisplaySpec + 3 + TextAlignDisplaySpec + Left + UnitsDisplaySpec + Abbreviate + + InspectingSpecIndex + 0 + LengthsDisplaySpec + + FormDisplaySpec + Decimal + PrecisionDisplaySpec + 2 + TextAlignDisplaySpec + Left + UnitsDisplaySpec + Nothing + + PercentDisplaySpec + + FormDisplaySpec + Decimal + PrecisionDisplaySpec + 1 + TextAlignDisplaySpec + Left + UnitsDisplaySpec + Punctuation + + PercentFormatDisplayDetails + + PercentForm + Percent + PrecisionPercents + 1 + + + DKDDocumentRuler + + AutoLightDarkModeulerStyle + YES + RulerBackgroundRGB + + Catalog + System + Catalog-Color + windowBackgroundColor + + RulerBorderLineWidth + 1 + RulerBorderRGB + + Catalog + System + Catalog-Color + systemGrayColor + + RulerFontIOS + + Family + .AppleSystemUIFont + Name + .AppleSystemUIFont + Size + 10 + + RulerHasBackgroundColor + YES + RulerHasBorder + YES + RulerHeightHorizontalRuler + 20 + RulerTextRGB + + Catalog + System + Catalog-Color + systemGrayColor + + RulerTickPattern + + TickPatternStyle + Quarters + + RulerWidthVerticalRuler + 18 + + DKDExportDoc + + BMPExporBackground + No (White) Background + BMPSpecification + + ColorDepthWIS + 256 Colors + ColorListNameWIS + 256 Best + HeightWIS + 64 + WidthWIS + 64 + + CSVStringEncoding + Unicode (UTF8) + DXFExportLayers + All + DXFExportRevision + AutoCADLT 2010 + ExporBackground + No (Black) Background + ExportColorTable + 256 Best + ExportCompresion + 1 + ExportContent + Just Graphics + ExportExpand + 3.944 + ExportFileExtension + png + ExportImageAntialias + YES + ExportImageInterpolation + None + ExportTransparentColor + Automatic + GIFExporBackground + White Background + GIFFDither + NO + HideExtension + NO + ICOColorTable + 256 Best + JPGColorSpace + sRGB_ColorSpace + JPGExporBackground + White Background + PDFExporBackground + No (Black) Background + PDFPagination + Single Page + PDFXMirror + NO + PDFYMirror + NO + PNGAlpha + YES + PNGColorSpace + sRGB_ColorSpace + PNGExporBackground + No (Black) Background + PNGInterlace + NO + SVGCompress + NO + SVGDOMIds + YES + SVGEmbedImages + YES + SVGFont + SVG + SVGGlyphs + System Font + SVGOverwriteImages + NO + SVGProfile + SVG 1.1 + SVGTidyFormatting + YES + TIFExporBackground + No (Black) Background + TIFFColorSpace + sRGB IEC61966-2.1 + + DKDGrids + + DynamicSnapGrid + YES + GuidesLayer + NO + MajorGrid + + GridAboveGraphics + NO + GridRGB + + BluePlus + 1 + ColorSpacePlus + DKDP3ColorSpace + GreenPlus + 0.713725 + OpacityPlus + 0.6 + RedPlus + 0.54902 + + GridSpacingX + 72 + GridSpacingY + 72 + LinkGridToRulers + NO + PrintLineWidth + 1 + PrintsGrid + NO + ShowsGrid + NO + SnapsToGrid + NO + + MinorGrid + + GridAboveGraphics + NO + GridRGB + + BluePlus + 0.87451 + ColorSpacePlus + DKDP3ColorSpace + GreenPlus + 0.854902 + OpacityPlus + 1 + RedPlus + 0.745098 + + GridSpacingX + 18 + GridSpacingY + 18 + LinkGridToRulers + NO + PrintLineWidth + 0.5 + PrintsGrid + NO + ShowsGrid + YES + SnapsToGrid + NO + + SnapDrawing + NO + SnapEnds + NO + SnapGuidelines + NO + SnapRadiusGrid + 3 + SnapSound + None + SoftSnap + YES + + DKDHideExtension + YES + DKDLayersList + + + CloakLayerGuidelines + NO + CloakLayerVertices + NO + FullLayerScale + + ArchivePrecision + 12 + ScaleOriginX + 0 + ScaleOriginY + 0 + ScalePlusDown + YES + ScalePlusToRight + YES + ScaleScale + 1 + ScaleUnits + Centimeters + + GraphicsList + + + Bounds + {{294.224122401, 275.716405174}, {108.290742591, 108.290742591}} + Class + DKDCircle + DKDLockInfo + 10 + GraphicID + C0B95773 + GraphicStyle + 8 + + + Bounds + {{503.514284115, 126.757499453}, {108.290742591, 108.290742591}} + Class + DKDCircle + DKDLockInfo + 10 + GraphicID + 6F7E8773 + GraphicStyle + 8 + + + Bounds + {{328.835052797, 128.125047075}, {78.4881890125, 78.4881890125}} + Class + DKDSquare + GraphicID + 17CBE773 + GraphicStyle + 8 + + + Class + DKDLine + GraphicID + 73CA6873 + GraphicStyle + 24 + SVGPath + M387.399317029,309.662716377 L529.441939138,204.802059204 + + + Bounds + {{305.98064227, 322.038627768}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 614BBC73 + GraphicStyle + 8 + + + Class + DKDContinuousBezier + DkBezArrow + 10 + GraphicID + C2545D73 + GraphicStyle + 40 + SVGPath + M328.408013566,330.573918646 C328.408013566,330.573918646 328.408013566,330.573918646 328.408013566,330.573918646 C330.201032664,294.273031091 287.069985825,268.786411738 281.183724024,239.232245681 C276.209392441,214.256764372 278.407101059,179.831516338 325.442174032,178.964447435 + + + AttributedText + + String + _edges + TextAttribute + 0 + + Bounds + {{324.236569375, 338.377538768}, {78.3773221901, 21.0231661209}} + Class + DKDTextArea + GraphicID + 35D9CF73 + GraphicStyle + 10 + + + Class + DKDContinuousBezier + DkBezArrow + 26 + GraphicID + 2BF2D183 + GraphicStyle + 56 + SVGPath + M389.018941427,152.095726702 C389.018941427,152.095726702 389.018941428,152.095726702 389.018941428,152.095726702 C410.302813906,152.053209509 440.736837683,163.117618759 446.549458254,183.026391843 C455.368197175,213.231403497 397.85483445,259.798877024 376.502004957,283.853746525 + + + Class + DKDContinuousBezier + DkBezArrow + 26 + GraphicID + AC19D283 + GraphicStyle + 56 + SVGPath + M388.651791845,172.116425773 C388.651791845,172.116425773 388.651791845,172.116425773 388.651791845,172.116425773 C432.427131192,172.591985962 437.539749991,148.859460237 462.719663264,143.758325424 C478.393662924,140.582969557 494.172144152,141.591955073 511.079252224,151.463000746 + + + AttributedText + + String + v1 + TextAttribute + 0 + + Bounds + {{358.242750341, 142.935407683}, {31.0306494531, 21.0231661209}} + Class + DKDTextArea + GraphicID + 38F37683 + GraphicStyle + 8 + + + AttributedText + + String + v2 + + TextAttribute + 0 + + Bounds + {{358.236818933, 163.775211565}, {30.9110329578, 22.1917445833}} + Class + DKDTextArea + GraphicID + 19EDA683 + GraphicStyle + 8 + + + AttributedText + + String + BaseVertex subclass + TextAttributes + + {0, 10} + 16 + {10, 9} + 32 + + + Bounds + {{586.619482542, 221.377689725}, {135.410183231, 32.1481976616}} + Class + DKDTextArea + GraphicID + 09BB7C83 + GraphicStyle + 10 + + + AttributedText + + String + Edge subclass + TextAttributes + + {0, 4} + 16 + {4, 9} + 32 + + + Bounds + {{329.991616456, 208.243955856}, {90.1938101669, 21.4163415091}} + Class + DKDTextArea + GraphicID + 18ED7C83 + GraphicStyle + 10 + + + Bounds + {{313.182629892, 635.149616291}, {108.290742591, 108.290742591}} + Class + DKDCircle + DKDLockInfo + 10 + GraphicID + 5943FD83 + GraphicStyle + 8 + + + Bounds + {{522.472791607, 486.19071057}, {108.290742591, 108.290742591}} + Class + DKDCircle + DKDLockInfo + 10 + GraphicID + 6943FD83 + GraphicStyle + 8 + + + Bounds + {{347.793560289, 487.558258192}, {78.4881890125, 78.4881890125}} + Class + DKDSquare + GraphicID + 7943FD83 + GraphicStyle + 8 + + + Class + DKDLine + DkBezArrow + 42 + GraphicID + 8943FD83 + GraphicStyle + 24 + SVGPath + M406.357824521,669.095927494 L548.400446629,564.235270321 + + + Bounds + {{324.939149762, 681.471838886}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 9943FD83 + GraphicStyle + 8 + + + Class + DKDContinuousBezier + DkBezArrow + 10 + GraphicID + A943FD83 + GraphicStyle + 40 + SVGPath + M347.366521058,690.007129763 C347.366521058,690.007129763 347.366521058,690.007129763 347.366521058,690.007129763 C349.159540156,653.706242208 306.028493317,628.219622855 300.142231515,598.665456798 C295.167899934,573.689975489 307.044296477,540.532783177 344.400681524,538.397658552 + + + AttributedText + + String + _edges + TextAttribute + 0 + + Bounds + {{343.195076867, 697.810749885}, {78.3773221901, 21.0231661209}} + Class + DKDTextArea + GraphicID + B943FD83 + GraphicStyle + 10 + + + Class + DKDContinuousBezier + DkBezArrow + 26 + GraphicID + C943FD83 + GraphicStyle + 56 + SVGPath + M407.977448919,511.528937819 C407.977448919,511.528937819 407.977448919,511.528937819 407.977448919,511.528937819 C429.261321398,511.486420626 459.695345175,522.550829876 465.507965746,542.45960296 C474.326704667,572.664614614 416.813341941,619.232088141 395.460512449,643.286957642 + + + Class + DKDContinuousBezier + DkBezArrow + 26 + GraphicID + D943FD83 + GraphicStyle + 56 + SVGPath + M407.610299337,531.54963689 C407.610299337,531.54963689 407.610299337,531.54963689 407.610299337,531.54963689 C451.385638684,532.025197079 456.498257482,508.292671354 481.678170755,503.191536541 C497.352170416,500.016180674 513.130651644,501.02516619 530.037759716,510.896211863 + + + AttributedText + + String + v1 + TextAttribute + 0 + + Bounds + {{377.201257832, 502.368618799}, {31.0306494531, 21.0231661209}} + Class + DKDTextArea + GraphicID + E943FD83 + GraphicStyle + 8 + + + AttributedText + + String + v2 + + TextAttribute + 0 + + Bounds + {{377.195326424, 523.208422682}, {30.9996031922, 21.0231661209}} + Class + DKDTextArea + GraphicID + F943FD83 + GraphicStyle + 8 + + + AttributedText + + String + Edge subclass + TextAttributes + + {0, 4} + 16 + {4, 9} + 32 + + + Bounds + {{348.950123948, 567.677166973}, {90.1938101669, 21.4163415091}} + Class + DKDTextArea + GraphicID + 2A43FD83 + GraphicStyle + 10 + + + Bounds + {{515.832308348, 171.943151603}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 43C31293 + GraphicStyle + 8 + + + AttributedText + + String + _edges + TextAttribute + 0 + + Bounds + {{534.088235453, 188.282062602}, {78.3773221901, 21.0231661209}} + Class + DKDTextArea + GraphicID + 53C31293 + GraphicStyle + 10 + + + Bounds + {{534.932171445, 530.358084862}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 895D5293 + GraphicStyle + 8 + + + AttributedText + + String + _edges + TextAttribute + 0 + + Bounds + {{553.18809855, 546.696995861}, {78.3773221901, 21.0231661209}} + Class + DKDTextArea + GraphicID + 995D5293 + GraphicStyle + 10 + + + Class + DKDBezier + DkBezArrow + 2 + GraphicID + AA5AC793 + GraphicStyle + 56 + SVGPath + M564.492647498,180.026681355 C564.492647498,180.026681355 564.492647498,180.026681355 564.492647498,180.026681355 C560.983309031,74.6526353337 486.944971131,55.5961066657 424.350870745,56.9055010207 C246.769676444,66.1107318805 267.032048519,152.262249804 327.980615122,151.478502838 + + + AttributedText + + String + Undirected graph + TextAttribute + 48 + + Bounds + {{450.645030196, 262.075333739}, {202.348893933, 34.1385596992}} + Class + DKDTextArea + GraphicID + 14DEC993 + GraphicStyle + 10 + + + AttributedText + + String + Directed graph + TextAttribute + 48 + + Bounds + {{466.989245863, 625.572791511}, {175.234506692, 31.2274995402}} + Class + DKDTextArea + GraphicID + 55CF0C93 + GraphicStyle + 10 + + + AttributedText + + String + v1 is always the start vertex + TextAttribute + 64 + + Bounds + {{434.664097974, 443.914155611}, {87.7526987382, 35.5524288103}} + Class + DKDTextArea + GraphicID + 0DE69F93 + GraphicStyle + 72 + + + Class + DKDLine + GraphicID + 810832A3 + GraphicStyle + 72 + SVGPath + M433.580362928,463.945782216 L398.62784885,502.411477667 + + + AttributedText + + String + order doesn't matter + TextAttribute + 64 + + Bounds + {{413.920132976, 87.3386642252}, {85.897378884, 35.5524288103}} + Class + DKDTextArea + GraphicID + 172903A3 + GraphicStyle + 72 + + + Class + DKDLine + GraphicID + 5E2CF3A3 + GraphicStyle + 72 + SVGPath + M379.904216486,143.380517666 L413.065903411,105.851078658 + + + AttributedText + + String + list of Edges + TextAttribute + 64 + + Bounds + {{433.509267317, 316.225318777}, {85.897378884, 18.9174921865}} + Class + DKDTextArea + GraphicID + 8BCA09A3 + GraphicStyle + 72 + + + Class + DKDLine + GraphicID + 7462B9A3 + GraphicStyle + 72 + SVGPath + M393.192917989,331.806383095 L432.633093519,326.734821747 + + + AttributedText + + String + BaseVertex subclass + TextAttributes + + {0, 10} + 16 + {10, 9} + 32 + + + Bounds + {{393.505995305, 736.467185297}, {135.410183231, 32.1481976616}} + Class + DKDTextArea + GraphicID + 4D9685B1 + GraphicStyle + 10 + + + AttributedText + + String + BaseVertex subclass + TextAttributes + + {0, 10} + 16 + {10, 9} + 32 + + + Bounds + {{590.256284561, 589.217001103}, {135.410183231, 32.1481976616}} + Class + DKDTextArea + GraphicID + 766E85B1 + GraphicStyle + 10 + + + AttributedText + + String + BaseVertex subclass + TextAttributes + + {0, 10} + 16 + {10, 9} + 32 + + + Bounds + {{380.443304193, 370.406352954}, {135.410183231, 32.1481976616}} + Class + DKDTextArea + GraphicID + E2F295B1 + GraphicStyle + 10 + + + Bounds + {{56.1300036422, 117.007832584}, {179.748202765, 138.79904252}} + Class + DKDGroup + GraphicID + A1C73EB1 + GroupGraphics + + + AttributedText + + String + _BaseGraph class + TextAttributes + + {0, 10} + 16 + {10, 6} + 32 + + + Bounds + {{68.0289994371, 223.658677443}, {121.597735034, 32.1481976616}} + Class + DKDTextArea + GraphicID + D962B6B1 + GraphicStyle + 10 + + + Bounds + {{139.483420339, 132.525658238}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 818AC7B1 + GraphicStyle + 8 + + + AttributedText + + String + _edgelist + TextAttribute + 80 + + Bounds + {{57.2009356021, 165.133742084}, {78.3773221901, 21.0231661209}} + Class + DKDTextArea + GraphicID + E917E8B1 + GraphicStyle + 10 + + + Bounds + {{56.1300036422, 117.007832584}, {179.748202765, 102.160261623}} + Class + DKDRectangle + GraphicID + 9CCE8AB1 + GraphicStyle + 88 + + + AttributedText + + String + _vertexlist + TextAttribute + 80 + + Bounds + {{57.1450032585, 132.577324973}, {93.7524652752, 28.0906581509}} + Class + DKDTextArea + GraphicID + C73E6BB1 + GraphicStyle + 10 + + + Bounds + {{139.273834123, 161.851595515}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + C8CA8BB1 + GraphicStyle + 8 + + + + + Class + DKDContinuousBezier + DkBezArrow + 2 + DkBezDashPattern + 0 + GraphicID + 2710EBC1 + GraphicStyle + 104 + SVGPath + M174.853229387,140.494743983 C174.853229387,140.494743983 174.853229387,140.494743983 174.853229387,140.494743983 C174.382483601,180.130470999 204.086118499,228.922086149 221.524983524,253.571835166 C235.199996386,272.901397863 244.886172162,282.39656773 296.315396389,306.197927546 + + + Class + DKDContinuousBezier + DkBezArrow + 2 + DkBezDashPattern + 16 + GraphicID + 2D4A7EC1 + GraphicStyle + 104 + SVGPath + M198.142929913,140.010933352 C198.142929913,140.010933352 199.713613947,134.251373061 199.713613947,134.251373061 C214.154610677,58.3618300063 319.60245667,28.7107386875 381.864182818,31.579042149 C463.451820359,35.3376610895 507.141790253,89.8477588426 538.37650548,129.225778752 + + + Class + DKDContinuousBezier + DkBezArrow + 2 + DkBezDashPattern + 32 + GraphicID + F9215BD1 + GraphicStyle + 120 + SVGPath + M210.844983401,170.766790232 C210.844983401,170.766790232 210.844983401,170.766790232 210.844983401,170.766790232 C213.752183867,199.117485484 250.788478524,197.804023056 258.597192548,193.417321791 C266.581638868,188.931899395 266.646650811,163.673519796 326.03417007,166.857994658 + + + Bounds + {{75.6929880143, 478.94959588}, {179.748202765, 138.799042521}} + Class + DKDGroup + GraphicID + 835D7ED1 + GraphicTransform + + coefficientM11 + 1 + coefficientM12 + 0 + coefficientM21 + 0 + coefficientM22 + 1 + coefficientTX + 0 + coefficientTY + 0 + + GroupGraphics + + + AttributedText + + String + _BaseGraph class + TextAttributes + + {0, 10} + 16 + {10, 6} + 32 + + + Bounds + {{87.5919838092, 585.600440739}, {121.597735034, 32.1481976616}} + Class + DKDTextArea + GraphicID + 235D7ED1 + GraphicStyle + 10 + + + Bounds + {{159.046404711, 494.467421534}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 335D7ED1 + GraphicStyle + 8 + + + AttributedText + + String + _edgelist + TextAttribute + 80 + + Bounds + {{76.7639199742, 527.07550538}, {78.3773221901, 21.0231661209}} + Class + DKDTextArea + GraphicID + 435D7ED1 + GraphicStyle + 10 + + + Bounds + {{75.6929880143, 478.94959588}, {179.748202765, 102.160261623}} + Class + DKDRectangle + GraphicID + 535D7ED1 + GraphicStyle + 88 + + + AttributedText + + String + _vertexlist + TextAttribute + 80 + + Bounds + {{76.7079876306, 494.519088269}, {93.7524652752, 28.0906581509}} + Class + DKDTextArea + GraphicID + 635D7ED1 + GraphicStyle + 10 + + + Bounds + {{158.836818495, 523.793358811}, {87.782600853, 16.648764451}} + Class + DKDRectangle + GraphicID + 735D7ED1 + GraphicStyle + 8 + + + + + Class + DKDContinuousBezier + DkBezArrow + 2 + DkBezDashPattern + 48 + GraphicID + 935D7ED1 + GraphicStyle + 104 + SVGPath + M194.416213759,502.436507279 C194.416213759,502.436507279 194.416213759,502.436507279 194.416213759,502.436507279 C193.945467973,542.072234295 223.649102871,590.863849445 241.087967896,615.513598462 C254.762980758,634.843161159 264.449156534,644.338331026 315.878380761,668.139690842 + + + Class + DKDContinuousBezier + DkBezArrow + 2 + DkBezDashPattern + 64 + GraphicID + A35D7ED1 + GraphicStyle + 104 + SVGPath + M217.705914285,501.952696648 C217.705914285,501.952696648 219.276598319,496.193136357 219.276598319,496.193136357 C233.717595049,420.303593302 339.165441042,390.652501983 401.42716719,393.520805445 C483.014804731,397.279424385 526.704774625,451.789522138 557.939489852,491.167542048 + + + Class + DKDContinuousBezier + DkBezArrow + 2 + DkBezDashPattern + 80 + GraphicID + B35D7ED1 + GraphicStyle + 120 + SVGPath + M230.407967773,532.708553528 C230.407967773,532.708553528 230.407967773,532.708553528 230.407967773,532.708553528 C233.315168239,561.05924878 270.351462896,559.745786352 278.16017692,555.359085087 C286.14462324,550.873662691 286.209635183,525.615283092 345.597154442,528.799757954 + + + HideDimensions + NO + LayerColorMod + + DKDOnColorMod + NO + DKDOpacityColorMod + 0.5 + DKDOutlineColorMod + NO + DKDTintColorColorMod + + ColorSpace + NSCalibratedWhiteColorSpace + Opacity + 1 + White + 0.5 + + DKDTintFractionColorMod + 0.5 + + LayerLock + NO + LayerName + Paper + LayerState + Active + OutlineLayer + NO + + + DKDPagesSpec + + BackgroundDisplay + Background + CanvasBorder + + BluePlus + 0.533333 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.533333 + OpacityPlus + 1 + RedPlus + 0.54902 + + CanvasColor + + Catalog + System + Catalog-Color + windowBackgroundColor + + CanvasMargin + 0 + DetailsDrawerWidth + 260 + DisplayAttributesBar + YES + DisplayRulers + NO + FullScreen + NO + FullScreenCanvasMargin + 141.73228 + LayersDrawerWidth + 266 + NonFullScreenCanvasMargin + 0 + NumberAcrossFirst + YES + PagesAcross + 1 + PagesDown + 1 + PagesSpecBackgroundRGB + + ColorSpace + NSCalibratedWhiteColorSpace + Opacity + 1 + White + 1 + + PagesSpecPrintBackground + NO + ShowPageBreaks + NO + SizeChecker + 8 + aCheckerColor + + BluePlus + 0.941176 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.941176 + OpacityPlus + 1 + RedPlus + 0.941176 + + bCheckerColor + + BluePlus + 0.784314 + ColorSpacePlus + DKDsRgbColorSpace + GreenPlus + 0.784314 + OpacityPlus + 1 + RedPlus + 0.784314 + + + DKDPrintInfo + + BottomMargin + 18 + Copies + 1 + FallBackPaperSizeHeight + 841.889770508 + FallBackPaperSizeWidth + 886.393700787 + FirstPage + 1 + HorizontalPagination + 2 + HorizontallyCentered + YES + LastPage + -1 + LeftMargin + 18 + MustCollate + YES + Orientation + 1 + PaperName + Custom + PaperSizeHeight + 841.889770508 + PaperSizeWidth + 886.393700787 + PreviewPageNumber + 1 + PrintAllPages + YES + PrintJobDisposition + NSPrintSpoolJob + PrintSavePath + + PrintScalingFactor + 1 + PrinterName + Brother MFC-L8690CDW series + ReversePageOrder + NO + RightMargin + 18 + TopMargin + 18 + VerticalPagination + 0 + VerticallyCentered + YES + XPrintMirror + NO + YPrintMirror + NO + + DKDSaveTimeStamp + 2026-10-12 02:33:47 +0600 + DKDTablet + + BrushDynamic + NO + BrushFit + 6 + PenMax + 25 + PenMin + 0.5 + PenPressureFactor + 0.5 + PencilDynamic + NO + PencilFit + 7 + + DKDTimeFormat + + Field 0 Include + Weekday + Field 0 Type + Long + Field 1 Include + Month + Field 1 Type + Short + Field 2 Include + Day + Field 2 Type + Number + Field 3 Include + Year + Field 3 Type + Long + Include GMT + NO + Include Title + YES + IncludeDate + YES + IncludeTime + YES + Seperator 0 + - + Seperator 1 + . + Seperator 2 + , + Seperator 3 + + Time Seperator + : + TimeAfterDate + YES + Twelve Hour Clock + YES + Used Once + YES + + DKDToolbarSelectedButtonPairs + + ColorTextToolbarItemIdentifier_0 + + HSB_10FFFF + + ColorFillToolbarItemIdentifier_0 + + HSB_0000D0 + + ColorStrokeToolbarItemIdentifier_0 + + HSB_000020 + + GradientToolbarItemIdentifier_0 + + + EndGradientColor + + Blue + 0.999991 + BluePlus + 1 + ColorSpace + NSCalibratedRGBColorSpace + ColorSpacePlus + DKDsRgbColorSpace + Green + 0.999974 + GreenPlus + 1 + Opacity + 1 + OpacityPlus + 1 + Red + 0.999999 + RedPlus + 0.999996 + + GradientFillClass + DKDHorizontalGradientFill + StartGradientColor + + Blue + 0 + BluePlus + 0 + ColorSpace + NSCalibratedRGBColorSpace + ColorSpacePlus + DKDsRgbColorSpace + Green + 0 + GreenPlus + 0 + Opacity + 1 + OpacityPlus + 1 + Red + 0 + RedPlus + 0 + + + + + DKDWindowState + + CursorMode + Nothing + DocCenter + {391.792986229, 378.287529766} + DocumentFileName + /Users/pic/Library/CloudStorage/Dropbox/code/pgraph-python/docs/source/datastructures.ezdraw + DrawersOnMainView + YES + GDetailsLayersDrawerEdgePreference + Auto + GraphicDetailsOpen + NO + LayerActiveAbove + NO + LayerOpen + NO + LayerSelect + Active Only + LayersDrawerEdgePreference + Auto + OutlineDrawing + NO + WindowLocation + 2317 -83 1142 1238 0 0 3840 1590 + ZoomPercent + 160.180664 + + GroupEdit + Fixed + NumberColorsToListInContextMenu + 12 + NumberPairColorsToListInContextMenu + 6 + + diff --git a/docs/figs/datastructures.svg b/docs/figs/datastructures.svg new file mode 100644 index 00000000..4236cdcc --- /dev/null +++ b/docs/figs/datastructures.svg @@ -0,0 +1,551 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + +_edges + + + + + +v1 + + + + +v2 + + +BaseVertex subclass + +Edge subclass + + + + + + + +_edges + + + + + +v1 + + + + +v2 + + +Edge subclass + + +_edges + + +_edges + + +Undirected graph + +Directed graph + + + +v1 is always the start vertex + + + + + +order doesn't matter + + + + + +list of Edges + + + +BaseVertex subclass + +BaseVertex subclass + +BaseVertex subclass + + +_BaseGraph class + + +_edgelist + + +_vertexlist + + + + + + + +_BaseGraph class + + +_edgelist + + +_vertexlist + + + + + + + diff --git a/docs/source/datastructures.ezdraw b/docs/source/datastructures.ezdraw deleted file mode 100644 index c25ab384..00000000 --- a/docs/source/datastructures.ezdraw +++ /dev/null @@ -1,2353 +0,0 @@ - - - - - AAAA_CurrentOSXVersion - macOS Catalina: 1677.104000 - AAAA_DKDDocumentVersion - 9.0.0 : Mobile Friendly - AAAA_EazyDrawVersion - 9.7.1 - AAAB_Build - 9088 - AAAB_Distribution - Free Market - ArchiveMatchStore - - Archive_Stores - - - Class_Store - DKDLock - Match_Hashes - - - BinIndex - 10 - BinMatches - - - CenterLock - YES - - - CenterLock - YES - - - - - BinIndex - 14 - BinMatches - - - - - - NHashBins - 16 - - - Class_Store - DKDGraphicStyle - Match_Hashes - - - BinIndex - 8 - BinMatches - - - DrawsFill - NO - DrawsLine - YES - LineCapStyle - Square - LineJoinStyle - Miter - LineRGB - - Blue - 0 - BluePlus - 0 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0 - GreenPlus - 0 - Opacity - 1 - OpacityPlus - 1 - Red - 0 - RedPlus - 0 - - LineWidth - 1 - MiterLimit - 10 - StrokePosition - Front - WindingRule - Non-Zero - - - DrawsFill - NO - DrawsLine - YES - LineCapStyle - Square - LineJoinStyle - Miter - LineRGB - - Blue - 0.027434 - BluePlus - 0 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.007121 - GreenPlus - 0.149131 - Opacity - 0.796019 - OpacityPlus - 0.796019 - Red - 0.986246 - RedPlus - 1 - - LineWidth - 6.34521484375 - MiterLimit - 10 - StrokePosition - Front - WindingRule - Non-Zero - - - DrawsFill - NO - DrawsLine - YES - LineCapStyle - Square - LineJoinStyle - Miter - LineRGB - - Blue - 0 - BluePlus - 0 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0 - GreenPlus - 0 - Opacity - 1 - OpacityPlus - 1 - Red - 0 - RedPlus - 0 - - LineWidth - 2.53369140625 - MiterLimit - 10 - StrokePosition - Front - WindingRule - Non-Zero - - - DrawsFill - NO - DrawsLine - YES - LineCapStyle - Square - LineJoinStyle - Miter - LineRGB - - Blue - 0 - BluePlus - 0 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0 - GreenPlus - 0 - Opacity - 1 - OpacityPlus - 1 - Red - 0 - RedPlus - 0 - - LineWidth - 2.5 - MiterLimit - 10 - StrokePosition - Front - WindingRule - Non-Zero - - - DrawsFill - NO - DrawsLine - YES - LineCapStyle - Square - LineJoinStyle - Miter - LineRGB - - Blue - 0.498039 - BluePlus - 0.570465 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.498039 - GreenPlus - 0.570472 - Opacity - 1 - OpacityPlus - 1 - Red - 0.498039 - RedPlus - 0.570459 - - LineWidth - 1 - MiterLimit - 10 - StrokePosition - Front - WindingRule - Non-Zero - - - - - BinIndex - 10 - BinMatches - - - DrawsFill - NO - DrawsLine - NO - LineCapStyle - Square - LineJoinStyle - Miter - LineWidth - 1 - MiterLimit - 10 - StrokePosition - Front - WindingRule - Non-Zero - - - - - NHashBins - 16 - - - Class_Store - DKDParagraph - Match_Hashes - - - BinIndex - 0 - BinMatches - - - FirstLineHeadIndent - 0 - HeadIndent - 0 - LineFragmentPadding - 5 - LineSpacing - 0 - ParagraphSpacing - 0 - ParagraphSpacingBefore - 0 - TailIndent - 0 - TextAlignment - Left - - - - - NHashBins - 16 - - - Class_Store - DKDFont - Match_Hashes - - - BinIndex - 0 - BinMatches - - - Family - Avenir - Name - Avenir-Book - Size - 12 - - - Family - Courier - Name - Courier - Size - 12 - Traits - 400 - - - Family - Avenir - Name - Avenir-Heavy - Size - 22 - Traits - 2 - - - Family - Bradley Hand - Name - BradleyHandITCTT-Bold - Size - 12 - Traits - 22 - - - - - NHashBins - 16 - - - Class_Store - DKDTextAttributes - Match_Hashes - - - BinIndex - 0 - BinMatches - - - Font - 0 - ForeColor - - Blue - 0.998189 - BluePlus - 1 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0 - GreenPlus - 0 - Opacity - 1 - OpacityPlus - 1 - Red - 0 - RedPlus - 0 - - Paragraph - 0 - - - Font - 16 - ForeColor - - ColorSpace - NSCalibratedWhiteColorSpace - Opacity - 1 - White - 0 - - Paragraph - 0 - - - Font - 16 - ForeColor - - Blue - 0.998189 - BluePlus - 1 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0 - GreenPlus - 0 - Opacity - 1 - OpacityPlus - 1 - Red - 0 - RedPlus - 0 - - Paragraph - 0 - - - Font - 32 - ForeColor - - Blue - 0.02921 - BluePlus - 0 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.238126 - GreenPlus - 0.333 - Opacity - 1 - OpacityPlus - 1 - Red - 0.987339 - RedPlus - 1 - - Paragraph - 0 - - - Font - 48 - ForeColor - - Blue - 0.498039 - BluePlus - 0.570465 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.498039 - GreenPlus - 0.570472 - Opacity - 1 - OpacityPlus - 1 - Red - 0.498039 - RedPlus - 0.570459 - - Paragraph - 0 - - - - - NHashBins - 16 - - - Class_Store - DKDArrow - Match_Hashes - - - BinIndex - 2 - BinMatches - - - ArrowAngle - 160 - ArrowArchiveVersion - 9 - ArrowBackEnd - YES - ArrowForEachSegment - NO - ArrowForm - solid - ArrowFrontEnd - NO - ArrowLineWidthFraction - 0.25 - ArrowOffset - 0 - ArrowSize - 8 - ReferenceArrow - Relief - UseCurveFillAndStroke - YES - - - - - BinIndex - 10 - BinMatches - - - ArrowAngle - 159.730010986 - ArrowArchiveVersion - 9 - ArrowBackEnd - YES - ArrowForEachSegment - NO - ArrowForm - solid - ArrowFrontEnd - NO - ArrowLineWidthFraction - 0.25 - ArrowOffset - 0 - ArrowSize - 10.7576407035 - ReferenceArrow - Relief - UseCurveFillAndStroke - YES - - - ArrowAngle - 160 - ArrowArchiveVersion - 9 - ArrowBackEnd - YES - ArrowForEachSegment - NO - ArrowForm - solid - ArrowFrontEnd - NO - ArrowLineWidthFraction - 0.25 - ArrowOffset - 0 - ArrowSize - 10.6271449412 - ReferenceArrow - Relief - UseCurveFillAndStroke - YES - - - ArrowAngle - 160 - ArrowArchiveVersion - 9 - ArrowBackEnd - YES - ArrowForEachSegment - NO - ArrowForm - solid - ArrowFrontEnd - NO - ArrowLineWidthFraction - 0.25 - ArrowOffset - 0 - ArrowSize - 37.5449518519 - ReferenceArrow - Relief - UseCurveFillAndStroke - YES - - - - - NHashBins - 16 - - - - BBB_DKDQuicklookData - - /9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEA - AAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAA - AAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAi+gAwAEAAAA - AQAAAyYAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ - 1B2M2Y8AsgTpgAmY7PhCfv/AABEIAyYCLwMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAA - AAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYT - UWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJ - SlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq - srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAf - AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncA - AQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkq - NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SV - lpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery - 8/T19vf4+fr/2wBDABwcHBwcHDAcHDBEMDAwRFxEREREXHRcXFxcXHSMdHR0dHR0jIyM - jIyMjIyoqKioqKjExMTExNzc3Nzc3Nzc3Nz/2wBDASIkJDg0OGA0NGDmnICc5ubm5ubm - 5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ub/3QAEACP/ - 2gAMAwEAAhEDEQA/AOkooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - AP/Q6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9HpKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0ukooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoqMzRL951 - H1IqM3doOsyf99CgCxRVf7Zaf89o/wDvoUourY9JUP8AwIUAT0UwSRt91gfxp9ABRRRQ - AUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU - UAFFFFABRRRQAUUUUAf/0+kooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAoopCQoyTgCgBaKzZdVtIzsQmVvRBmofP1S4/1MSwr6uefy/8ArUAbFQSXVtD/AK2R - VPoTz+VZ/wDZs83/AB93Lt7L8o/z+FWI9LsYukYJ/wBrn+dAELaxZg7Y90h/2R/jim/b - 7yT/AFFo31Y4rVVEQYQBR7cU6gDIzrMnaKP8z/jR9k1OT/WXW3/dX/8AVWvRQBkf2W7f - 625lb8cf40v9jWh++Xf6tWtRQBmDSNPH/LPP1J/xqUaZYD/lkv61eooAo/2bY/8APJaQ - 6XYHrEPzP+NX6KAMw6Pp56RkfQn/ABqP+x4V/wBVJIn0ateigDI+wX0fMN2x9nGaN2sx - dVjmHtwf6Vr0UAZA1UR8XcLxe+Mj86vQ3ltcf6mQMfTv+VWSM8GqE+m2c/JTa3qvBoAv - 0Vj/AGfUrTm2l85R/A/X86kh1SFm8q5UwSejdPzoA1KKAQRkUUAFFFFABRRRQAUUUUAF - FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH//U6SiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKZJLHCpeVgqjuaoXOoCN/s9svmzHsOg+tRR6 - c8zifUX81uy/wigBDqM1ySmnRF/9tuFpRpjznfqEpkP90cKK1lVVAVQAB0ApaAIYbeGA - bYUC/SpqKKACiiigAooooAKKKKACiiigAophkQHHPHsaTzU9/wAjQBJRUfmp7/kaPNT3 - /I0ASUVH5qe/5GjzU9/yNAElFR+anv8AkaPNT3/I0ASUVH5qe/5GjzU9/wAjQBJUU0EN - wuyZQw96XzUHJz+RqSgDFNteWB3WbebF3jbqPoavWl9DdghPlcdVPUVcrPu7BLg+bGfL - mXo4/rQBoUVl2t8/mfZL0bJh0PZvpWpQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU - UAFFFFABRRRQAUUUUAFFFFABRRRQB//V6SiiigAooooAKKKKACiiigAooooAKKKKACii - igArHuLqa6mNlYnGPvydl9h71LqFxIu20t/9bLwPYdzVq1to7SERR/ifU+tACWtpDaJs - iHJ6sepq1RRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBHHwGP8AtGm+emMnIz/XpTkA - KsD3JoEaDHsc0ARtMdwVQRkgUouEIzg4zgH1p/lJktjknNAiRcdeOmT6UADSbQDjk9qY - sw2KzZOcn6DNSsivjcM4pvlJnOKAGeepzgHg4HvTmlRThuvH60eSmMc/maURRjoO2Pwo - Aa8jKwAGQSBU1M8tNu3HAp/TigBkn+rb6GnDoKbJ/q2+hpw6CgBaKKKAKt3aRXkex+GH - 3WHUGqlldSLIbG84lXof7w9a1ao31mLqMFDtlTlG9DQBeoqjYXZuYisg2yxnDj39avUA - FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//9bpKKKK - ACiiigAooooAKKKKACiiigAooooAKKKhuWKW0rDqEY/pQBm6cPtNxNftzk7E+grYrP0p - QthEB3BP5k1oUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFRsxzsTr/IUrsR8q8selKq - hR6k9TQAyP5VPsTUf2kYzjqBj3qWPo3+8aftX0HTH4UAQPNhwq4PQfnSCSTC8dSc8/Wp - 9ieg/wD1UbEOcqOevFAEPnkEkr8oOPqelTgkjJGKb5aA5wMmn0AFFFFABRRRQAyT/Vt9 - DTh0FNk/1bfQ04dBQAtFFFABRRRQBjXyNaTrqMI4+7IB3HrWujK6h0OQwyDQ6LIhjcZD - DBFZWmu0LyafKcmI5U+qmgDXooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACi - iigAooooAKKKKAP/1+kooooAKKKKACiiigAooooAKKKKACiiigApkqeZE0f94EfnT6KA - MvSH3WSofvRkqfzzWpWKx/s6/MjcQ3HU+jVtUAFFFFABUck0UODK6pnpuIFSVWk/4+ov - 91/6UAH2yz/57R/99Cj7ZZ/89o/++hVmigCt9ss/+e0f/fQo+2Wf/PaP/voVZooArfbL - P/ntH/30KPtln/z2j/76FWaKAK32yz/57R/99CkN7ahSyyocejCrLMFGTTVU53v1P6UA - VkurReTNGWPU7hViOaKYExOr467TmpKrR/8AH1L/ALqf1oAl8vk4YjPNGw/32/T/AAqS - igCPYf77fp/hRsP99v0/wqSigCPYf77fp/hRsP8Afb9P8KkooAj2H++36f4UbD/fb9P8 - KkooAj2H++36f4UbD/fb9P8ACpKKAIzHkYLNg/SpKKKACiiigAoqCScK3lxgu/oO31Pa - meTLJzO5x/dTgfn1P6UASyTwxcSOFPoTzWNfTJHcRX0Qb5Dtc7SAVPuRitqOGKL/AFah - foKZcwi4t3hP8Q4+vagBoukxnZJj/cJ/kKUXduTguFPo3y/zxVfS5jNZpu+8nyH8P/rV - fIDDDDIoAXryKKrG1jXmEmI/7PT8un6UnnSw/wDHwMr/AH16fiOo/WgC1RSAhgGU5B7i - loAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//0OkooooAKKKKACiiigAo - oooAKKKKACiiigAooooAinhjuIjFKMq1ZMc82mMILvLw9EkHb2NbdIyq6lXAIPUGgBEd - JFDxkMp6EU6sltMaJjJYSGEn+HqppPtWpwcT24kH96M/0oA16rSf8fUX+6/9KzX1uONl - Dwuueu4Yx9PWrMV3Dd3Ebwk8K2cjHpQBpUUUUAFVzdRhioDsVODhSRn8qsVWt85mx/z0 - P8hQAfak/uSf98N/hR9qT+5J/wB8N/hUcN08romwAndu+b7u049Klt3kZpVkOdj4HGOM - A/1oAjFwpbc6ScdBsbj9KsxyLKgkTofXinkgDJ6Csm11GyWIK0oByeue5NAGtVaP/j6l - /wB1P60qXdrJ9yVD+IpI/wDj6l/3U/rQBZooooAKKKZJLHChklYKo7mgB9RyTRQjdK4U - e5xWUbu8vjtsV8uP/no39BUsWlW6nzJyZn7lz/SgAfWLJTtQs5/2R/jimf2pI3+rtZWH - 0xWokaRjbGoUegGKfQBkf2lcDraS/h/+ql/tiBTiaOSP/eWtaggEYNAFaG8tbjiKRSfT - ofyNTeYgcREjcRnHfFVJtNs5/vRgH1Xg/pXPvYanDMJowWKH5TuycUAddVVpHmcwwnCj - 7z+nsPf+VQJcy3KLEFaOQ/fyMbR6j69v/rVfRFjUIgwB0FACRxpEuyMYH8/rT6KKACii - igDIs/3GoXNt2bEg/Hr/ADrXrIuf3WqW8vZwUP8AT+da9ABRRRQBUaNoCZLcZXqyevuv - of51YjkSVBJGcg0+qkn+jP56/cb749P9r/GgC3RRRQAUUUUAFFFFABRRRQAUUUUAFFFF - ABRRRQAUUUUAf//R6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAIZb - eGYq0qhivTPvUbgC5iAGBtf+lWqrSf8AH1F/uv8A0oAs0UUUAFVrfrL/ANdD/IVZrNjj - uWklMUoQbzwVz2HfNAF5IY0d5FGGfGT9KbFBHCzMm7LdcsT/ADJqDyb3/nuv/fH/ANem - ql22dtypx1wg/wAaAL/Ws+2trZ4AXiQ8t1Uepp/k3v8Az3X/AL4/+vT7LItl3HJ5z+Zo - AifTLGTrEB9OP5VQXTSk8i2kzxbQp9Rzmt6qyEC5lJ/ur/WgCh5uqWv+uQTp6pw35f8A - 1qtW2o2tydiNtf8AutwabNqdnDwX3t6Lyf8ACs6W3udVYMYhAg/ib75oA0rrUIrY+Wo8 - yU9EXr+NVo7Ga6cXGpHOPuxj7o+tVY4pNHkMjoJYm6uB8w+tb8ciTIJIyGU9CKAHAADA - GAKWiigAooooAKKKKACmsyopdjgKMk06q1z8+yH++3P0HJ/lj8aAFtkbaZZBh5OT7DsP - wFWKKKACiiigAooooAyNX+SKKf8A55yKa16zdXXdp8ntg/qKuwNvhR/VQf0oAlooooAK - QgMCDyDS0UAVrclN1u3WPp7qen+H4VZqtL8k8cvrlD+PI/UfrVmgAooooAKKKKACiiig - AooooAKKKKACiiigAooooA//0ukooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKj - lljhTfKwVfU0ASU1mVFLucADJNZR1RpiVsYWl/2jwtRTR6xPC4cogIPyryT7d/50AbSS - JIu6Ngw9Qc1BJ/x9Rf7r/wBKwbPR7tWEjyeV/unmtwqVuIVJLYV+T1PSgC3RRRQAVWtx - kyj/AKaH+QqzVKCWJGlDMAfMPBPsKAIoEmE22QvthzySfnycj64FLZkNI8nltHkABSpU - BR07DJq558H/AD0X8xR58H/PRfzFAD3DFCEOGxwfesS3h1RoQUnVV542+/0rY8+D/nov - 5io7P/j3X8f5mgCj9i1F/wDWXZH+6v8A+qoU0uKSd1uJHl2hTyeuc1u1Wj/4+pf91P60 - ALDaW1v/AKmML79/z61YoooAQgEYPINYkitpM3nR820h+Zf7p9RW5TJY0mjaJxlWGDQA - 4EMAynIPIpaytKdxE9rIctA238O1atABRRRQAUUUUAFVj814P9iM/wDjx/8ArVZqsv8A - x+Sf9c0/m1AEszmOF5F6qpI/AVCqXZUHzV5/2P8A7Kn3X/HtL/uN/KpU+4PpQBBsuv8A - nqv/AHx/9lRsuv8Anqv/AHx/9lVmigCtsuv+eq/98f8A2VGy6/56r/3x/wDZVZooAzL2 - O5NpLukUjaSRtx0/GiyW5a0iKyKBtGBtz/Wr08ZlheIcb1I/MUy0ha3t0hY5KjGRQAmy - 6/56r/3x/wDZUbLr/nqv/fH/ANlVmigCtsuv+eq/98f/AGVRTG7hheXzFOxS2NnXAz/e - q9Va8/485v8Arm38jQAXnFuz/wBzDf8AfJz/AEqzVe7x9kmz/cb+VTjoM0ALRRRQAUUU - UAFFFFABRRRQAUUUUAFFFFABRRRQB//T6SiiigAooooAKKKKACiiigAooooAKKKKACii - o5pVhiaV+ijJoArXl6tooAG+R+FUd6qw6e8zC41E737J/CtGnQtMx1G4HzyfcH91a16A - EVQoCqMAdhS0UUAFVpP+PqL/AHX/AKVZqtJ/x9Rf7r/0oAs0UUUAFMMcbHLKCfcU+igC - Pyov7i/kKPKi/uL+QqSigCPyov7i/kKkAAGB0oooAKrR/wDH1L/up/WrNVo/+PqX/dT+ - tAFmiiigAooooAyLXjVbpR0IU/oK16yNN/ez3N12d9oPsK16ACiiigAooooAKrfdvP8A - fj/9BP8A9erNVrj5DHN/cbB+jcfzwaAJZU8yJ4843KRn61CBeAAZj4+tWqKAK3+mf9M/ - 1o/0z/pn+tWaKAK3+mf9M/1o/wBM/wCmf60+483yH8j7+07frWYhuRHJsLkYXghs9fmx - u749KALwa5YkK0RI4OM8U7/TP+mf61nJ5gMpjEu0svJBDFcc4J9/xpoNwYQVL7fMbcSS - WAHAHy8/lQBp/wCmf9M/1o/0z/pn+tZshufKjBaQnDdmHfjkc5+tbEefLXcCDgZz1oAh - /wBM/wCmf61HLHdzRNExQBwVJ578VdooArXnNs6/3/l/76OP61ZqtN880UXod5+i9P1I - qzQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/U6SiiigAooooAKKKKACiiigAo - oooAKKKKACsjVCZmhsV/5atlvoK16yP9ZrPP/LKL9T/+ugDWACgKvAHApaKKACiiigAq - rMWSeOQKzABgdvPXH+FWqKAK32n/AKZSflR9p/6ZSflSzXAhdUKk7gSDkDp9SPWmS3kc - UW8/eK7tp4P49cVSg3sA77T/ANMpPyo+0/8ATKT8qV7qCM7XbB44wT16dKd9oh8zy93O - cdDjPpnpmjlfYBn2n/plJ+VH2n/plJ+VSvLHGwVjyfQE/wAqa8yiMsp5OcfUZ/wpcrAZ - 9p/6ZSflR9p/6ZSflSx3MTqCWx8u45BAx3Iz1pPPDOgTOCTnII7Z70+RgH2n/plJ+VJD - uaaSQqVBCgZ46Zp4uYSC2TxjjBzz04xmlE8TMFBOT7Hv6+n40cr7ATUUUVIBVHUbj7Na - sy/eb5V+pq9WLH/xMb/zesNvwvoW9aANCyt/stqkPcDJ+p61aoooAKKKKACiiigApros - iNG3IYYP406igCC3dmTZJ99Dtb/H8RzTpZkhGW69hVG5nEcvmQnLYw3oR2/EVWjjluW3 - dfUmolPojeFL7UthzzzTSDb2PAFa4BePEnBI5wcfqKZDAkIwvXuampxTW5NSaekURLAi - NuBbI9WYj8ialooqjIayK6lHGQeooREjUJGAqjoBTqKACiiigAo6cmiqkp89/sy/dH+s - Pt/d+p7+1AC2/wC8LXJ/j4X/AHR0/PrVqjpwKKACiiigAooooAKKKKACiiigAooooAKK - KKACiiigD//V6SiiigAooooAKKKKACiiigAooooAKKKKACsi3/5C9z/ur/IVr1kRfLrM - o/vRg/lgUAa9FFFABRRRQAUUUUAVZoHlkWRWUBQRhlz1x7j0qN7SVlKiX76BHJXOcd+t - XqKtTaCxnSwS+YqoSQzIzcDGVxnvnt0p4sgsu8EY3b+Rk5znrn+lXqKftHsKxWngMzKy - sFI745/A5FMNq5ON42gsQMc/NnvntmrlFSptaDKZtXdQkr5CqVGBj8TyaeYZXwXcZGcF - RjqMepqzRT52BQWzIDZKksFH3ePlz757+tOW1dWRt/K4ycHJ9s56fXNXaKPaSFYKKKp3 - t2tpFu6u3Cr6moGVtQuHYixtv9bL1P8AdXuav28CW0Kwx9FH5n1qpYWjQhp7g5ml5Y+n - tWjQAUUUUAFFFFABRRUM06Qj5uT2FDdhpNuyJWZUG5jgCsqe7aT5I+F/U1C8kty+OvoB - WhBaLH80nLfoKyu5aI6VGNPWW5Vgs2f55OF9O5q0Ua2JeIFoz95R1HuP6irlFXGKRhOo - 5bjUdJFDocg9xTqrvB8xkgbY569wfqP69ab9oMfFwhT/AGhyv59vxqiC1RTVdHG5CGHq - OadQAUUUUAFFV2uYlOxTvb+6vJ/H0/Gm7J5/9afLT+6p5P1Pb8PzoAHlaRjDbnkcM/Zf - 8T/k1PHGsSBE6fqT6mq0xFt5bKdkQyCABgccGoDPcxhI/vOV3kkZ/DjFWqbauK5p0VS8 - 65dkVAo3pu5ycHj0PPWlEkwuWRiuwKp/Mn/CjkYy5RWfHdTSSD5RsLFfcY75z+mKdDcT - STmJ9vy5yQDz9Oe3em6bQrl6gkDk1UkmkSYLgBOOcZyT2yDx+VQzzloR0+aMP+o/SkoN - 2C5o0ZGcVSaeWNvLbaWO3BAIAyccjP8A+ukd3jkLOV3BDyAcdRjijkYXL1FZ32m42H5R - lWKk47YB6Z9/Wpo53eba2Arfd468Z6/0xQ6bC5boooqBhRRRQAUUUUAf/9bpKKKKACii - igAooooAKKKKACiiigAooooAKyLj91q0EnaRSn5Vr1latG3kLcR/ehYP+FAGrRTIpFlj - WVOjDI/Gn0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFRTTRwRmWU4UUANuLiO1iM0 - pwB+p9BWfZ28lxL/AGhdj5j9xf7oplvDJqEwvLoYjX/Vof5mtqgAooooAKKKKACimu6x - ruc4FZM900vypwv6mplJI0hTctizPeBfki5Pr2qnFDLcNu7dyangsy3zy8D0rTACjAGA - KhRctWaucYK0NyOKFIVwg+pqWiitTnbb1YUUUUCCiiigCBraBzuKAN6jg/mMGm/Zsfdl - kH45/nmrNFAFb7O/eaQ/98/0FH2SE/fy/wDvMSPyJxVmigBFVUG1AAB2FLRRQAjKrjaw - BB7GmvFHKMSKGx6jNPop3AiMEBYOY13DocDPHSlaKJ2DOoYjoSM4qSii7Aj8mHf5mxd3 - rgZpFghUgqijb0wBxn0qWii7AYYoy4kKgsOhxzTRBCM4ReevAqWii7AjEMKqUVFAPUYG - DQIYQu0IuOmMVJRRdgReRAV2eWuM5xgdacIolbeqgN645p9FF2AUUUUgCiiigAooooA/ - /9fpKKKKACiiigAooooAKKKKACiiigAooooAKayq6lGGQRg06igDH052tpX02U/c+ZD6 - qa2KzNRtnkVbm34mi5HuPSrVpcpdwLMn0I9D6UAWaKKKACiiigAooooAKKKKACiiigAo - oqOWWOGMyynaq9TQASyxwRmWU4VeprIiik1SUXNwNsCn5EPf3NJHHJqkguLgFYFPyJ/e - 9zW4AAMCgAooooAKKKKACq89wkIweW9Krz3gHyQ/n/hVWG3knO48DuTWbn0R0Qpac09h - rNNcv6n07CtGC1WL5m5b+VTxxJEu1BUlOMOrJnVvpHYKKKKsxCiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/Q6Sii - igAooooAKKKKACiiigAooooAKKKKACiiigArEnVtNuftcQzDIcSKOx9a26a6LIpRxkEY - IoAFZXUOhyCMg06sS3dtNuPscxzDIcxsex9DW3QAUUVA9zErFFy7D+FRk/j6fjQBPRVb - fdP91FQf7RyfyH+NGy6PWVR9E/xNAFmiq2y6HSVT9U/wIo3XSfeRXH+ycH8j/jQBZoqu - tzGWCPmNj2YY/I9D+Bpt7cfZrZ5e4GB9T0oAmlljhjMsp2qvU1jxxyapILi4BWBT8if3 - vc1QsWl1KZI7ss8cYyOOCf8AaNdWAAMCgAAAGBRRRQAUUVWnuUh4HLelJuw4xbdkTSSJ - Gu5zgVkz3LzHavC+nrUf766k9T+grUgtkh5PLetZ3ctjpSjT1erK0Fnn55vy/wAatzyi - 3jDBcjOMdKnqlf8A+pH+9/Q05Lli7Gak5yXMWnfZGXxnAzSRSebGJMYz2pk5H2dj/s1R - LSLbwhCVJJ/nRKdmKMOZGrRWevmw3KxlywYd6QebczOA5RUOOKOfyD2fnoTCd/MkXGQg - 4x1NWInMiByNpPY1mx70afJywU80skkgtI2DHJPXP1qVPqy5U09EalFZsxlto+XLM56+ - n0piSskq7GdgTg7qr2lnZkqldXTNWmSSJEu5zgVS/e3MrqHKKhxxTLqJ0hBdy2OMUObs - 2kCpq6TZp0VnStJbwgBySx6nsKjSVklXYzsCcHdQ6lnZgqTaumatV45med4yBhelVh5t - zM4DlFQ44pbUMLiQOckd6XPdqwciSdzQoqvcyNFEWXr0qpJHPHD5vmknjI+tVKduhMad - +pp0VmO0rNCiOQWUZqWX9zEEaVsk9R1NLn8h+z21L1FZsEjCYx5YqR/F1psCTToWMpGD - gUKpfZDdK27L1xI0URdcZHrUiMWRWPcA1mmR5LRw/JUgZpGE0KRy7yc447VPtNbj9lpb - qatFFFbGAUUUUAFFFFABRRRQAUUUUAFFFFAH/9HpKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigCvdW0d3CYZO/Q+h9az7O9aINaXn+tj4HcsO2PU1qSyrEm5uewA6k+g - rKu7Od1+2Kf9ITkAdMD+Ef55oAv+XLPzMSif3AefxI/kP1qwiJGuyMBQOwqC0uUu4FmT - v1HofSrNABRRRQAUUUUAIyq6lXAIPUGs25sPMChSWjU58sng/Q9R/KtOigCC3aEpthXZ - t4K4wQfcVPUE0JYiWI7ZF6HsR6H2p0MolXONrA4ZT2NAEtBIAyajklSJdzmsma4knO0c - DsBUykka06TkWJ7z+CH8/wDCoILZ5jubhfX1qxBZ4+eb8v8AGtGpUW9ZGkqiguWAyONI - l2oMCn0UVoczdwpkkayoUboafRQ0NO2qKH2NyNjSEoO1TyW4cIFO0IasUVCginUkyB4d - 8yy5xt7VG9qTIZInKE9at0U3BMFNoppabA/zZ3jHSla13QrFu+6c5xVuilyIPaS3uRTQ - rMm1uPQ1EkEgcO8hbHQVaopuKvcSm0rFR7UlzJE5QnrTmt98HksxJHc1Zoo5EP2ktCr9 - mLReXK245yD6UJBIHDvIWx0FWqKORB7RlR7UmQyROUJ606C28ly27dmrNFHIr3D2krWI - bjy/Kbzfu+1ZcgTy9qyl/RcVtEAjBqMRRKdyqAfpUzhcqnU5UQrbndE5OCigYp08HmlW - VtrL0NWKKrlVrE87vcqLbMsnnM+4454qpbQPJGWRyuTgitakVVQYUAD2qXTVylVdmVvs - qiAwqcZ5JpZbfzI1j3Y245x6VZoquRE88t7hRRRVEBRRRQAUUUUAFFFFABRRRQAUUUUA - f//S6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqtcklBCpwZTt+g7n8qAGxfv - 5PtJ+6OI/p3b8e3t9at0gAUBV4A4FLQBi/8AIP1DI4huPyDVtVTv7f7VavGPvdV+opNP - uPtNqkh+90b6igC7RRRQAUUUUAFFFFABWddTLDKJIjl8YYdiPf3FSXkksagJwD1NVILV - pfmfhf1NRKTvZG8KatzSehEqy3T56n17CtWC3SEZ6t6021Aj32/9w8e6nkf4fhVqiMLa - sVSq5aLYKKKKsxCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigA - ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9PpKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACqw+e7J7Rpj8WP/ANarNVoOZJm/28fkooAs0VVuhkRpkgM4 - BwSOOe4pfskPq/8A323+NAFmsex/cXtza9sh1/Hr/Sr/ANkh9X/77b/GstoI11dYxuw0 - WfvNnqe+c9qAN2iq32SH1f8A77b/ABo+yQ+r/wDfbf40AWaKrfZIfV/++2/xo+yQ+r/9 - 9t/jQBZoqt9kh9X/AO+2/wAaPskPq/8A323+NAFgqGGGGRS1W+yQ+r/99t/jUTxLDLEY - y3L4OWYjG09iaAJX+S6jf++Ch+o5H8jVmq1zwI29JF/Xj+tWaACikLKOpApnmx9mB+nN - ACuWBULjk45+lJ+8HVl/L/69NLBnTAPXuMdjSOSHJAJ+XigBw8wjIZcH2P8AjS4l9V/I - /wCNRGR8rw2PYU+HcqkNnj2oAcfNAyWX8v8A69IpkYZVlI+h/wAagJldOTwcDkepqWZW - MYHuM4/+tQA/Evqv5H/GgeYeQy/l/wDXqD51UBQRk9hg/rmkHmhQgyDj075/lQBYxL6r - +R/xoxL6r+R/xqv5khIOcfLnGM5JFTxMWXnP40ALiX1X8j/jQpbcVbBxjp71JUY/1jfQ - f1oAkooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig - AooooA//1OkooooAKKKKACiiigAooooAKKKKACiiigAooooAKrW/35h6SfzUGrNVk+W6 - kX+8FYfyP8hQAXHWL/roP5GrNRyxLKoDEjByCOoNRfZv+msn5/8A1qALNVmtUa6W7JO5 - V247UfZv+msn5/8A1qPs3/TWT8//AK1AFmiqjwrGu6SZ1A7lgBTWSNU8wzttPQ7hg/Sg - C7RWeoiaNZTM6hum5gKdthG3Nw3zfd+cc/SgC9RVJkiVxG07Bj0BYZ/KpPs3/TWT8/8A - 61AFmq1x96EDg+Z/7KaPs3/TWT8//rUotlDq7O7bTkAnjOMUAR3CnagLE5dfT1qx5Sd8 - n6kmopvmnhj9CXP0Ax/MirNADBGg6KB+FPoooAjk4KnnAPYZ7GjzV9G/75P+FSUUAR+a - vo3/AHyf8KPNX0b/AL5P+FSUUARF0YYIb/vk/wCFL5q+jf8AfJ/wqSigCPzV9G/75P8A - hTWMb9Q34BhU1FAEYkQDADf98n/CjzV9G/75P+FSUUAR+avo3/fJ/wAKRDudmAOMDqMe - vrUtFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFF - FABRRRQB/9XpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACq0/wC7eOfsp2t9G/8A - r4qzTXRZEKOMhhg/jQA6iq9u7YMMhy8fB9x2P4/zqxQAUx5I4xmRgo9zin0UAUJ1W72G - 3kVjG27Ab2x1GcVGtpKgRlVGILZViSPm75x1/CtOigDFkt5ILdAwUkKyYwWHPPGBTjYy - svYhkVSCxGMD2Bz+lbFFAGc9tM1x5i7Qu5TnJ5xjqpBGfQ5rRoooAKKKgnkZFCx/fc4X - 6+v4daAGxfvJ5Juy/Iv4df14/CrNMijWKNY16KMU+gAooooAKKKKACiiigAooooAKKKK - ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKAP/W6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooArzRsSJov - vr+o7g1JFKkyb1+hB6gjsakqvJC2/wA6EhX756MPQ/40AWKKgjnVz5bDY46qev4eoqeg - AooooAKKKKACiioZZ0jO37znoo6n/PrQA+SRIkLucAf5xUUKOWM8ww7DAH90en19aSOJ - 2cTT4LD7qjov+J96s0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF - FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9fp - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAjkijlXbIM+nt9DUOy4i/ - 1bCQej9f++h/UVaooArfaCv+tjdfoNw/8dzR9stu7gfXj+dWaKAK32y27SA/Tn+VH2nd - /qo3b8No/wDHsVZooAq7bmX77CMei8n8z/hTWUWzJ5eArNhyck57ZOfwq5TJI0lQxyDK - nqKcXrqBQ+2uqruAZmBbuPl7evJqc3MhKCOPPmLuGTjGMdePepXgikwSCCBgEEg49OKQ - 20RZW+YFRgYYjj8605odhDPOm+0eVs42g9fWoxegzeWFyu7bnvn6Y6fjViSCOVgzg5Ax - wSOPfFH2eLf5gBBznqcZ9cdKSceqAhjuXecwFACv3ueg7HpzmpGnKziErgHuTjP045/O - hbWFGDKDlSSDk9T1709oY2cO2cj3OOPbpQ3G+wFWa43W+QMF4y/XpjH+NSNcvGdsiYYg - bQDnOSB6DHJFP+ywc5UnIK9T0PUDmlFtDgggnIxySePbPSneAETSOsoaQBcI5xnjgr3w - KZ9tO0nZyDg8nHTOc4/pVj7PERggnII5JJ5xn+VJ9miwR83PJO45PbrmhOHVAItxum8r - AA7EnrxnjjkfjVmoRBErBwDlegycDjHTp0qaolboMKKKKkAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0OkooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/R6SiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooA/9LpKKKKACiiigAooooAKKKiZQ0gB6YP9KAJ - aKiKRAEnoKRVhYkLzjrQBNRUGyMZLYAHvS7Idu/t1zQBNRVf9ycY5Bzz9KeUiAz2+tAE - tFQDyCSPTrTikQ69uetAEtFQEQhDJ1A96MQ5x7A5z60AT0VFsi3bO/XFIyKpUrxzQBNR - RVa7JEBwSMlRwcHlgOooAs0VW+yResn/AH8f/Gj7JF6yf9/H/wAaALNFVvskXrJ/38f/ - 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 - a/QUSJ5kbJ03Aj86gUXaqFHl8DHegC1VDULaS6iRI8ZVwxz6DNTf6Z/0z/Wj/TP+mf60 - AWaKrf6Z/wBM/wBaP9M/6Z/rQBZoqoHuWJCmIkcHBPFO/wBM/wCmf60AWaKrf6Z/0z/W - j/TP+mf60AWagu/+PWX/AHG/lTf9M/6Z/rTJUu5Y2jPlgMCM896ALP8Ayz59Kjtf+PaL - P9xf5UlyTHbOR1CkD644qZFCIEHYYoAdRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQ - AUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//Q - 6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKr3QPlb15MZDj8Ov5irFFACA - hgGHIPNLVW3/AHTNbH+Hlf8AdP8Ah0q1QAUUUhAYFT39OKAIrjzfIfyPv7Tt+tZiG5Ec - mwuRheCGz1+bG7vj0rTWBFYMC3Hq7H9CamoAxk8wCYxCXBZTkghivfGf/wBdMBnKHaZN - nmENksWAA4Hy8/lW2yq6lWGQeopEjSJdkahQOwoAyXNx5MSlnJ+bJww78Z28g+metacG - 7yU3gg7RnPJzjvUtFABRRRQBWuPneOEfxNuP0Xn+eKs1Vt/3jNcno3C/7o/x6/lVqgAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooA/9HpKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAgnjZwHj4kTlf6g+xp8UqypuXg9CD1B9DUlVpImD+fBw/Qg9GA7H+ - hoAs0VFFMsuQMhh1U9RUtABRRRQAUUUUAFFFFABVSU/aHNun3R/rD/7L+Pf2oaVpyY7c - 4HRn7D2Hqf0FWI40iQIgwBQA8AAYFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRR - RQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB/ - /9LpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAIpYUlwTkMOj - Dgj8ai3XMX3181fVeG/EHj8vyq1RQBAt1Ax27gG/utwfyNT01kVxtcBh6HmoPsdt/CgX - /d4/lQBZprukY3OwUepOKg+yQf7R+rN/jTltrdDuWNQfXHP50AN+1K3ECmT3HT8zxUMy - yeWZbrlV5KJ0x7k4z+lX6QgMCp5B4przAr+fHGShUqqAc8Y56AYpxuoAnmFsLnaSQeD7 - +n41B9iHk+UXydwbcR6cDP4UfZHERjVlBLBj8vHGD6+3rWlodxErXcKhSScMcdDTnuYY - wCx6jPAJ49eKJInkRfmAdTnOOM/TP9agks2kYSMwLbdpyvHXsMj1pJQ6gTPdQISGboAe - hPB4FSPKkYBcnnpgEn8hVWS0dypDgeWAE+XoeMnrzU08JmQLkAjuR/LkEUWjpqBJ5seC - c9CAfqcY/nUMNyj/ACsfm3EdDjgnjPTNIbZzwH4JVjkZJK47574oW2cAIzgoG3Yxg9c4 - zn1otG24CvcoVHlHJ3KOhwQWAOD3p5uYQSCcbQScgjp1x6/hTFt3CLEXBVNuOOflIPPP - tUf2NtxZmByGHK5zn15p2h3AnNxEACSec9jnjrkY4qeqBs3KBd4yCcHByM/3ecir9TJR - 6DCiiioAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigD/0+kooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - AP/U6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9XpKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/1ukooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKAP/X6SiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooA/9DpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA - KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK - ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igD/0ekooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii - igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo - oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/Z - - DKDChangeTimeStamp - 2020-09-21 23:11:11 +0000 - DKDCreateTimeStamp - 2020-09-21 22:51:46 +0000 - DKDDisplayGraphicDetails - - AngleFormatDisplayDetails - - AngleDirection - Right - AngleForm - degrees - AngleRotation - Counter Clockwise - PrecisionAngles - 1 - - AnglesDisplaySpec - - FormDisplaySpec - Decimal - PrecisionDisplaySpec - 2 - TextAlignDisplaySpec - Left - UnitsDisplaySpec - Punctuation - - AreaForm - Natural - HelpTipDisplaySpec - - FormDisplaySpec - Decimal - PrecisionDisplaySpec - 3 - TextAlignDisplaySpec - Left - UnitsDisplaySpec - Abbreviate - - InspectingSpecIndex - 0 - LengthsDisplaySpec - - FormDisplaySpec - Decimal - PrecisionDisplaySpec - 2 - TextAlignDisplaySpec - Left - UnitsDisplaySpec - Nothing - - PercentDisplaySpec - - FormDisplaySpec - Decimal - PrecisionDisplaySpec - 1 - TextAlignDisplaySpec - Left - UnitsDisplaySpec - Punctuation - - PercentFormatDisplayDetails - - PercentForm - Percent - PrecisionPercents - 1 - - - DKDGrids - - DynamicSnapGrid - YES - GuidesLayer - NO - MajorGrid - - GridAboveGraphics - NO - GridRGB - - Blue - 1 - BluePlus - 1 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDP3ColorSpace - Green - 0.651064 - GreenPlus - 0.713725 - Opacity - 0.6 - OpacityPlus - 0.6 - Red - 0.432559 - RedPlus - 0.54902 - - GridSpacingX - 72 - GridSpacingY - 72 - LinkGridToRulers - NO - PrintLineWidth - 1 - PrintsGrid - NO - ShowsGrid - NO - SnapsToGrid - NO - - MinorGrid - - GridAboveGraphics - NO - GridRGB - - Blue - 0.848787 - BluePlus - 0.87451 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDP3ColorSpace - Green - 0.828351 - GreenPlus - 0.854902 - Opacity - 1 - OpacityPlus - 1 - Red - 0.664186 - RedPlus - 0.745098 - - GridSpacingX - 18 - GridSpacingY - 18 - LinkGridToRulers - NO - PrintLineWidth - 0.5 - PrintsGrid - NO - ShowsGrid - YES - SnapsToGrid - NO - - SnapDrawing - NO - SnapEnds - NO - SnapGuidelines - NO - SnapRadiusGrid - 3 - SnapSound - None - SoftSnap - YES - - DKDHideExtension - YES - DKDLayersList - - - CloakLayerGuidelines - NO - CloakLayerVertices - NO - FullLayerScale - - ArchivePrecision - 12 - ScaleOriginX - 0 - ScaleOriginY - 0 - ScalePlusDown - YES - ScalePlusToRight - YES - ScaleScale - 1 - ScaleUnits - Centimeters - - GraphicsList - - - AttributedText - - String - Vertex subclass - TextAttribute - 0 - - Bounds - {{584.594428198, 92.5444270325}, {111.11092077, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 9852BB83 - GraphicStyle - 10 - - - Bounds - {{122.115430323, 283.075572388}, {108.290742591, 108.290742591}} - Class - DKDCircle - DKDLockInfo - 10 - GraphicID - C0B95773 - GraphicStyle - 8 - - - Bounds - {{331.405592037, 134.116666667}, {108.290742591, 108.290742591}} - Class - DKDCircle - DKDLockInfo - 26 - GraphicID - 6F7E8773 - GraphicStyle - 8 - - - Bounds - {{156.726360719, 135.484214289}, {78.4881890125, 78.4881890125}} - Class - DKDSquare - DKDLockInfo - 14 - GraphicID - 17CBE773 - GraphicStyle - 8 - - - Class - DKDLine - DKDLockInfo - 14 - GraphicID - 73CA6873 - GraphicStyle - 24 - SVGPath - M215.290624951,317.021883591 L357.33324706,212.161226418 - - - Bounds - {{133.871950192, 329.397794982}, {87.782600853, 16.648764451}} - Class - DKDRectangle - DKDLockInfo - 14 - GraphicID - 614BBC73 - GraphicStyle - 8 - - - Class - DKDContinuousBezier - DKDLockInfo - 14 - DkBezArrow - 10 - GraphicID - C2545D73 - GraphicStyle - 40 - SVGPath - M156.299321488,337.93308586 C156.299321488,337.93308586 156.299321488,337.93308586 156.299321488,337.93308586 C158.092340586,301.632198305 114.961293747,276.145578952 109.075031946,246.591412895 C104.100700363,221.615931586 106.298408981,187.190683552 153.333481954,186.323614649 - - - AttributedText - - String - _edges - TextAttribute - 16 - - Bounds - {{152.127877297, 345.736705982}, {78.3773221901, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 35D9CF73 - GraphicStyle - 10 - - - Class - DKDContinuousBezier - DKDLockInfo - 14 - DkBezArrow - 26 - GraphicID - 2BF2D183 - GraphicStyle - 56 - SVGPath - M216.910249349,159.454893916 C216.910249349,159.454893916 216.91024935,159.454893916 216.91024935,159.454893916 C238.194121828,159.412376723 268.628145605,170.476785973 274.440766176,190.385559057 C283.259505097,220.590570711 225.746142372,267.158044238 204.393312879,291.212913739 - - - Class - DKDContinuousBezier - DKDLockInfo - 14 - DkBezArrow - 26 - GraphicID - AC19D283 - GraphicStyle - 56 - SVGPath - M216.543099767,179.475592987 C216.543099767,179.475592987 216.543099767,179.475592987 216.543099767,179.475592987 C260.318439114,179.951153176 265.431057913,156.218627451 290.610971186,151.117492638 C306.284970846,147.942136771 322.063452074,148.951122287 338.970560146,158.82216796 - - - AttributedText - - String - v1 - TextAttribute - 16 - - Bounds - {{186.134058263, 150.294574897}, {31.0306494531, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 38F37683 - GraphicStyle - 8 - - - AttributedText - - String - v2 - - TextAttribute - 16 - - Bounds - {{186.128126855, 171.134378779}, {30.9110329578, 20.8018422269}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 19EDA683 - GraphicStyle - 8 - - - AttributedText - - String - Vertex subclass - TextAttributes - - {0, 6} - 32 - {6, 9} - 0 - - - Bounds - {{198.775981826, 381.234341154}, {111.11092077, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 5B25DB83 - GraphicStyle - 10 - - - AttributedText - - String - Vertex subclass - TextAttributes - - {0, 6} - 32 - {6, 9} - 0 - - - Bounds - {{414.510790464, 228.736856939}, {111.11092077, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 09BB7C83 - GraphicStyle - 10 - - - AttributedText - - String - Edge subclass - TextAttributes - - {0, 4} - 32 - {4, 9} - 0 - - - Bounds - {{157.882924378, 215.60312307}, {90.1938101669, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 18ED7C83 - GraphicStyle - 10 - - - Bounds - {{140.115430322, 589.075572388}, {108.290742591, 108.290742591}} - Class - DKDCircle - DKDLockInfo - 26 - GraphicID - 5943FD83 - GraphicStyle - 8 - - - Bounds - {{349.405592037, 440.116666667}, {108.290742591, 108.290742591}} - Class - DKDCircle - DKDLockInfo - 26 - GraphicID - 6943FD83 - GraphicStyle - 8 - - - Bounds - {{174.726360719, 441.484214289}, {78.4881890125, 78.4881890125}} - Class - DKDSquare - DKDLockInfo - 14 - GraphicID - 7943FD83 - GraphicStyle - 8 - - - Class - DKDLine - DKDLockInfo - 14 - DkBezArrow - 42 - GraphicID - 8943FD83 - GraphicStyle - 24 - SVGPath - M233.290624951,623.021883591 L375.333247059,518.161226418 - - - Bounds - {{151.871950192, 635.397794983}, {87.782600853, 16.648764451}} - Class - DKDRectangle - DKDLockInfo - 14 - GraphicID - 9943FD83 - GraphicStyle - 8 - - - Class - DKDContinuousBezier - DKDLockInfo - 14 - DkBezArrow - 10 - GraphicID - A943FD83 - GraphicStyle - 40 - SVGPath - M174.299321488,643.93308586 C174.299321488,643.93308586 174.299321488,643.93308586 174.299321488,643.93308586 C176.092340586,607.632198305 132.961293747,582.145578952 127.075031945,552.591412895 C122.100700364,527.615931586 133.977096907,494.458739274 171.333481954,492.323614649 - - - AttributedText - - String - _edges - TextAttribute - 16 - - Bounds - {{170.127877297, 651.736705982}, {78.3773221901, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - B943FD83 - GraphicStyle - 10 - - - Class - DKDContinuousBezier - DKDLockInfo - 14 - DkBezArrow - 26 - GraphicID - C943FD83 - GraphicStyle - 56 - SVGPath - M234.910249349,465.454893916 C234.910249349,465.454893916 234.910249349,465.454893916 234.910249349,465.454893916 C256.194121828,465.412376723 286.628145605,476.476785973 292.440766176,496.385559057 C301.259505097,526.590570711 243.746142371,573.158044238 222.393312879,597.212913739 - - - Class - DKDContinuousBezier - DKDLockInfo - 14 - DkBezArrow - 26 - GraphicID - D943FD83 - GraphicStyle - 56 - SVGPath - M234.543099767,485.475592987 C234.543099767,485.475592987 234.543099767,485.475592987 234.543099767,485.475592987 C278.318439114,485.951153176 283.431057912,462.218627451 308.610971185,457.117492638 C324.284970846,453.942136771 340.063452074,454.951122287 356.970560146,464.82216796 - - - AttributedText - - String - v1 - TextAttribute - 16 - - Bounds - {{204.134058262, 456.294574896}, {31.0306494531, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - E943FD83 - GraphicStyle - 8 - - - AttributedText - - String - v2 - - TextAttribute - 16 - - Bounds - {{204.128126854, 477.134378779}, {30.9996031922, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - F943FD83 - GraphicStyle - 8 - - - AttributedText - - String - Vertex subclass - TextAttributes - - {0, 6} - 32 - {6, 9} - 0 - - - Bounds - {{216.775981825, 687.234341154}, {111.11092077, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 0A43FD83 - GraphicStyle - 10 - - - AttributedText - - String - Vertex subclass - TextAttributes - - {0, 6} - 32 - {6, 9} - 0 - - - Bounds - {{432.510790464, 534.736856939}, {111.11092077, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 1A43FD83 - GraphicStyle - 10 - - - AttributedText - - String - Edge subclass - TextAttributes - - {0, 4} - 32 - {4, 9} - 0 - - - Bounds - {{175.882924378, 521.60312307}, {90.1938101669, 21.4163415091}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 2A43FD83 - GraphicStyle - 10 - - - Bounds - {{343.72361627, 179.302318817}, {87.782600853, 16.648764451}} - Class - DKDRectangle - DKDLockInfo - 14 - GraphicID - 43C31293 - GraphicStyle - 8 - - - AttributedText - - String - _edges - TextAttribute - 16 - - Bounds - {{361.979543375, 195.641229816}, {78.3773221901, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 53C31293 - GraphicStyle - 10 - - - Bounds - {{361.864971875, 484.284040959}, {87.782600853, 16.648764451}} - Class - DKDRectangle - DKDLockInfo - 14 - GraphicID - 895D5293 - GraphicStyle - 8 - - - AttributedText - - String - _edges - TextAttribute - 16 - - Bounds - {{380.12089898, 500.622951958}, {78.3773221901, 21.0231661209}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 995D5293 - GraphicStyle - 10 - - - Class - DKDBezier - DKDLockInfo - 14 - DkBezArrow - 2 - GraphicID - AA5AC793 - GraphicStyle - 56 - SVGPath - M392.38395542,187.385848569 C392.38395542,187.385848569 392.38395542,187.385848569 392.38395542,187.385848569 C388.874616953,82.0118025472 314.836279053,62.9552738792 252.242178667,64.2646682342 C74.660984366,73.469899094 94.9233564416,159.621417018 155.871923044,158.837670052 - - - AttributedText - - String - Undirected graph - TextAttribute - 48 - - Bounds - {{278.536338118, 269.434500953}, {202.348893933, 34.1385596992}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 14DEC993 - GraphicStyle - 10 - - - AttributedText - - String - Directed graph - TextAttribute - 48 - - Bounds - {{293.922046293, 579.498747608}, {175.234506692, 31.2274995402}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 55CF0C93 - GraphicStyle - 10 - - - AttributedText - - String - v1 is always the start vertex - TextAttribute - 64 - - Bounds - {{60.293252336, 441.009991214}, {97.7060551675, 35.5524288103}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 0DE69F93 - GraphicStyle - 72 - - - Class - DKDLine - DKDLockInfo - 14 - GraphicID - 810832A3 - GraphicStyle - 72 - SVGPath - M157.882132105,455.367402409 L205.163707398,464.535419242 - - - AttributedText - - String - order doesn't matter - TextAttribute - 64 - - Bounds - {{241.811440898, 94.6978314387}, {85.897378884, 35.5524288103}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 172903A3 - GraphicStyle - 72 - - - Class - DKDLine - DKDLockInfo - 14 - GraphicID - 5E2CF3A3 - GraphicStyle - 72 - SVGPath - M207.795524408,150.73968488 L240.957211333,113.210245872 - - - AttributedText - - String - list of Edges - TextAttribute - 64 - - Bounds - {{261.400575239, 323.584485991}, {85.897378884, 18.9174921865}} - Class - DKDTextArea - DKDLockInfo - 14 - GraphicID - 8BCA09A3 - GraphicStyle - 72 - - - Class - DKDLine - DKDLockInfo - 14 - GraphicID - 7462B9A3 - GraphicStyle - 72 - SVGPath - M221.084225911,339.165550309 L260.524401441,334.093988961 - - - HideDimensions - NO - LayerColorMod - - DKDOnColorMod - NO - DKDOpacityColorMod - 0.5 - DKDOutlineColorMod - NO - DKDTintColorColorMod - - ColorSpace - NSCalibratedWhiteColorSpace - Opacity - 1 - White - 0.5 - - DKDTintFractionColorMod - 0.5 - - LayerLock - NO - LayerName - Paper - LayerState - Active - OutlineLayer - NO - - - DKDPagesSpec - - BackgroundDisplay - Background - CanvasBorder - - Blue - 0.45904 - BluePlus - 0.533333 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.458672 - GreenPlus - 0.533333 - Opacity - 1 - OpacityPlus - 1 - Red - 0.475001 - RedPlus - 0.54902 - - CanvasColor - - Catalog - System - Catalog-Color - windowBackgroundColor - - CanvasMargin - 0 - DetailsDrawerWidth - 260 - DisplayAttributesBar - YES - DisplayRulers - NO - FullScreen - NO - FullScreenCanvasMargin - 141.73228 - LayersDrawerWidth - 266 - NonFullScreenCanvasMargin - 0 - NumberAcrossFirst - YES - PagesAcross - 1 - PagesDown - 1 - PagesSpecBackgroundRGB - - ColorSpace - NSCalibratedWhiteColorSpace - Opacity - 1 - White - 1 - - PagesSpecPrintBackground - NO - ShowPageBreaks - NO - SizeChecker - 8 - aCheckerColor - - Blue - 0.926349 - BluePlus - 0.941176 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.926333 - GreenPlus - 0.941176 - Opacity - 1 - OpacityPlus - 1 - Red - 0.926361 - RedPlus - 0.941176 - - bCheckerColor - - Blue - 0.737155 - BluePlus - 0.784314 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.737142 - GreenPlus - 0.784314 - Opacity - 1 - OpacityPlus - 1 - Red - 0.737164 - RedPlus - 0.784314 - - - DKDPrintInfo - - BottomMargin - 18 - Copies - 1 - FallBackPaperSizeHeight - 841.889770508 - FallBackPaperSizeWidth - 595.27557373 - FirstPage - 1 - HorizontalPagination - 2 - HorizontallyCentered - YES - LastPage - -1 - LeftMargin - 18 - MustCollate - YES - Orientation - 0 - PaperName - A4 - PaperSizeHeight - 841.889770508 - PaperSizeWidth - 595.27557373 - PreviewPageNumber - 1 - PrintAllPages - YES - PrintJobDisposition - NSPrintSpoolJob - PrintSavePath - - PrintScalingFactor - 1 - PrinterName - Brother MFC-9340CDW - ReversePageOrder - NO - RightMargin - 18 - TopMargin - 18 - VerticalPagination - 0 - VerticallyCentered - YES - XPrintMirror - NO - YPrintMirror - NO - - DKDSaveTimeStamp - 2020-09-21 23:12:02 +0000 - DKDTablet - - BrushDynamic - NO - BrushFit - 6 - PenMax - 25 - PenMin - 0.5 - PenPressureFactor - 0.5 - PencilDynamic - NO - PencilFit - 7 - - DKDTimeFormat - - Field 0 Include - Weekday - Field 0 Type - Long - Field 1 Include - Month - Field 1 Type - Short - Field 2 Include - Day - Field 2 Type - Number - Field 3 Include - Year - Field 3 Type - Long - Include GMT - NO - Include Title - YES - IncludeDate - YES - IncludeTime - YES - Seperator 0 - - - Seperator 1 - . - Seperator 2 - , - Seperator 3 - - Time Seperator - : - TimeAfterDate - YES - Twelve Hour Clock - YES - Used Once - YES - - DKDToolbarSelectedButtonPairs - - ColorTextToolbarItemIdentifier_0 - - HSB_10FFFF - - ColorFillToolbarItemIdentifier_0 - - HSB_0000D0 - - ColorStrokeToolbarItemIdentifier_0 - - HSB_000020 - - GradientToolbarItemIdentifier_0 - - - EndGradientColor - - Blue - 0.999991 - BluePlus - 1 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0.999974 - GreenPlus - 1 - Opacity - 1 - OpacityPlus - 1 - Red - 0.999999 - RedPlus - 0.999996 - - GradientFillClass - DKDHorizontalGradientFill - StartGradientColor - - Blue - 0 - BluePlus - 0 - ColorSpace - NSCalibratedRGBColorSpace - ColorSpacePlus - DKDsRgbColorSpace - Green - 0 - GreenPlus - 0 - Opacity - 1 - OpacityPlus - 1 - Red - 0 - RedPlus - 0 - - - - PatternForToolbarItemIdentifier_0 - - Toolbar_02 - - TextureForToolbarItemIdentifier_0 - - Ancient - - - DKDWindowState - - CursorMode - Nothing - DocCenter - {279.637786865, 269.670318249} - DrawersOnMainView - YES - GDetailsLayersDrawerEdgePreference - Auto - GraphicDetailsOpen - NO - LayerActiveAbove - NO - LayerOpen - NO - LayerSelect - Active Only - LayersDrawerEdgePreference - Auto - OutlineDrawing - NO - WindowLocation - 852 319 909 912 0 0 2560 1417 - ZoomPercent - 160.180664 - - GroupEdit - Fixed - NumberColorsToListInContextMenu - 12 - NumberPairColorsToListInContextMenu - 6 - - diff --git a/docs/source/edges_vertices.rst b/docs/source/edges_vertices.rst index b72a49ef..d0d6ea5c 100644 --- a/docs/source/edges_vertices.rst +++ b/docs/source/edges_vertices.rst @@ -1,6 +1,19 @@ Edges & Vertices ================= +Underlying data structures +---------------------------- + +.. image:: ../figs/datastructures.svg + +The graph class maintains a list of vertices and a list of edges. Each vertex maintains +a list of edges that connect to it, and each edge maintains a reference to the two +vertices it connects. The graph class provides methods for adding and removing vertices +and edges, as well as for querying the graph's structure. + +Working with vertices and edges directly +----------------------------------------- + In the earlier examples we did not explicitly create vertices or edges, but we can do so if we want to. The graph classes provide methods to create vertices and edges, and to add them to the graph. @@ -55,7 +68,7 @@ and then add it to the graph The ``Edge`` constuctor line above is equivalent to .. code-block:: python - + >>> e = Edge() >>> e.connect(v1, v2) From 2679025d156b53443ab02d10d6790f0920b4fa48 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 06:50:27 +1000 Subject: [PATCH 06/10] docs: outline the text in datastructures.svg Export with the font set to Outline, so the figure no longer depends on Avenir, Bradley Hand and Courier being installed. With live text a viewer without those fonts (anything but macOS) got a generic face and three callouts overflowed their boxes. The export writes path coordinates with nine decimal places, giving 1.2 MB. Rounded to three decimals, and only inside d= attributes, it is 708 KB (220 KB gzipped) and differs from the original in 94 of 4.4 million pixels at 2000 px wide. Co-Authored-By: Claude Sonnet 5.5 --- docs/figs/datastructures.svg | 6023 ++++++++++++++++++++++++++++++++-- 1 file changed, 5777 insertions(+), 246 deletions(-) diff --git a/docs/figs/datastructures.svg b/docs/figs/datastructures.svg index 4236cdcc..bfbf875e 100644 --- a/docs/figs/datastructures.svg +++ b/docs/figs/datastructures.svg @@ -15,7 +15,7 @@ preserveAspectRatio="none" id="svg2"> + id="defs_A5583312"> + d="M0,0 L-10.091,3.727 L-10.091,-3.727 z"/> + d="M0,0 L-9.986,3.635 L-9.986,-3.635 z"/> + d="M0,0 L-35.281,12.841 L-35.281,-12.841 z"/> + d="M0,0 L-7.518,2.736 L-7.518,-2.736 z"/> + d="M0,0 L-7.518,2.736 L-7.518,-2.736 z"/> + d="M0,0 L-7.518,2.736 L-7.518,-2.736 z"/> - -_edges + d="M272.778,300.463 C272.778,300.463 272.778,300.463 272.778,300.463 C274.571,264.162 231.44,238.675 225.554,209.121 C220.579,184.146 222.777,149.72 269.812,148.853"/> + + + + + + + + + d="M333.389,121.984 C333.389,121.984 333.389,121.984 333.389,121.984 C354.673,121.942 385.107,133.006 390.919,152.915 C399.738,183.12 342.225,229.688 320.872,253.743"/> - + d="M333.022,142.005 C333.022,142.005 333.022,142.005 333.022,142.005 C376.797,142.481 381.91,118.748 407.09,113.647 C422.764,110.472 438.542,111.481 455.449,121.352"/> + + + + + + - -v1 + id="AA583312" + x="302.606815291" + y="133.663968608" + width="30.9110329578" + height="22.1917445833"/> + + - + - -v2 + style="fill:#0000FF;stroke:none" + id="7B583312" + d="M538.339,202.44 C538.773,202.44 539.206,202.44 539.64,202.44 C540.374,202.44 540.892,202.348 541.193,202.162 C541.493,201.976 541.644,201.669 541.644,201.239 +C541.644,200.806 541.5,200.497 541.213,200.313 C540.926,200.13 540.421,200.038 539.698,200.038 C539.245,200.038 538.792,200.038 538.339,200.038 +z M538.339,199.317 C538.747,199.317 539.155,199.317 539.564,199.317 C540.118,199.317 540.53,199.229 540.797,199.054 C541.065,198.878 541.198,198.606 541.198,198.239 +C541.198,197.891 541.077,197.625 540.835,197.439 C540.593,197.254 540.245,197.161 539.792,197.161 C539.308,197.161 538.823,197.161 538.339,197.161 +z M539.517,203.266 C538.642,203.266 537.767,203.266 536.892,203.266 C536.755,203.266 536.653,203.231 536.584,203.161 C536.516,203.091 536.482,202.985 536.482,202.845 +C536.482,202.688 536.525,202.582 536.611,202.525 C536.697,202.469 536.902,202.44 537.226,202.44 C537.29,202.44 537.355,202.44 537.419,202.44 +C537.419,200.681 537.419,198.921 537.419,197.161 C537.355,197.161 537.29,197.161 537.226,197.161 C536.925,197.161 536.732,197.131 536.646,197.07 +C536.56,197.01 536.517,196.899 536.517,196.739 C536.517,196.602 536.552,196.498 536.622,196.426 C536.693,196.353 536.794,196.317 536.927,196.317 +C537.839,196.317 538.751,196.317 539.663,196.317 C540.491,196.317 541.122,196.478 541.556,196.801 C541.989,197.123 542.206,197.591 542.206,198.204 +C542.206,198.513 542.14,198.782 542.007,199.013 C541.874,199.243 541.677,199.432 541.415,199.581 C541.837,199.733 542.151,199.946 542.356,200.22 +C542.561,200.493 542.663,200.841 542.663,201.263 C542.663,201.618 542.575,201.942 542.4,202.235 C542.224,202.528 541.976,202.768 541.655,202.956 +C541.464,203.065 541.251,203.144 541.017,203.193 C540.782,203.242 540.45,203.266 540.021,203.266 z"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -BaseVertex subclass - -Edge subclass - -_edges + d="M291.737,659.896 C291.737,659.896 291.737,659.896 291.737,659.896 C293.53,623.595 250.398,598.108 244.512,568.554 C239.538,543.579 251.414,510.422 288.771,508.286"/> + + + + + + + + + d="M352.347,481.418 C352.347,481.418 352.347,481.418 352.347,481.418 C373.631,481.375 404.065,492.44 409.878,512.348 C418.697,542.553 361.183,589.121 339.831,613.176"/> - + d="M351.98,501.438 C351.98,501.438 351.98,501.438 351.98,501.438 C395.756,501.914 400.868,478.181 426.048,473.08 C441.722,469.905 457.501,470.914 474.408,480.785"/> + + + + + + - -v1 + id="B7683312" + x="321.565322782" + y="493.097179725" + width="30.9996031922" + height="21.0231661209"/> + + - + - -v2 + style="fill:#0000FF;stroke:none" + id="78683312" + d="M299.41,549.566 C299.318,549.566 299.226,549.566 299.135,549.566 C298.994,549.566 298.888,549.531 298.815,549.46 C298.743,549.39 298.707,549.285 298.707,549.144 +C298.707,548.988 298.749,548.881 298.833,548.825 C298.917,548.768 299.109,548.74 299.41,548.74 C299.535,548.74 299.66,548.74 299.785,548.74 +C299.785,546.98 299.785,545.22 299.785,543.46 C299.66,543.46 299.535,543.46 299.41,543.46 C299.117,543.46 298.927,543.43 298.839,543.37 +C298.751,543.309 298.707,543.199 298.707,543.039 C298.707,542.894 298.743,542.788 298.815,542.719 C298.888,542.651 298.998,542.617 299.146,542.617 +C299.234,542.617 299.322,542.617 299.41,542.617 C301.023,542.617 302.637,542.617 304.25,542.617 C304.383,542.617 304.483,542.657 304.552,542.737 +C304.62,542.817 304.654,542.933 304.654,543.085 C304.654,543.546 304.654,544.007 304.654,544.468 C304.654,544.671 304.625,544.812 304.566,544.89 +C304.508,544.968 304.402,545.007 304.25,545.007 C304.09,545.007 303.979,544.964 303.919,544.878 C303.858,544.792 303.828,544.599 303.828,544.298 +C303.828,544.019 303.828,543.74 303.828,543.46 C302.789,543.46 301.75,543.46 300.711,543.46 C300.711,544.119 300.711,544.777 300.711,545.435 +C301.222,545.435 301.734,545.435 302.246,545.435 C302.246,545.332 302.246,545.228 302.246,545.125 C302.246,544.914 302.274,544.767 302.331,544.685 +C302.388,544.603 302.486,544.562 302.627,544.562 C302.763,544.562 302.858,544.606 302.911,544.694 C302.964,544.782 302.99,544.97 302.99,545.259 +C302.99,545.623 302.99,545.986 302.99,546.349 C302.99,546.642 302.963,546.83 302.908,546.912 C302.853,546.994 302.756,547.035 302.615,547.035 +C302.474,547.035 302.378,546.988 302.325,546.894 C302.272,546.8 302.246,546.619 302.246,546.349 C302.246,546.281 302.246,546.212 302.246,546.144 +C301.734,546.144 301.222,546.144 300.711,546.144 C300.711,547.009 300.711,547.875 300.711,548.74 C301.75,548.74 302.789,548.74 303.828,548.74 +C303.828,548.423 303.828,548.107 303.828,547.791 C303.828,547.49 303.858,547.296 303.919,547.21 C303.979,547.125 304.09,547.082 304.25,547.082 +C304.398,547.082 304.503,547.122 304.563,547.202 C304.624,547.282 304.654,547.433 304.654,547.656 C304.654,548.148 304.654,548.64 304.654,549.132 +C304.654,549.273 304.619,549.38 304.549,549.455 C304.478,549.529 304.379,549.566 304.25,549.566 z"/> + + + + + + + + + + + - -Edge subclass - -_edges + + + + + + + + - -_edges + + + + + + + + - -Undirected graph - -Directed graph - + d="M508.863,149.915 C508.863,149.915 508.863,149.915 508.863,149.915 C505.353,44.541 431.315,25.485 368.721,26.794 C191.14,35.999 211.402,122.151 272.351,121.367"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -v1 is always the start vertex + id="4D783312" + x="379.034094332" + y="413.802912654" + width="87.7526987382" + height="35.5524288103"/> + + + + + + + + + + + + + + + + + + + + + + + + - - + - -order doesn't matter + id="2C883312" + x="358.290129334" + y="57.227421268" + width="85.897378884" + height="35.5524288103"/> + + + + + + + + + + + + + + + + + + - - + - -list of Edges + id="B6983312" + x="377.879263675" + y="286.11407582" + width="85.897378884" + height="18.9174921865"/> + + + + + + + + + + + - -BaseVertex subclass - -BaseVertex subclass - -BaseVertex subclass + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -_BaseGraph class + + + + + + + + + + + + + + + + + - -_edgelist + + + + + + + + + + + - -_vertexlist + + + + + + + + + + + + + + d="M119.223,110.384 C119.223,110.384 119.223,110.384 119.223,110.384 C118.752,150.019 148.456,198.811 165.895,223.461 C179.57,242.79 189.256,252.285 240.685,276.087"/> + d="M142.513,109.9 C142.513,109.9 144.084,104.14 144.084,104.14 C158.525,28.251 263.972,-1.401 326.234,1.468 +C407.822,5.226 451.512,59.737 482.747,99.115"/> + d="M155.215,140.656 C155.215,140.656 155.215,140.656 155.215,140.656 C158.122,169.006 195.158,167.693 202.967,163.306 C210.952,158.821 211.017,133.562 270.404,136.747"/> - -_BaseGraph class + + + + + + + + + + + + + + + + + - -_edgelist + + + + + + + + + + + - -_vertexlist + + + + + + + + + + + + + + d="M138.786,472.325 C138.786,472.325 138.786,472.325 138.786,472.325 C138.315,511.961 168.019,560.753 185.458,585.402 C199.133,604.732 208.819,614.227 260.248,638.028"/> + d="M162.076,471.841 C162.076,471.841 163.647,466.082 163.647,466.082 C178.088,390.192 283.535,360.541 345.797,363.41 C427.385,367.168 471.075,421.678 502.309,461.056"/> + d="M174.778,502.597 C174.778,502.597 174.778,502.597 174.778,502.597 C177.685,530.948 214.721,529.635 222.53,525.248 C230.515,520.762 230.58,495.504 289.967,498.689"/> From f5edea3cdb18319ef1e774b6dc2da7d8eed7b673 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 07:29:59 +1000 Subject: [PATCH 07/10] docs: say what embedding a graph gives you, in the README and intro The opening paragraph mentioned 'embedded coordinates' only in passing. Say that embedding is optional and what it enables: edge costs computed from the vertex positions with a metric, nearest-vertex lookup, A* with the metric as its heuristic, and plotting or export with the layout preserved. Drop 'not a plotting library', which contradicted that. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 11 ++++++++--- docs/source/intro.rst | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1693b2ac..242ecd8f 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,14 @@ [![Powered by Spatial Maths](https://raw.githubusercontent.com/petercorke/spatialmath-python/master/.github/svg/sm_powered.min.svg)](https://github.com/petercorke/spatialmath-python) This package provides classes for directed and undirected graphs: vertices -connected by edges, with embedded coordinates, edge costs and path planning. -Self-loops and parallel edges are permitted. It is a graph-theory package, not -a plotting library. +connected by edges, with edge costs and path planning. Self-loops and +parallel edges are permitted. + +Vertices can optionally be *embedded*: given a coordinate in a space of any +dimension. Edge costs are then computed from the vertex positions using a +distance metric (Euclidean, Manhattan, SE(2), or your own), the vertex nearest +a point can be found, `A*` uses the metric as its heuristic, and the graph can +be plotted, or exported with its layout preserved. ![road network](https://github.com/petercorke/pgraph-python/raw/main/examples/roads.png) diff --git a/docs/source/intro.rst b/docs/source/intro.rst index bd6565a0..6c57ccb6 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -2,9 +2,14 @@ Introduction ================= This package provides classes for directed and undirected graphs: vertices -connected by edges, with embedded coordinates, edge costs and path planning. -Self-loops and parallel edges are permitted. It is a graph-theory package, not -a plotting library. +connected by edges, with edge costs and path planning. Self-loops and +parallel edges are permitted. + +Vertices can optionally be *embedded*: given a coordinate in a space of any +dimension. Edge costs are then computed from the vertex positions using a +distance metric (Euclidean, Manhattan, SE(2), or your own), the vertex nearest +a point can be found, ``A*`` uses the metric as its heuristic, and the graph can +be plotted, or exported with its layout preserved. What is a graph? ----------------- From a39951ba2b4aa460631ff60945131b7353c77b22 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 08:27:15 +1000 Subject: [PATCH 08/10] docs: follow the API changes in the directives and tables - BaseGraph, not _BaseGraph, in every autoclass/automethod/autosummary directive (67 references) - analysis.rst lists adjacent() and isadjacent(), the canonical names, and not the neighbours/neighbors/isneighbour aliases These need the API changes in the source, so the build reads the new code only after this branch is rebased onto them; against the 0.6.5-era source the names do not exist. Co-Authored-By: Claude Sonnet 5.5 --- docs/source/analysis.rst | 51 ++++++++++++++++----------------- docs/source/common.rst | 6 ++-- docs/source/construction.rst | 10 +++---- docs/source/export.rst | 8 +++--- docs/source/modification.rst | 18 ++++++------ docs/source/planning.rst | 12 ++++---- docs/source/representations.rst | 20 ++++++------- docs/source/visualization.rst | 20 ++++++------- 8 files changed, 71 insertions(+), 74 deletions(-) diff --git a/docs/source/analysis.rst b/docs/source/analysis.rst index 2967a120..8b7e2555 100644 --- a/docs/source/analysis.rst +++ b/docs/source/analysis.rst @@ -1,7 +1,7 @@ Graph analysis =============== -Querying structure -- counts, neighbours, connectivity, distance -- for directed and +Querying structure -- counts, adjacent vertices, connectivity, distance -- for directed and undirected graphs. See :doc:`planning` for finding paths between vertices, and :doc:`representations` for matrix/dot forms of the @@ -13,16 +13,16 @@ Graph .. autosummary:: :nosignatures: - ~pgraph.PGraph._BaseGraph.n - ~pgraph.PGraph._BaseGraph.ne - ~pgraph.PGraph._BaseGraph.nc - ~pgraph.PGraph._BaseGraph.edges - ~pgraph.PGraph._BaseGraph.average_degree - ~pgraph.PGraph._BaseGraph.iscyclic - ~pgraph.PGraph._BaseGraph.connectivity - ~pgraph.PGraph._BaseGraph.component - ~pgraph.PGraph._BaseGraph.samecomponent - ~pgraph.PGraph._BaseGraph.closest + ~pgraph.PGraph.BaseGraph.n + ~pgraph.PGraph.BaseGraph.ne + ~pgraph.PGraph.BaseGraph.nc + ~pgraph.PGraph.BaseGraph.edges + ~pgraph.PGraph.BaseGraph.average_degree + ~pgraph.PGraph.BaseGraph.iscyclic + ~pgraph.PGraph.BaseGraph.connectivity + ~pgraph.PGraph.BaseGraph.component + ~pgraph.PGraph.BaseGraph.samecomponent + ~pgraph.PGraph.BaseGraph.closest Vertex ------ @@ -31,9 +31,8 @@ Vertex :nosignatures: ~pgraph.PGraph.BaseVertex.edges - ~pgraph.PGraph.BaseVertex.neighbours - ~pgraph.PGraph.BaseVertex.neighbors - ~pgraph.PGraph.BaseVertex.isneighbour + ~pgraph.PGraph.BaseVertex.adjacent + ~pgraph.PGraph.BaseVertex.isadjacent ~pgraph.PGraph.BaseVertex.incidences ~pgraph.PGraph.BaseVertex.degree ~pgraph.PGraph.BaseVertex.heuristic_distance @@ -45,22 +44,20 @@ Vertex Details ------- -.. autoproperty:: pgraph.PGraph._BaseGraph.n -.. autoproperty:: pgraph.PGraph._BaseGraph.ne -.. autoproperty:: pgraph.PGraph._BaseGraph.nc -.. automethod:: pgraph.PGraph._BaseGraph.edges -.. automethod:: pgraph.PGraph._BaseGraph.average_degree -.. automethod:: pgraph.PGraph._BaseGraph.iscyclic -.. automethod:: pgraph.PGraph._BaseGraph.connectivity -.. automethod:: pgraph.PGraph._BaseGraph.component -.. automethod:: pgraph.PGraph._BaseGraph.samecomponent -.. automethod:: pgraph.PGraph._BaseGraph.closest +.. autoproperty:: pgraph.PGraph.BaseGraph.n +.. autoproperty:: pgraph.PGraph.BaseGraph.ne +.. autoproperty:: pgraph.PGraph.BaseGraph.nc +.. automethod:: pgraph.PGraph.BaseGraph.edges +.. automethod:: pgraph.PGraph.BaseGraph.average_degree +.. automethod:: pgraph.PGraph.BaseGraph.iscyclic +.. automethod:: pgraph.PGraph.BaseGraph.connectivity +.. automethod:: pgraph.PGraph.BaseGraph.component +.. automethod:: pgraph.PGraph.BaseGraph.samecomponent +.. automethod:: pgraph.PGraph.BaseGraph.closest .. automethod:: pgraph.PGraph.BaseVertex.edges -.. automethod:: pgraph.PGraph.BaseVertex.neighbours -.. automethod:: pgraph.PGraph.BaseVertex.neighbors .. automethod:: pgraph.PGraph.BaseVertex.adjacent -.. automethod:: pgraph.PGraph.BaseVertex.isneighbour +.. automethod:: pgraph.PGraph.BaseVertex.isadjacent .. automethod:: pgraph.PGraph.BaseVertex.incidences .. autoproperty:: pgraph.PGraph.BaseVertex.degree .. automethod:: pgraph.PGraph.BaseVertex.heuristic_distance diff --git a/docs/source/common.rst b/docs/source/common.rst index 8944b1bd..c60277d1 100644 --- a/docs/source/common.rst +++ b/docs/source/common.rst @@ -4,12 +4,12 @@ Common methods The package supports both directed (prefix ``D``) and undirected graphs (prefix ``U``), and the classes for these share a common interface. -The graph classes represent a single graph, either directed or undirected, and they share :class:`_BaseGraph`: +The graph classes represent a single graph, either directed or undirected, and they share :class:`BaseGraph`: .. inheritance-diagram:: pgraph.PGraph.UGraph pgraph.PGraph.DGraph :parts: 1 :private-bases: - :top-classes: pgraph.PGraph._BaseGraph + :top-classes: pgraph.PGraph.BaseGraph The vertices in a directed graph (``DGraph``) inherit from :class:`DVertex` and the vertices in an undirected graph (``UGraph``) inherit from :class:`UVertex`. @@ -19,7 +19,7 @@ vertices in an undirected graph (``UGraph``) inherit from :class:`UVertex`. The vertex classes share almost their entire interface, defined once on their common base classes -(:class:`_BaseGraph`, :class:`BaseVertex`). This section documents that +(:class:`BaseGraph`, :class:`BaseVertex`). This section documents that shared interface by task rather than by class; see :doc:`construction` for the :class:`DGraph` and :class:`UGraph` classes and :doc:`edges_vertices` for what's specific to directed vs. undirected vertices. diff --git a/docs/source/construction.rst b/docs/source/construction.rst index 02ff0598..290cbdf8 100644 --- a/docs/source/construction.rst +++ b/docs/source/construction.rst @@ -12,8 +12,8 @@ standalone, independent of a graph. ~pgraph.PGraph.DGraph ~pgraph.PGraph.UGraph - ~pgraph.PGraph._BaseGraph.Dict - ~pgraph.PGraph._BaseGraph.Adjacency + ~pgraph.PGraph.BaseGraph.Dict + ~pgraph.PGraph.BaseGraph.Adjacency .. autoclass:: pgraph.PGraph.DGraph @@ -22,17 +22,17 @@ standalone, independent of a graph. .. autoclass:: pgraph.PGraph.UGraph :show-inheritance: -.. autoclass:: pgraph.PGraph._BaseGraph.Adjacency +.. autoclass:: pgraph.PGraph.BaseGraph.Adjacency :show-inheritance: -.. autoclass:: pgraph.PGraph._BaseGraph.Dict +.. autoclass:: pgraph.PGraph.BaseGraph.Dict :show-inheritance: Inherited methods for all graph types -------------------------------------- -.. autoclass:: pgraph.PGraph._BaseGraph +.. autoclass:: pgraph.PGraph.BaseGraph :members: copy, vertex_copy, metric, heuristic, show, vertex_class, edge_class :special-members: __init__, __str__, __repr__, __getitem__, __iter__, __contains__ diff --git a/docs/source/export.rst b/docs/source/export.rst index 1d86782e..059afcb1 100644 --- a/docs/source/export.rst +++ b/docs/source/export.rst @@ -13,11 +13,11 @@ it. .. autosummary:: :nosignatures: - ~pgraph.PGraph._BaseGraph.export - ~pgraph.PGraph._BaseGraph.dotfile + ~pgraph.PGraph.BaseGraph.export + ~pgraph.PGraph.BaseGraph.dotfile Details ------- -.. automethod:: pgraph.PGraph._BaseGraph.export -.. automethod:: pgraph.PGraph._BaseGraph.dotfile +.. automethod:: pgraph.PGraph.BaseGraph.export +.. automethod:: pgraph.PGraph.BaseGraph.dotfile diff --git a/docs/source/modification.rst b/docs/source/modification.rst index 1dd07a92..23fa98a1 100644 --- a/docs/source/modification.rst +++ b/docs/source/modification.rst @@ -13,10 +13,10 @@ Graph .. autosummary:: :nosignatures: - ~pgraph.PGraph._BaseGraph.add_vertex - ~pgraph.PGraph._BaseGraph.add_edge - ~pgraph.PGraph._BaseGraph.remove_edge - ~pgraph.PGraph._BaseGraph.remove_vertex + ~pgraph.PGraph.BaseGraph.add_vertex + ~pgraph.PGraph.BaseGraph.add_edge + ~pgraph.PGraph.BaseGraph.remove_edge + ~pgraph.PGraph.BaseGraph.remove_vertex Vertex ------ @@ -31,11 +31,11 @@ Vertex Details ------- -.. automethod:: pgraph.PGraph._BaseGraph.add_vertex -.. automethod:: pgraph.PGraph._BaseGraph.add_edge -.. automethod:: pgraph.PGraph._BaseGraph.remove_edge -.. automethod:: pgraph.PGraph._BaseGraph.remove_vertex -.. automethod:: pgraph.PGraph._BaseGraph.remove +.. automethod:: pgraph.PGraph.BaseGraph.add_vertex +.. automethod:: pgraph.PGraph.BaseGraph.add_edge +.. automethod:: pgraph.PGraph.BaseGraph.remove_edge +.. automethod:: pgraph.PGraph.BaseGraph.remove_vertex +.. automethod:: pgraph.PGraph.BaseGraph.remove .. automethod:: pgraph.PGraph.BaseVertex.connect .. automethod:: pgraph.PGraph.BaseVertex.edgeto diff --git a/docs/source/planning.rst b/docs/source/planning.rst index 3dbb0dd8..590abde6 100644 --- a/docs/source/planning.rst +++ b/docs/source/planning.rst @@ -6,13 +6,13 @@ These methods find optimal paths from a start vertex to a goal vertex. .. autosummary:: :nosignatures: - ~pgraph.PGraph._BaseGraph.path_BFS - ~pgraph.PGraph._BaseGraph.path_UCS - ~pgraph.PGraph._BaseGraph.path_Astar + ~pgraph.PGraph.BaseGraph.path_BFS + ~pgraph.PGraph.BaseGraph.path_UCS + ~pgraph.PGraph.BaseGraph.path_Astar Details ------- -.. automethod:: pgraph.PGraph._BaseGraph.path_BFS -.. automethod:: pgraph.PGraph._BaseGraph.path_UCS -.. automethod:: pgraph.PGraph._BaseGraph.path_Astar +.. automethod:: pgraph.PGraph.BaseGraph.path_BFS +.. automethod:: pgraph.PGraph.BaseGraph.path_UCS +.. automethod:: pgraph.PGraph.BaseGraph.path_Astar diff --git a/docs/source/representations.rst b/docs/source/representations.rst index a08db7db..7a7a56ed 100644 --- a/docs/source/representations.rst +++ b/docs/source/representations.rst @@ -7,19 +7,19 @@ These methods convert the graph to a number of different matrix representations. .. autosummary:: :nosignatures: - ~pgraph.PGraph._BaseGraph.distance - ~pgraph.PGraph._BaseGraph.adjacency - ~pgraph.PGraph._BaseGraph.degree - ~pgraph.PGraph._BaseGraph.incidence - ~pgraph.PGraph._BaseGraph.Laplacian + ~pgraph.PGraph.BaseGraph.distance + ~pgraph.PGraph.BaseGraph.adjacency + ~pgraph.PGraph.BaseGraph.degree + ~pgraph.PGraph.BaseGraph.incidence + ~pgraph.PGraph.BaseGraph.Laplacian Details ------- -.. automethod:: pgraph.PGraph._BaseGraph.distance -.. automethod:: pgraph.PGraph._BaseGraph.adjacency -.. automethod:: pgraph.PGraph._BaseGraph.degree -.. automethod:: pgraph.PGraph._BaseGraph.incidence -.. automethod:: pgraph.PGraph._BaseGraph.Laplacian +.. automethod:: pgraph.PGraph.BaseGraph.distance +.. automethod:: pgraph.PGraph.BaseGraph.adjacency +.. automethod:: pgraph.PGraph.BaseGraph.degree +.. automethod:: pgraph.PGraph.BaseGraph.incidence +.. automethod:: pgraph.PGraph.BaseGraph.Laplacian diff --git a/docs/source/visualization.rst b/docs/source/visualization.rst index f9421981..74299981 100644 --- a/docs/source/visualization.rst +++ b/docs/source/visualization.rst @@ -8,17 +8,17 @@ file) rather than displaying it. .. autosummary:: :nosignatures: - ~pgraph.PGraph._BaseGraph.plot - ~pgraph.PGraph._BaseGraph.showgraph - ~pgraph.PGraph._BaseGraph.highlight_path - ~pgraph.PGraph._BaseGraph.highlight_edge - ~pgraph.PGraph._BaseGraph.highlight_vertex + ~pgraph.PGraph.BaseGraph.plot + ~pgraph.PGraph.BaseGraph.showgraph + ~pgraph.PGraph.BaseGraph.highlight_path + ~pgraph.PGraph.BaseGraph.highlight_edge + ~pgraph.PGraph.BaseGraph.highlight_vertex Details ------- -.. automethod:: pgraph.PGraph._BaseGraph.plot -.. automethod:: pgraph.PGraph._BaseGraph.showgraph -.. automethod:: pgraph.PGraph._BaseGraph.highlight_path -.. automethod:: pgraph.PGraph._BaseGraph.highlight_edge -.. automethod:: pgraph.PGraph._BaseGraph.highlight_vertex +.. automethod:: pgraph.PGraph.BaseGraph.plot +.. automethod:: pgraph.PGraph.BaseGraph.showgraph +.. automethod:: pgraph.PGraph.BaseGraph.highlight_path +.. automethod:: pgraph.PGraph.BaseGraph.highlight_edge +.. automethod:: pgraph.PGraph.BaseGraph.highlight_vertex From 0af373c07849e3c69683446f46306afe264880cf Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 09:44:10 +1000 Subject: [PATCH 09/10] docs: follow the renames merged to main Laplacian is laplacian, _BaseGraph is BaseGraph, and the vertex pages list adjacent rather than neighbours. Co-Authored-By: Claude Sonnet 5.5 --- docs/source/dvertex.rst | 2 +- docs/source/representations.rst | 4 ++-- docs/source/uvertex.rst | 2 +- src/pgraph/PGraph.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/dvertex.rst b/docs/source/dvertex.rst index 732da277..10e4d49f 100644 --- a/docs/source/dvertex.rst +++ b/docs/source/dvertex.rst @@ -2,7 +2,7 @@ Directed graph vertex --------------------- ``DVertex`` overrides only ``connect()`` -- see :doc:`common` for the full -method list (neighbours, edges, distance, degree, removal, ...). +method list (adjacent, edges, distance, degree, removal, ...). .. autoclass:: pgraph.PGraph.DVertex :members: connect diff --git a/docs/source/representations.rst b/docs/source/representations.rst index 7a7a56ed..52ec2459 100644 --- a/docs/source/representations.rst +++ b/docs/source/representations.rst @@ -11,7 +11,7 @@ These methods convert the graph to a number of different matrix representations. ~pgraph.PGraph.BaseGraph.adjacency ~pgraph.PGraph.BaseGraph.degree ~pgraph.PGraph.BaseGraph.incidence - ~pgraph.PGraph.BaseGraph.Laplacian + ~pgraph.PGraph.BaseGraph.laplacian Details ------- @@ -20,6 +20,6 @@ Details .. automethod:: pgraph.PGraph.BaseGraph.adjacency .. automethod:: pgraph.PGraph.BaseGraph.degree .. automethod:: pgraph.PGraph.BaseGraph.incidence -.. automethod:: pgraph.PGraph.BaseGraph.Laplacian +.. automethod:: pgraph.PGraph.BaseGraph.laplacian diff --git a/docs/source/uvertex.rst b/docs/source/uvertex.rst index 92564eba..8c5a42f4 100644 --- a/docs/source/uvertex.rst +++ b/docs/source/uvertex.rst @@ -2,7 +2,7 @@ Undirected graph vertex ----------------------- ``UVertex`` overrides only ``connect()`` -- see :doc:`common` for the full -method list (neighbours, edges, distance, degree, removal, ...). +method list (adjacent, edges, distance, degree, removal, ...). .. autoclass:: pgraph.PGraph.UVertex :members: connect diff --git a/src/pgraph/PGraph.py b/src/pgraph/PGraph.py index 46898fa7..63282e00 100644 --- a/src/pgraph/PGraph.py +++ b/src/pgraph/PGraph.py @@ -3752,7 +3752,7 @@ def connect( vertices must agree with them: in either order if undirected, exactly ``self`` then ``dest`` if directed. - Both vertices must already have been added to the same graph, - e.g. via :meth:`_BaseGraph.add_vertex` -- since a graph only ever + e.g. via :meth:`BaseGraph.add_vertex` -- since a graph only ever accepts its own vertex subclass (see :meth:`UGraph.add_vertex`, :meth:`DGraph.add_vertex`), this also rules out connecting a ``UVertex`` to a ``DVertex``. From 2136091daf66eabff5fb33f2767998146ef55ea2 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Mon, 12 Oct 2026 09:44:32 +1000 Subject: [PATCH 10/10] docs: correct three statements in the README's reference lists Co-Authored-By: Claude Sonnet 5.5 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 242ecd8f..0c16fed5 100644 --- a/README.md +++ b/README.md @@ -97,18 +97,18 @@ Graphs belong to the class `UGraph` or `DGraph` for undirected or directed graph - `g.incidence()` incidence matrix ### Properties and methods of a vertex -Vertices belong to the class `UVertex` (for undirected graphs) or `DVertex` (for directed graphs), which are each subclasses of `Vertex`. +Vertices belong to the class `UVertex` (for undirected graphs) or `DVertex` (for directed graphs), which are each subclasses of `BaseVertex`. - `v.coord` the coordinate vector for embedded graph (optional) - `v.name` the name of the vertex (optional) -- `v.neighbours()` is a list of the neighbouring vertices +- `v.adjacent()` is a list of the adjacent vertices (`neighbours()` and `neighbors()` are the same) - `v1.samecomponent(v2)` predicate for vertices belonging to the same component Vertices can be named and referenced by name. ### Properties and methods of an edge Edges are instances of the class `Edge`. -Edges are not referenced by the graph object, each edge references a pair of vertices, and the vertices reference the edges. For a directed graph only the start vertex of an edge references the edge object, whereas for an undirected graph both vertices reference the edge object. +Each edge references a pair of vertices, the graph lists all of its edges in `g.edges()`, and the vertices reference the edges. For a directed graph only the start vertex of an edge references the edge object, whereas for an undirected graph both vertices reference the edge object. - `e.cost` cost of edge for planning methods - `e.next(v)` vertex on edge `e` that is not `v`