chemrecon 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chemrecon/__init__.py +73 -0
- chemrecon/chem/__init__.py +0 -0
- chemrecon/chem/chemreaction.py +223 -0
- chemrecon/chem/constant_compounds.py +3 -0
- chemrecon/chem/create_mol.py +91 -0
- chemrecon/chem/elements.py +141 -0
- chemrecon/chem/gml/__init__.py +0 -0
- chemrecon/chem/gml/gml.py +324 -0
- chemrecon/chem/gml/gml_reactant_matching.py +130 -0
- chemrecon/chem/gml/gml_to_rdk.py +217 -0
- chemrecon/chem/mol.py +483 -0
- chemrecon/chem/sumformula.py +120 -0
- chemrecon/connection.py +97 -0
- chemrecon/core/__init__.py +0 -0
- chemrecon/core/id_types.py +687 -0
- chemrecon/core/ontology.py +209 -0
- chemrecon/core/populate_query_handler.py +336 -0
- chemrecon/core/query_handler.py +587 -0
- chemrecon/database/__init__.py +1 -0
- chemrecon/database/connect.py +63 -0
- chemrecon/database/connection_params/chemrecon_pub.dbinfo +5 -0
- chemrecon/database/connection_params/local_docker_dev.dbinfo +5 -0
- chemrecon/database/connection_params/local_docker_init.dbinfo +5 -0
- chemrecon/database/connection_params/local_docker_pub.dbinfo +5 -0
- chemrecon/database/params.py +88 -0
- chemrecon/entrygraph/draw.py +119 -0
- chemrecon/entrygraph/entrygraph.py +301 -0
- chemrecon/entrygraph/explorationprotocol.py +199 -0
- chemrecon/entrygraph/explore.py +421 -0
- chemrecon/entrygraph/explore_procedure.py +183 -0
- chemrecon/entrygraph/filter.py +88 -0
- chemrecon/entrygraph/scoring.py +141 -0
- chemrecon/query/__init__.py +26 -0
- chemrecon/query/create_entry.py +86 -0
- chemrecon/query/default_protocols.py +57 -0
- chemrecon/query/find_entry.py +84 -0
- chemrecon/query/get_relations.py +143 -0
- chemrecon/query/get_structures_from_compound.py +65 -0
- chemrecon/schema/__init__.py +86 -0
- chemrecon/schema/db_object.py +363 -0
- chemrecon/schema/direction.py +10 -0
- chemrecon/schema/entry_types/__init__.py +0 -0
- chemrecon/schema/entry_types/aam.py +34 -0
- chemrecon/schema/entry_types/aam_repr.py +37 -0
- chemrecon/schema/entry_types/compound.py +52 -0
- chemrecon/schema/entry_types/enzyme.py +49 -0
- chemrecon/schema/entry_types/molstructure.py +64 -0
- chemrecon/schema/entry_types/molstructure_repr.py +41 -0
- chemrecon/schema/entry_types/reaction.py +57 -0
- chemrecon/schema/enums.py +154 -0
- chemrecon/schema/procedural_relation_entrygraph.py +66 -0
- chemrecon/schema/relation_types_composed/__init__.py +0 -0
- chemrecon/schema/relation_types_composed/compound_has_molstructure_relation.py +59 -0
- chemrecon/schema/relation_types_composed/reaction_has_aam_relation.py +50 -0
- chemrecon/schema/relation_types_procedural/__init__.py +0 -0
- chemrecon/schema/relation_types_procedural/aam_convert_relation.py +69 -0
- chemrecon/schema/relation_types_procedural/compound_select_structure_proceduralrelation.py +36 -0
- chemrecon/schema/relation_types_procedural/compound_similarlity_proceduralrelation.py +1 -0
- chemrecon/schema/relation_types_procedural/molstructure_convert_relation.py +49 -0
- chemrecon/schema/relation_types_procedural/reaction_select_aam_proceduralrelation.py +38 -0
- chemrecon/schema/relation_types_procedural/reaction_similarity_proceduralrelation.py +1 -0
- chemrecon/schema/relation_types_source/__init__.py +0 -0
- chemrecon/schema/relation_types_source/aam_involves_molstructure_relation.py +77 -0
- chemrecon/schema/relation_types_source/aam_repr_involves_molstructure_repr_relation.py +79 -0
- chemrecon/schema/relation_types_source/compound_has_structure_representation_relation.py +33 -0
- chemrecon/schema/relation_types_source/compound_reference_relation.py +34 -0
- chemrecon/schema/relation_types_source/molstructure_standardisation_relation.py +71 -0
- chemrecon/schema/relation_types_source/ontology/__init__.py +0 -0
- chemrecon/schema/relation_types_source/ontology/compound_ontology.py +369 -0
- chemrecon/schema/relation_types_source/ontology/enzyme_ontology.py +142 -0
- chemrecon/schema/relation_types_source/ontology/reaction_ontology.py +140 -0
- chemrecon/schema/relation_types_source/reaction_has_aam_representation_relation.py +34 -0
- chemrecon/schema/relation_types_source/reaction_has_enzyme_relation.py +71 -0
- chemrecon/schema/relation_types_source/reaction_involves_compound_relation.py +69 -0
- chemrecon/schema/relation_types_source/reaction_reference_relation.py +33 -0
- chemrecon/scripts/initialize_database.py +494 -0
- chemrecon/utils/copy_signature.py +10 -0
- chemrecon/utils/encodeable_list.py +11 -0
- chemrecon/utils/get_id_type.py +70 -0
- chemrecon/utils/hungarian.py +31 -0
- chemrecon/utils/reactant_matching.py +168 -0
- chemrecon/utils/rxnutils.py +44 -0
- chemrecon/utils/set_cwd.py +12 -0
- chemrecon-0.1.1.dist-info/METADATA +143 -0
- chemrecon-0.1.1.dist-info/RECORD +86 -0
- chemrecon-0.1.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
""" This script loads and exports connection params.
|
|
2
|
+
"""
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Params:
|
|
8
|
+
"""
|
|
9
|
+
Represents database connection parameters and provides a method to generate a formatted
|
|
10
|
+
connection string compatible with PostgreSQL.
|
|
11
|
+
"""
|
|
12
|
+
connection_title: str # Name of the connection file
|
|
13
|
+
db_name: str
|
|
14
|
+
db_host: str
|
|
15
|
+
db_port: str
|
|
16
|
+
username: str
|
|
17
|
+
password: str
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
connection_title: str,
|
|
22
|
+
db_name: str,
|
|
23
|
+
db_host: str,
|
|
24
|
+
db_port: str,
|
|
25
|
+
username: str,
|
|
26
|
+
password: str
|
|
27
|
+
):
|
|
28
|
+
self.connection_title = connection_title
|
|
29
|
+
self.db_name = db_name
|
|
30
|
+
self.db_host = db_host
|
|
31
|
+
self.db_port = db_port
|
|
32
|
+
self.username = username
|
|
33
|
+
self.password = password
|
|
34
|
+
|
|
35
|
+
def connection_string(self) -> str:
|
|
36
|
+
""" Returns a connection string compatible with PostgreSQL."""
|
|
37
|
+
return f'postgres://{self.username}:{self.password}@{self.db_host}:{self.db_port}/{self.db_name}'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Load params
|
|
41
|
+
parameter_sets: list[Params] = list()
|
|
42
|
+
|
|
43
|
+
script_path = os.path.dirname(__file__)
|
|
44
|
+
rel_path = 'connection_params/'
|
|
45
|
+
params_dir = os.path.join(script_path, rel_path)
|
|
46
|
+
for param_file in os.listdir(params_dir):
|
|
47
|
+
try:
|
|
48
|
+
with open(os.path.join(params_dir, param_file)) as f:
|
|
49
|
+
flines = f.readlines()
|
|
50
|
+
param = Params(
|
|
51
|
+
param_file,
|
|
52
|
+
db_name = flines[0].strip(),
|
|
53
|
+
db_host = flines[3].strip(),
|
|
54
|
+
db_port = flines[4].strip(),
|
|
55
|
+
username = flines[1].strip(),
|
|
56
|
+
password = flines[2].strip()
|
|
57
|
+
)
|
|
58
|
+
if len(flines) > 5:
|
|
59
|
+
raise ValueError('Malformed database connection parameters file.')
|
|
60
|
+
|
|
61
|
+
parameter_sets.append(param)
|
|
62
|
+
except (KeyError, IndexError):
|
|
63
|
+
print('Invalid connection parameters file.')
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# Set params to find easily
|
|
67
|
+
|
|
68
|
+
# Test
|
|
69
|
+
local_docker_init: Optional[Params] = None
|
|
70
|
+
local_docker_dev: Optional[Params] = None
|
|
71
|
+
local_docker_pub: Optional[Params] = None
|
|
72
|
+
|
|
73
|
+
# Production
|
|
74
|
+
chemrecon_dev: Optional[Params] = None
|
|
75
|
+
chemrecon_pub: Optional[Params] = None
|
|
76
|
+
|
|
77
|
+
for p in parameter_sets:
|
|
78
|
+
match p.connection_title:
|
|
79
|
+
case 'local_docker_init.dbinfo':
|
|
80
|
+
local_docker_init = p
|
|
81
|
+
case 'local_docker_dev.dbinfo':
|
|
82
|
+
local_docker_dev = p
|
|
83
|
+
case 'local_docker_pub.dbinfo':
|
|
84
|
+
local_docker_pub = p
|
|
85
|
+
case 'chemrecon_dev.dbinfo':
|
|
86
|
+
chemrecon_dev = p
|
|
87
|
+
case 'chemrecon_pub.dbinfo':
|
|
88
|
+
chemrecon_pub = p
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
""" Drawing utilites for each type of entry.
|
|
2
|
+
"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Type, Optional
|
|
6
|
+
|
|
7
|
+
from PIL import Image
|
|
8
|
+
import rustworkx.visualization as rx_vis
|
|
9
|
+
|
|
10
|
+
import chemrecon.entrygraph.entrygraph
|
|
11
|
+
from chemrecon.schema import Entry, Relation
|
|
12
|
+
|
|
13
|
+
attr_none: str = "''"
|
|
14
|
+
linewidth: int = 30
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# noinspection PyProtectedMember
|
|
18
|
+
class EntryGraphDrawer():
|
|
19
|
+
entrygraph_type: Type[chemrecon.entrygraph.entrygraph.EntryGraph]
|
|
20
|
+
|
|
21
|
+
# Draw settings
|
|
22
|
+
draw_reconids: bool
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
# Settings
|
|
27
|
+
draw_reconids: bool = False
|
|
28
|
+
):
|
|
29
|
+
""" The initializer for the Drawer sets the settings
|
|
30
|
+
"""
|
|
31
|
+
self.draw_reconids = draw_reconids
|
|
32
|
+
|
|
33
|
+
def draw(
|
|
34
|
+
self,
|
|
35
|
+
entrygraph: chemrecon.entrygraph.entrygraph.EntryGraph,
|
|
36
|
+
filename: str = None,
|
|
37
|
+
filetype: str = 'png',
|
|
38
|
+
scores: Optional[dict[Entry, float]] = None
|
|
39
|
+
) -> Image.Image:
|
|
40
|
+
""" Draw the entry graph.
|
|
41
|
+
|
|
42
|
+
For possible file formats, see the the corresponding
|
|
43
|
+
[RustWorkX documentation](https://www.rustworkx.org/apiref/rustworkx.visualization.graphviz_draw.html).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
g_ = entrygraph.g.copy()
|
|
47
|
+
|
|
48
|
+
# Empty score dict if not given
|
|
49
|
+
score_dict: dict[Entry, float] = scores if scores is not None else dict()
|
|
50
|
+
|
|
51
|
+
# Remove duplicate edges (for symmetric relations)
|
|
52
|
+
for e_index in g_.edge_indices():
|
|
53
|
+
data = g_.get_edge_data_by_index(e_index)
|
|
54
|
+
if data.relation.symmetric:
|
|
55
|
+
x, y = g_.get_edge_endpoints_by_index(e_index)
|
|
56
|
+
if x > y:
|
|
57
|
+
g_.remove_edge_from_index(e_index)
|
|
58
|
+
|
|
59
|
+
# Remove self-edges
|
|
60
|
+
for e_index in g_.edge_indices():
|
|
61
|
+
x, y = g_.get_edge_endpoints_by_index(e_index)
|
|
62
|
+
if x == y:
|
|
63
|
+
g_.remove_edge_from_index(e_index)
|
|
64
|
+
|
|
65
|
+
return rx_vis.graphviz_draw(
|
|
66
|
+
g_,
|
|
67
|
+
node_attr_fn = lambda v: self.node_attr(v.entry, score_dict.get(v.entry, None)),
|
|
68
|
+
edge_attr_fn = lambda e: self.edge_attr(e.relation),
|
|
69
|
+
graph_attr = None, # TODO
|
|
70
|
+
filename = filename,
|
|
71
|
+
image_type = filetype,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def node_attr(self, entry: Entry, score: Optional[float]) -> dict[str, str]:
|
|
75
|
+
assert entry.recon_id is not None
|
|
76
|
+
|
|
77
|
+
# Label
|
|
78
|
+
if self.draw_reconids:
|
|
79
|
+
recon_id_part = f'\n{entry.recon_id}'
|
|
80
|
+
else:
|
|
81
|
+
recon_id_part = ''
|
|
82
|
+
|
|
83
|
+
# Break lines of label
|
|
84
|
+
entrylabel = entry._vis_str()
|
|
85
|
+
label_lines = entrylabel.splitlines()
|
|
86
|
+
split_label: list[str] = list()
|
|
87
|
+
for l in label_lines:
|
|
88
|
+
while l != '':
|
|
89
|
+
split_label.append(l[:linewidth])
|
|
90
|
+
l = l[linewidth:]
|
|
91
|
+
entry_label_final = ('\n').join(split_label)
|
|
92
|
+
|
|
93
|
+
# Determine label
|
|
94
|
+
label: str = f'{entry.get_table_name()}\n{entry_label_final}{recon_id_part}'
|
|
95
|
+
if score is not None:
|
|
96
|
+
label += f'\n{score:.3f}'
|
|
97
|
+
|
|
98
|
+
# Other drawing
|
|
99
|
+
d = {
|
|
100
|
+
'style': 'filled',
|
|
101
|
+
'shape': 'box',
|
|
102
|
+
'label': label
|
|
103
|
+
}
|
|
104
|
+
d.update(entry._vis_attrs())
|
|
105
|
+
return d
|
|
106
|
+
|
|
107
|
+
def edge_attr(self, rel: Relation) -> dict[str, str]:
|
|
108
|
+
label = rel._vis_str()
|
|
109
|
+
d = {
|
|
110
|
+
'label': label,
|
|
111
|
+
'dir': 'none' if rel.symmetric else 'forward',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Update with rel_type-specific attributes and return
|
|
115
|
+
d.update(rel._vis_attrs())
|
|
116
|
+
return d
|
|
117
|
+
|
|
118
|
+
# Default drawer - this is used by the EntryGraph .draw() and .show() methods.
|
|
119
|
+
defaultdrawer = EntryGraphDrawer()
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, ClassVar, Generator
|
|
4
|
+
|
|
5
|
+
import rustworkx as rx
|
|
6
|
+
|
|
7
|
+
from chemrecon.entrygraph.draw import defaultdrawer
|
|
8
|
+
from chemrecon.entrygraph.explore_procedure import ExploreProcedure
|
|
9
|
+
from chemrecon.schema import Entry, Relation
|
|
10
|
+
|
|
11
|
+
from chemrecon import connection as connection
|
|
12
|
+
|
|
13
|
+
type VertexIndex = int
|
|
14
|
+
type EdgeIndex = int
|
|
15
|
+
type ReconID = int
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Vertices and Edges
|
|
19
|
+
# --------------------------------------------------------------------------------------------------------------
|
|
20
|
+
# Vertex and Edge objects are used as the payload for the RustWorkX graph.
|
|
21
|
+
|
|
22
|
+
class VertexBase:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Vertex[E: Entry](VertexBase):
|
|
27
|
+
""" A database entry in a graph context.
|
|
28
|
+
None is used as the type parameter for artificial vertices (those not corresponding to a DB object).
|
|
29
|
+
A vertex is initial if its generation is 0.
|
|
30
|
+
"""
|
|
31
|
+
entry: E #: The entry represented by this vertex
|
|
32
|
+
recon_id: ReconID #:
|
|
33
|
+
vertex_index: VertexIndex #: Index in the graph representation.
|
|
34
|
+
generation: int #: Distance from initial entries.
|
|
35
|
+
|
|
36
|
+
def __init__(self, entry: E, generation: int):
|
|
37
|
+
assert entry.recon_id is not None
|
|
38
|
+
self.entry = entry
|
|
39
|
+
self.recon_id = entry.recon_id
|
|
40
|
+
self.generation = generation
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class EdgeBase:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Edge[R: Relation | None](EdgeBase):
|
|
48
|
+
""" A database relation in a graph context.
|
|
49
|
+
None is used as the type parameter for artificial edges (those not corresponding to a DB object).
|
|
50
|
+
"""
|
|
51
|
+
relation: R #:
|
|
52
|
+
recon_id_1: ReconID #:
|
|
53
|
+
recon_id_2: ReconID #:
|
|
54
|
+
edge_index: EdgeIndex #: Index in the graph representation
|
|
55
|
+
|
|
56
|
+
def __init__(self, relation: R):
|
|
57
|
+
self.relation = relation
|
|
58
|
+
self.recon_id_1 = relation.recon_id_1
|
|
59
|
+
self.recon_id_2 = relation.recon_id_2
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Special case graph vertices and edges which do not represent database entries or relations
|
|
63
|
+
class SourceVertexArtificial(VertexBase):
|
|
64
|
+
""" Artificial vertex used as a source"""
|
|
65
|
+
|
|
66
|
+
def __init__(self):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SourceEdgeArtificial(EdgeBase):
|
|
71
|
+
""" Artificial edge from the SourceVertex to initial vertices in the graph. """
|
|
72
|
+
|
|
73
|
+
def __init__(self):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ReturnEdgeArtificial(EdgeBase):
|
|
78
|
+
""" Temporary edge added from sink vertices back to the SourceVertex for pagerank purposes. """
|
|
79
|
+
|
|
80
|
+
def __init__(self):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Generic EntryGraph
|
|
85
|
+
# --------------------------------------------------------------------------------------------------------------
|
|
86
|
+
class EntryGraph:
|
|
87
|
+
"""
|
|
88
|
+
Represents a directed graph structure of entries and relations.
|
|
89
|
+
|
|
90
|
+
:ivar g: RustWorkX directed graph object used to store vertices and edges.
|
|
91
|
+
:type g: rx.PyDiGraph
|
|
92
|
+
:ivar index_entry: Maps Entry objects to corresponding vertex indices.
|
|
93
|
+
:type index_entry: dict[Entry, VertexIndex]
|
|
94
|
+
:ivar index_reconid: Maps (Entry type, ReconID) tuples to vertex indices.
|
|
95
|
+
:type index_reconid: dict[tuple[type[Entry], ReconID], VertexIndex]
|
|
96
|
+
:ivar index_relation: Maps Relation objects to corresponding edge indices.
|
|
97
|
+
:type index_relation: dict[Relation, EdgeIndex]
|
|
98
|
+
:ivar initial_entries: Set of Entry objects that serve as the initial vertices in the graph.
|
|
99
|
+
:type initial_entries: set[Entry]
|
|
100
|
+
:ivar initial_vertices: Set of vertex indices corresponding to the initial entries.
|
|
101
|
+
:type initial_vertices: set[VertexIndex]
|
|
102
|
+
:ivar parent_entrygraph: Reference to the parent EntryGraph if this graph
|
|
103
|
+
is a subgraph view.
|
|
104
|
+
:type parent_entrygraph: Optional[EntryGraph]
|
|
105
|
+
"""
|
|
106
|
+
g: rx.PyDiGraph
|
|
107
|
+
|
|
108
|
+
# Indices for looking up vertices / edges based on the underlying entries
|
|
109
|
+
index_entry: dict[Entry, VertexIndex]
|
|
110
|
+
index_reconid: dict[tuple[type[Entry], ReconID], VertexIndex]
|
|
111
|
+
index_relation: dict[Relation, EdgeIndex]
|
|
112
|
+
|
|
113
|
+
# Misc
|
|
114
|
+
initial_entries: set[Entry]
|
|
115
|
+
initial_vertices: set[VertexIndex]
|
|
116
|
+
|
|
117
|
+
# Parentage
|
|
118
|
+
parent_entrygraph: Optional[EntryGraph] # If created as a subgraph view, gives the parent graph
|
|
119
|
+
|
|
120
|
+
# List of finished ExplorationProcedure objects to call when exploring
|
|
121
|
+
explore_procedures: ClassVar[list[ExploreProcedure]]
|
|
122
|
+
post_explore_procedures: ClassVar[list[ExploreProcedure]]
|
|
123
|
+
transitive_subprocedures: ClassVar[dict[type[Relation], tuple[ExploreProcedure, ExploreProcedure]]]
|
|
124
|
+
|
|
125
|
+
# TODO scoring as abstract class variables
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self, # TODO specification refactor
|
|
130
|
+
initial_entries: set[Entry]
|
|
131
|
+
):
|
|
132
|
+
""" Initialize an entrygraph with a set of initial vertices.
|
|
133
|
+
"""
|
|
134
|
+
# Consistency check
|
|
135
|
+
# TODO init vertices in allowed types
|
|
136
|
+
|
|
137
|
+
# Create
|
|
138
|
+
self.index_entry = dict()
|
|
139
|
+
self.index_reconid = dict()
|
|
140
|
+
self.index_relation = dict()
|
|
141
|
+
|
|
142
|
+
# Create graph
|
|
143
|
+
self.g = rx.PyDiGraph(multigraph = True)
|
|
144
|
+
|
|
145
|
+
# Add initial vertices
|
|
146
|
+
# The initial vertices may not have recon-ids attached, so we add those by DB lookup
|
|
147
|
+
for entry in initial_entries:
|
|
148
|
+
if entry.recon_id is None:
|
|
149
|
+
res = connection.handler.get_entry_by_index(entry)
|
|
150
|
+
if res is None:
|
|
151
|
+
continue
|
|
152
|
+
entry.recon_id = res.recon_id
|
|
153
|
+
|
|
154
|
+
# If no initial vertices have recon ids, abort
|
|
155
|
+
if all(e.recon_id is None for e in initial_entries):
|
|
156
|
+
raise ValueError('No initial entries have recon ids.')
|
|
157
|
+
|
|
158
|
+
self.initial_entries = initial_entries
|
|
159
|
+
self.initial_vertices = {
|
|
160
|
+
self.add_vertex(e, generation = 0)
|
|
161
|
+
for e in initial_entries
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Getters
|
|
165
|
+
# ----------------------------------------------------------------------------------------------------------
|
|
166
|
+
def get_vertex_index_by_recon_id(self, entry_type: type[Entry], recon_id: ReconID) -> Optional[VertexIndex]:
|
|
167
|
+
"""
|
|
168
|
+
Retrieve the vertex index associated with the given entry type and recon ID.
|
|
169
|
+
If no matching vertex index is found, the method returns None.
|
|
170
|
+
|
|
171
|
+
:param entry_type: The class type of the entry being queried.
|
|
172
|
+
:type entry_type: type[Entry]
|
|
173
|
+
:param recon_id: The recon ID that uniquely identifies the entry.
|
|
174
|
+
:type recon_id: ReconID
|
|
175
|
+
:return: The vertex index corresponding to the given entry type and recon ID, or None
|
|
176
|
+
if not found.
|
|
177
|
+
:rtype: Optional[VertexIndex]
|
|
178
|
+
"""
|
|
179
|
+
return self.index_reconid.get((entry_type, recon_id), None)
|
|
180
|
+
|
|
181
|
+
def get_vertex_by_entry(self, entry: Entry) -> Optional[VertexIndex]:
|
|
182
|
+
"""
|
|
183
|
+
Retrieves the vertex index associated with a given entry and returns None if it does not exist.
|
|
184
|
+
|
|
185
|
+
:param entry: The entry object to search for in the index map.
|
|
186
|
+
:type entry: Entry
|
|
187
|
+
:return: The vertex index associated with the given entry, or None if the entry
|
|
188
|
+
does not exist in the mapping.
|
|
189
|
+
:rtype: Optional[VertexIndex]
|
|
190
|
+
"""
|
|
191
|
+
return self.index_entry.get(entry, None)
|
|
192
|
+
|
|
193
|
+
def get_vertex_by_vertex_index(self, vertex_index: VertexIndex) -> Optional[Vertex]:
|
|
194
|
+
"""
|
|
195
|
+
Retrieve the vertex corresponding to the given vertex index, returns None if it does not exist.
|
|
196
|
+
:param vertex_index: Index of the vertex to retrieve.
|
|
197
|
+
:type vertex_index: VertexIndex
|
|
198
|
+
:return: The vertex associated with the given index or None if it does not exist.
|
|
199
|
+
:rtype: Optional[Vertex]
|
|
200
|
+
"""
|
|
201
|
+
return self.g.get_node_data(vertex_index)
|
|
202
|
+
|
|
203
|
+
def get_out_edges_of_vertex(self, vertex_index: VertexIndex) -> list[tuple[Edge, Vertex]]:
|
|
204
|
+
"""
|
|
205
|
+
Retrieve all outgoing edges of a vertex along with the corresponding target
|
|
206
|
+
vertices.
|
|
207
|
+
|
|
208
|
+
This method provides a list of tuples where each tuple contains the edge data
|
|
209
|
+
and the target vertex data for the edges originating from the specified
|
|
210
|
+
vertex. This function is useful for analysing the outgoing connections
|
|
211
|
+
and relationships in a graph data structure.
|
|
212
|
+
|
|
213
|
+
:param vertex_index: Index of the vertex whose outgoing edges are to be queried.
|
|
214
|
+
:type vertex_index: VertexIndex
|
|
215
|
+
:return: A list of tuples with each tuple containing edge data and the target
|
|
216
|
+
vertex data.
|
|
217
|
+
:rtype: list[tuple[Edge, Vertex]]
|
|
218
|
+
"""
|
|
219
|
+
return [
|
|
220
|
+
(edge_data, self.g.get_node_data(target_index))
|
|
221
|
+
for _, target_index, edge_data in self.g.out_edges(vertex_index)
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
# Iterators
|
|
225
|
+
def vertices(self) -> Generator[Vertex, None, None]:
|
|
226
|
+
""" Generator over vertices (entries). """
|
|
227
|
+
yield from self.g.nodes()
|
|
228
|
+
|
|
229
|
+
def edges(self) -> Generator[Edge, None, None]:
|
|
230
|
+
""" Generator over edges (relations). """
|
|
231
|
+
yield from self.g.edges()
|
|
232
|
+
|
|
233
|
+
# Adders
|
|
234
|
+
# ----------------------------------------------------------------------------------------------------------
|
|
235
|
+
def add_vertex(self, entry: Entry, generation: int = -1) -> VertexIndex:
|
|
236
|
+
""" Add a new entry to the graph. If it already exists, return the index.
|
|
237
|
+
"""
|
|
238
|
+
if entry.recon_id is None:
|
|
239
|
+
raise ValueError('Cannot construct vertex from entry without reconid.')
|
|
240
|
+
lookup = self.get_vertex_index_by_recon_id(type(entry), entry.recon_id)
|
|
241
|
+
if lookup is not None:
|
|
242
|
+
return lookup
|
|
243
|
+
|
|
244
|
+
# Add the vertex
|
|
245
|
+
vertex = Vertex(entry, generation = generation)
|
|
246
|
+
v_index: VertexIndex = self.g.add_node(vertex)
|
|
247
|
+
self.index_reconid[(type(entry), entry.recon_id)] = v_index
|
|
248
|
+
self.index_entry[entry] = v_index
|
|
249
|
+
vertex.vertex_index = v_index
|
|
250
|
+
return v_index
|
|
251
|
+
|
|
252
|
+
def add_edge(
|
|
253
|
+
self,
|
|
254
|
+
source_v_index: VertexIndex,
|
|
255
|
+
target_v_index: VertexIndex,
|
|
256
|
+
relation: Relation
|
|
257
|
+
) -> EdgeIndex:
|
|
258
|
+
""" Add a relation between the specified source and target vertices
|
|
259
|
+
"""
|
|
260
|
+
# Try returning existing edge
|
|
261
|
+
try:
|
|
262
|
+
return self.index_relation[relation]
|
|
263
|
+
except KeyError:
|
|
264
|
+
# Does not already exist
|
|
265
|
+
edge = Edge(relation)
|
|
266
|
+
edge_index: EdgeIndex = self.g.add_edge(source_v_index, target_v_index, edge)
|
|
267
|
+
edge.edge_index = edge_index
|
|
268
|
+
self.index_relation[relation] = edge_index
|
|
269
|
+
return edge_index
|
|
270
|
+
|
|
271
|
+
def add_vertex_from[T1: Entry, T2: Entry](
|
|
272
|
+
self,
|
|
273
|
+
from_index: VertexIndex,
|
|
274
|
+
relation: Relation[T1, T2],
|
|
275
|
+
entry: T2,
|
|
276
|
+
generation: int
|
|
277
|
+
) -> VertexIndex:
|
|
278
|
+
""" Adds a new entry vertex along with a relation. Returns vertex_index of the newly creted vertex.
|
|
279
|
+
If the vertex already exists, return add the edge and return the vertex index.
|
|
280
|
+
"""
|
|
281
|
+
target_v_index = self.add_vertex(entry, generation = generation)
|
|
282
|
+
self.add_edge(from_index, target_v_index, relation)
|
|
283
|
+
return target_v_index
|
|
284
|
+
|
|
285
|
+
# Draw
|
|
286
|
+
# ----------------------------------------------------------------------------------------------------------
|
|
287
|
+
def draw(
|
|
288
|
+
self,
|
|
289
|
+
filename: str = None,
|
|
290
|
+
filetype: str = 'jpg',
|
|
291
|
+
scores: Optional[dict[Entry, float]] = None
|
|
292
|
+
):
|
|
293
|
+
""" Draw using default settings, returning an image, or write to the disk if the filename is specified.
|
|
294
|
+
For more settings, create an EntryGraphDrawer instance.
|
|
295
|
+
"""
|
|
296
|
+
return defaultdrawer.draw(self, filename = filename, filetype = filetype, scores = scores)
|
|
297
|
+
|
|
298
|
+
def show(self, scores: Optional[dict[Entry, float]] = None):
|
|
299
|
+
""" Same as draw, but displays the image in a window instead of saving to disk.
|
|
300
|
+
"""
|
|
301
|
+
return self.draw(scores = scores).show()
|