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.
Files changed (86) hide show
  1. chemrecon/__init__.py +73 -0
  2. chemrecon/chem/__init__.py +0 -0
  3. chemrecon/chem/chemreaction.py +223 -0
  4. chemrecon/chem/constant_compounds.py +3 -0
  5. chemrecon/chem/create_mol.py +91 -0
  6. chemrecon/chem/elements.py +141 -0
  7. chemrecon/chem/gml/__init__.py +0 -0
  8. chemrecon/chem/gml/gml.py +324 -0
  9. chemrecon/chem/gml/gml_reactant_matching.py +130 -0
  10. chemrecon/chem/gml/gml_to_rdk.py +217 -0
  11. chemrecon/chem/mol.py +483 -0
  12. chemrecon/chem/sumformula.py +120 -0
  13. chemrecon/connection.py +97 -0
  14. chemrecon/core/__init__.py +0 -0
  15. chemrecon/core/id_types.py +687 -0
  16. chemrecon/core/ontology.py +209 -0
  17. chemrecon/core/populate_query_handler.py +336 -0
  18. chemrecon/core/query_handler.py +587 -0
  19. chemrecon/database/__init__.py +1 -0
  20. chemrecon/database/connect.py +63 -0
  21. chemrecon/database/connection_params/chemrecon_pub.dbinfo +5 -0
  22. chemrecon/database/connection_params/local_docker_dev.dbinfo +5 -0
  23. chemrecon/database/connection_params/local_docker_init.dbinfo +5 -0
  24. chemrecon/database/connection_params/local_docker_pub.dbinfo +5 -0
  25. chemrecon/database/params.py +88 -0
  26. chemrecon/entrygraph/draw.py +119 -0
  27. chemrecon/entrygraph/entrygraph.py +301 -0
  28. chemrecon/entrygraph/explorationprotocol.py +199 -0
  29. chemrecon/entrygraph/explore.py +421 -0
  30. chemrecon/entrygraph/explore_procedure.py +183 -0
  31. chemrecon/entrygraph/filter.py +88 -0
  32. chemrecon/entrygraph/scoring.py +141 -0
  33. chemrecon/query/__init__.py +26 -0
  34. chemrecon/query/create_entry.py +86 -0
  35. chemrecon/query/default_protocols.py +57 -0
  36. chemrecon/query/find_entry.py +84 -0
  37. chemrecon/query/get_relations.py +143 -0
  38. chemrecon/query/get_structures_from_compound.py +65 -0
  39. chemrecon/schema/__init__.py +86 -0
  40. chemrecon/schema/db_object.py +363 -0
  41. chemrecon/schema/direction.py +10 -0
  42. chemrecon/schema/entry_types/__init__.py +0 -0
  43. chemrecon/schema/entry_types/aam.py +34 -0
  44. chemrecon/schema/entry_types/aam_repr.py +37 -0
  45. chemrecon/schema/entry_types/compound.py +52 -0
  46. chemrecon/schema/entry_types/enzyme.py +49 -0
  47. chemrecon/schema/entry_types/molstructure.py +64 -0
  48. chemrecon/schema/entry_types/molstructure_repr.py +41 -0
  49. chemrecon/schema/entry_types/reaction.py +57 -0
  50. chemrecon/schema/enums.py +154 -0
  51. chemrecon/schema/procedural_relation_entrygraph.py +66 -0
  52. chemrecon/schema/relation_types_composed/__init__.py +0 -0
  53. chemrecon/schema/relation_types_composed/compound_has_molstructure_relation.py +59 -0
  54. chemrecon/schema/relation_types_composed/reaction_has_aam_relation.py +50 -0
  55. chemrecon/schema/relation_types_procedural/__init__.py +0 -0
  56. chemrecon/schema/relation_types_procedural/aam_convert_relation.py +69 -0
  57. chemrecon/schema/relation_types_procedural/compound_select_structure_proceduralrelation.py +36 -0
  58. chemrecon/schema/relation_types_procedural/compound_similarlity_proceduralrelation.py +1 -0
  59. chemrecon/schema/relation_types_procedural/molstructure_convert_relation.py +49 -0
  60. chemrecon/schema/relation_types_procedural/reaction_select_aam_proceduralrelation.py +38 -0
  61. chemrecon/schema/relation_types_procedural/reaction_similarity_proceduralrelation.py +1 -0
  62. chemrecon/schema/relation_types_source/__init__.py +0 -0
  63. chemrecon/schema/relation_types_source/aam_involves_molstructure_relation.py +77 -0
  64. chemrecon/schema/relation_types_source/aam_repr_involves_molstructure_repr_relation.py +79 -0
  65. chemrecon/schema/relation_types_source/compound_has_structure_representation_relation.py +33 -0
  66. chemrecon/schema/relation_types_source/compound_reference_relation.py +34 -0
  67. chemrecon/schema/relation_types_source/molstructure_standardisation_relation.py +71 -0
  68. chemrecon/schema/relation_types_source/ontology/__init__.py +0 -0
  69. chemrecon/schema/relation_types_source/ontology/compound_ontology.py +369 -0
  70. chemrecon/schema/relation_types_source/ontology/enzyme_ontology.py +142 -0
  71. chemrecon/schema/relation_types_source/ontology/reaction_ontology.py +140 -0
  72. chemrecon/schema/relation_types_source/reaction_has_aam_representation_relation.py +34 -0
  73. chemrecon/schema/relation_types_source/reaction_has_enzyme_relation.py +71 -0
  74. chemrecon/schema/relation_types_source/reaction_involves_compound_relation.py +69 -0
  75. chemrecon/schema/relation_types_source/reaction_reference_relation.py +33 -0
  76. chemrecon/scripts/initialize_database.py +494 -0
  77. chemrecon/utils/copy_signature.py +10 -0
  78. chemrecon/utils/encodeable_list.py +11 -0
  79. chemrecon/utils/get_id_type.py +70 -0
  80. chemrecon/utils/hungarian.py +31 -0
  81. chemrecon/utils/reactant_matching.py +168 -0
  82. chemrecon/utils/rxnutils.py +44 -0
  83. chemrecon/utils/set_cwd.py +12 -0
  84. chemrecon-0.1.1.dist-info/METADATA +143 -0
  85. chemrecon-0.1.1.dist-info/RECORD +86 -0
  86. 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()