Coverage for pyTooling / Graph / __init__.py: 76%

1211 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-21 22:22 +0000

1# ==================================================================================================================== # 

2# _____ _ _ ____ _ # 

3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / ___|_ __ __ _ _ __ | |__ # 

4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` || | _| '__/ _` | '_ \| '_ \ # 

5# | |_) | |_| || | (_) | (_) | | | | | | (_| || |_| | | | (_| | |_) | | | | # 

6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____|_| \__,_| .__/|_| |_| # 

7# |_| |___/ |___/ |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2017-2025 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32A powerful **graph** data structure for Python. 

33 

34Graph algorithms using all vertices are provided as methods on the graph instance. Whereas graph algorithms based on a 

35starting vertex are provided as methods on a vertex. 

36 

37.. admonition:: Example Graph 

38 

39 .. mermaid:: 

40 :caption: A directed graph with backward-edges denoted by dotted vertex relations. 

41 

42 %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%% 

43 graph LR 

44 A(A); B(B); C(C); D(D); E(E); F(F) ; G(G); H(H); I(I) 

45 

46 A --> B --> E 

47 G --> F 

48 A --> C --> G --> H --> D 

49 D -.-> A 

50 D & F -.-> B 

51 I ---> E --> F --> D 

52 

53 classDef node fill:#eee,stroke:#777,font-size:smaller; 

54""" 

55import heapq 

56from collections import deque 

57from itertools import chain 

58from typing import TypeVar, Generic, List, Tuple, Dict, Set, Deque, Union, Optional as Nullable 

59from typing import Callable, Iterator as typing_Iterator, Generator, Iterable, Mapping, Hashable 

60 

61try: 

62 from pyTooling.Decorators import export, readonly 

63 from pyTooling.MetaClasses import ExtendedType 

64 from pyTooling.Exceptions import ToolingException 

65 from pyTooling.Common import getFullyQualifiedName 

66 from pyTooling.Tree import Node 

67except (ImportError, ModuleNotFoundError): # pragma: no cover 

68 print("[pyTooling.Graph] Could not import from 'pyTooling.*'!") 

69 

70 try: 

71 from Decorators import export, readonly 

72 from MetaClasses import ExtendedType, mixin 

73 from Exceptions import ToolingException 

74 from Common import getFullyQualifiedName 

75 from Tree import Node 

76 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover 

77 print("[pyTooling.Graph] Could not import directly!") 

78 raise ex 

79 

80 

81DictKeyType = TypeVar("DictKeyType", bound=Hashable) 

82"""A type variable for dictionary keys.""" 

83 

84DictValueType = TypeVar("DictValueType") 

85"""A type variable for dictionary values.""" 

86 

87IDType = TypeVar("IDType", bound=Hashable) 

88"""A type variable for an ID.""" 

89 

90WeightType = TypeVar("WeightType", bound=Union[int, float]) 

91"""A type variable for a weight.""" 

92 

93ValueType = TypeVar("ValueType") 

94"""A type variable for a value.""" 

95 

96VertexIDType = TypeVar("VertexIDType", bound=Hashable) 

97"""A type variable for a vertex's ID.""" 

98 

99VertexWeightType = TypeVar("VertexWeightType", bound=Union[int, float]) 

100"""A type variable for a vertex's weight.""" 

101 

102VertexValueType = TypeVar("VertexValueType") 

103"""A type variable for a vertex's value.""" 

104 

105VertexDictKeyType = TypeVar("VertexDictKeyType", bound=Hashable) 

106"""A type variable for a vertex's dictionary keys.""" 

107 

108VertexDictValueType = TypeVar("VertexDictValueType") 

109"""A type variable for a vertex's dictionary values.""" 

110 

111EdgeIDType = TypeVar("EdgeIDType", bound=Hashable) 

112"""A type variable for an edge's ID.""" 

113 

114EdgeWeightType = TypeVar("EdgeWeightType", bound=Union[int, float]) 

115"""A type variable for an edge's weight.""" 

116 

117EdgeValueType = TypeVar("EdgeValueType") 

118"""A type variable for an edge's value.""" 

119 

120EdgeDictKeyType = TypeVar("EdgeDictKeyType", bound=Hashable) 

121"""A type variable for an edge's dictionary keys.""" 

122 

123EdgeDictValueType = TypeVar("EdgeDictValueType") 

124"""A type variable for an edge's dictionary values.""" 

125 

126LinkIDType = TypeVar("LinkIDType", bound=Hashable) 

127"""A type variable for an link's ID.""" 

128 

129LinkWeightType = TypeVar("LinkWeightType", bound=Union[int, float]) 

130"""A type variable for an link's weight.""" 

131 

132LinkValueType = TypeVar("LinkValueType") 

133"""A type variable for an link's value.""" 

134 

135LinkDictKeyType = TypeVar("LinkDictKeyType", bound=Hashable) 

136"""A type variable for an link's dictionary keys.""" 

137 

138LinkDictValueType = TypeVar("LinkDictValueType") 

139"""A type variable for an link's dictionary values.""" 

140 

141ComponentDictKeyType = TypeVar("ComponentDictKeyType", bound=Hashable) 

142"""A type variable for a component's dictionary keys.""" 

143 

144ComponentDictValueType = TypeVar("ComponentDictValueType") 

145"""A type variable for a component's dictionary values.""" 

146 

147SubgraphDictKeyType = TypeVar("SubgraphDictKeyType", bound=Hashable) 

148"""A type variable for a component's dictionary keys.""" 

149 

150SubgraphDictValueType = TypeVar("SubgraphDictValueType") 

151"""A type variable for a component's dictionary values.""" 

152 

153ViewDictKeyType = TypeVar("ViewDictKeyType", bound=Hashable) 

154"""A type variable for a component's dictionary keys.""" 

155 

156ViewDictValueType = TypeVar("ViewDictValueType") 

157"""A type variable for a component's dictionary values.""" 

158 

159GraphDictKeyType = TypeVar("GraphDictKeyType", bound=Hashable) 

160"""A type variable for a graph's dictionary keys.""" 

161 

162GraphDictValueType = TypeVar("GraphDictValueType") 

163"""A type variable for a graph's dictionary values.""" 

164 

165 

166@export 

167class GraphException(ToolingException): 

168 """Base exception of all exceptions raised by :mod:`pyTooling.Graph`.""" 

169 

170 

171@export 

172class InternalError(GraphException): 

173 """ 

174 The exception is raised when a data structure corruption is detected. 

175 

176 .. danger:: 

177 

178 This exception should never be raised. 

179 

180 If so, please create an issue at GitHub so the data structure corruption can be investigated and fixed. |br| 

181 `⇒ Bug Tracker at GitHub <https://github.com/pyTooling/pyTooling/issues>`__ 

182 """ 

183 

184 

185@export 

186class NotInSameGraph(GraphException): 

187 """The exception is raised when creating an edge between two vertices, but these are not in the same graph.""" 

188 

189 

190@export 

191class DuplicateVertexError(GraphException): 

192 """The exception is raised when the vertex already exists in the graph.""" 

193 

194 

195@export 

196class DuplicateEdgeError(GraphException): 

197 """The exception is raised when the edge already exists in the graph.""" 

198 

199 

200@export 

201class DestinationNotReachable(GraphException): 

202 """The exception is raised when a destination vertex is not reachable.""" 

203 

204 

205@export 

206class NotATreeError(GraphException): 

207 """ 

208 The exception is raised when a subgraph is not a tree. 

209 

210 Either the subgraph has a cycle (backward edge) or links between branches (cross-edge). 

211 """ 

212 

213 

214@export 

215class CycleError(GraphException): 

216 """The exception is raised when a not permitted cycle is found.""" 

217 

218 

219@export 

220class Base( 

221 Generic[DictKeyType, DictValueType], 

222 metaclass=ExtendedType, slots=True 

223): 

224 _dict: Dict[DictKeyType, DictValueType] #: A dictionary to store arbitrary key-value-pairs. 

225 

226 def __init__( 

227 self, 

228 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

229 ) -> None: 

230 """ 

231 .. todo:: GRAPH::Base::init Needs documentation. 

232 

233 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

234 """ 

235 self._dict = {key: value for key, value in keyValuePairs.items()} if keyValuePairs is not None else {} 

236 

237 def __del__(self): 

238 """ 

239 .. todo:: GRAPH::Base::del Needs documentation. 

240 

241 """ 

242 try: 

243 del self._dict 

244 except AttributeError: 

245 pass 

246 

247 def Delete(self) -> None: 

248 self._dict = None 

249 

250 def __getitem__(self, key: DictKeyType) -> DictValueType: 

251 """ 

252 Read a vertex's attached attributes (key-value-pairs) by key. 

253 

254 :param key: The key to look for. 

255 :returns: The value associated to the given key. 

256 """ 

257 return self._dict[key] 

258 

259 def __setitem__(self, key: DictKeyType, value: DictValueType) -> None: 

260 """ 

261 Create or update a vertex's attached attributes (key-value-pairs) by key. 

262 

263 If a key doesn't exist yet, a new key-value-pair is created. 

264 

265 :param key: The key to create or update. 

266 :param value: The value to associate to the given key. 

267 """ 

268 self._dict[key] = value 

269 

270 def __delitem__(self, key: DictKeyType) -> None: 

271 """ 

272 Remove an entry from vertex's attached attributes (key-value-pairs) by key. 

273 

274 :param key: The key to remove. 

275 :raises KeyError: If key doesn't exist in the vertex's attributes. 

276 """ 

277 del self._dict[key] 

278 

279 def __contains__(self, key: DictKeyType) -> bool: 

280 """ 

281 Checks if the key is an attached attribute (key-value-pairs) on this vertex. 

282 

283 :param key: The key to check. 

284 :returns: ``True``, if the key is an attached attribute. 

285 """ 

286 return key in self._dict 

287 

288 def __len__(self) -> int: 

289 """ 

290 Returns the number of attached attributes (key-value-pairs) on this vertex. 

291 

292 :returns: Number of attached attributes. 

293 """ 

294 return len(self._dict) 

295 

296 

297@export 

298class BaseWithIDValueAndWeight( 

299 Base[DictKeyType, DictValueType], 

300 Generic[IDType, ValueType, WeightType, DictKeyType, DictValueType] 

301): 

302 _id: Nullable[IDType] #: Field storing the object's Identifier. 

303 _value: Nullable[ValueType] #: Field storing the object's value of any type. 

304 _weight: Nullable[WeightType] #: Field storing the object's weight. 

305 

306 def __init__( 

307 self, 

308 identifier: Nullable[IDType] = None, 

309 value: Nullable[ValueType] = None, 

310 weight: Nullable[WeightType] = None, 

311 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

312 ) -> None: 

313 """ 

314 .. todo:: GRAPH::Vertex::init Needs documentation. 

315 

316 :param identifier: The optional unique ID. 

317 :param value: The optional value. 

318 :param weight: The optional weight. 

319 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

320 """ 

321 super().__init__(keyValuePairs) 

322 

323 self._id = identifier 

324 self._value = value 

325 self._weight = weight 

326 

327 @readonly 

328 def ID(self) -> Nullable[IDType]: 

329 """ 

330 Read-only property to access the unique ID (:attr:`_id`). 

331 

332 If no ID was given at creation time, ID returns ``None``. 

333 

334 :returns: Unique ID, if ID was given at creation time, else ``None``. 

335 """ 

336 return self._id 

337 

338 @property 

339 def Value(self) -> ValueType: 

340 """ 

341 Property to get and set the value (:attr:`_value`). 

342 

343 :returns: The value. 

344 """ 

345 return self._value 

346 

347 @Value.setter 

348 def Value(self, value: ValueType) -> None: 

349 self._value = value 

350 

351 @property 

352 def Weight(self) -> Nullable[EdgeWeightType]: 

353 """ 

354 Property to get and set the weight (:attr:`_weight`) of an edge. 

355 

356 :returns: The weight of an edge. 

357 """ 

358 return self._weight 

359 

360 @Weight.setter 

361 def Weight(self, value: Nullable[EdgeWeightType]) -> None: 

362 self._weight = value 

363 

364 

365@export 

366class BaseWithName( 

367 Base[DictKeyType, DictValueType], 

368 Generic[DictKeyType, DictValueType] 

369): 

370 _name: Nullable[str] #: Field storing the object's name. 

371 

372 def __init__( 

373 self, 

374 name: Nullable[str] = None, 

375 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None, 

376 ) -> None: 

377 """ 

378 .. todo:: GRAPH::BaseWithName::init Needs documentation. 

379 

380 :param name: The optional name. 

381 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

382 """ 

383 if name is not None and not isinstance(name, str): 

384 ex = TypeError("Parameter 'name' is not of type 'str'.") 

385 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

386 raise ex 

387 

388 super().__init__(keyValuePairs) 

389 

390 self._name = name 

391 

392 @property 

393 def Name(self) -> Nullable[str]: 

394 """ 

395 Property to get and set the name (:attr:`_name`). 

396 

397 :returns: The value of a component. 

398 """ 

399 return self._name 

400 

401 @Name.setter 

402 def Name(self, value: str) -> None: 

403 if not isinstance(value, str): 

404 ex = TypeError("Name is not of type 'str'.") 

405 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

406 raise ex 

407 

408 self._name = value 

409 

410 

411@export 

412class BaseWithVertices( 

413 BaseWithName[DictKeyType, DictValueType], 

414 Generic[ 

415 DictKeyType, DictValueType, 

416 GraphDictKeyType, GraphDictValueType, 

417 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

418 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

419 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

420 ] 

421): 

422 _graph: 'Graph[GraphDictKeyType, GraphDictValueType,' \ 

423 'VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType,' \ 

424 'EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType,' \ 

425 'LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType' \ 

426 ']' #: Field storing a reference to the graph. 

427 _vertices: Set['Vertex[GraphDictKeyType, GraphDictValueType,' 

428 'VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType,' 

429 'EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType,' 

430 'LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType' 

431 ']'] #: Field storing a set of vertices. 

432 

433 def __init__( 

434 self, 

435 graph: 'Graph', 

436 name: Nullable[str] = None, 

437 vertices: Nullable[Iterable['Vertex']] = None, 

438 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

439 ) -> None: 

440 """ 

441 .. todo:: GRAPH::Component::init Needs documentation. 

442 

443 :param graph: The reference to the graph. 

444 :param name: The optional name. 

445 :param vertices: The optional list of vertices. 

446 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

447 """ 

448 if graph is None: 448 ↛ 449line 448 didn't jump to line 449 because the condition on line 448 was never true

449 raise ValueError("Parameter 'graph' is None.") 

450 elif not isinstance(graph, Graph): 450 ↛ 451line 450 didn't jump to line 451 because the condition on line 450 was never true

451 ex = TypeError("Parameter 'graph' is not of type 'Graph'.") 

452 ex.add_note(f"Got type '{getFullyQualifiedName(graph)}'.") 

453 raise ex 

454 

455 super().__init__(name, keyValuePairs) 

456 

457 self._graph = graph 

458 self._vertices = set() if vertices is None else {v for v in vertices} 

459 

460 def __del__(self): 

461 """ 

462 .. todo:: GRAPH::BaseWithVertices::del Needs documentation. 

463 

464 """ 

465 try: 

466 del self._vertices 

467 except AttributeError: 

468 pass 

469 

470 super().__del__() 

471 

472 @readonly 

473 def Graph(self) -> 'Graph': 

474 """ 

475 Read-only property to access the graph, this object is associated to (:attr:`_graph`). 

476 

477 :returns: The graph this object is associated to. 

478 """ 

479 return self._graph 

480 

481 @readonly 

482 def Vertices(self) -> Set['Vertex']: 

483 """ 

484 Read-only property to access the vertices in this component (:attr:`_vertices`). 

485 

486 :returns: The set of vertices in this component. 

487 """ 

488 return self._vertices 

489 

490 @readonly 

491 def VertexCount(self) -> int: 

492 """ 

493 Read-only property to access the number of vertices referenced by this object. 

494 

495 :returns: The number of vertices this object references. 

496 """ 

497 return len(self._vertices) 

498 

499 

500@export 

501class Vertex( 

502 BaseWithIDValueAndWeight[VertexIDType, VertexValueType, VertexWeightType, VertexDictKeyType, VertexDictValueType], 

503 Generic[ 

504 GraphDictKeyType, GraphDictValueType, 

505 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

506 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

507 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

508 ] 

509): 

510 """ 

511 A **vertex** can have a unique ID, a value and attached meta information as key-value-pairs. A vertex has references 

512 to inbound and outbound edges, thus a graph can be traversed in reverse. 

513 """ 

514 _graph: 'BaseGraph[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]' #: Field storing a reference to the graph. 

515 _subgraph: 'Subgraph[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]' #: Field storing a reference to the subgraph. 

516 _component: 'Component' 

517 _views: Dict[Hashable, 'View'] 

518 _inboundEdges: List['Edge[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]'] #: Field storing a list of inbound edges. 

519 _outboundEdges: List['Edge[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]'] #: Field storing a list of outbound edges. 

520 _inboundLinks: List['Link[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]'] #: Field storing a list of inbound links. 

521 _outboundLinks: List['Link[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]'] #: Field storing a list of outbound links. 

522 

523 def __init__( 

524 self, 

525 vertexID: Nullable[VertexIDType] = None, 

526 value: Nullable[VertexValueType] = None, 

527 weight: Nullable[VertexWeightType] = None, 

528 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None, 

529 graph: Nullable['Graph'] = None, 

530 subgraph: Nullable['Subgraph'] = None 

531 ) -> None: 

532 """ 

533 .. todo:: GRAPH::Vertex::init Needs documentation. 

534 

535 :param vertexID: The optional ID for the new vertex. 

536 :param value: The optional value for the new vertex. 

537 :param weight: The optional weight for the new vertex. 

538 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

539 :param graph: The optional reference to the graph. 

540 :param subgraph: undocumented 

541 """ 

542 if vertexID is not None and not isinstance(vertexID, Hashable): 542 ↛ 543line 542 didn't jump to line 543 because the condition on line 542 was never true

543 ex = TypeError("Parameter 'vertexID' is not of type 'VertexIDType'.") 

544 ex.add_note(f"Got type '{getFullyQualifiedName(vertexID)}'.") 

545 raise ex 

546 

547 super().__init__(vertexID, value, weight, keyValuePairs) 

548 

549 if subgraph is None: 

550 self._graph = graph if graph is not None else Graph() 

551 self._subgraph = None 

552 self._component = Component(self._graph, vertices=(self,)) 

553 

554 if vertexID is None: 

555 self._graph._verticesWithoutID.append(self) 

556 elif vertexID not in self._graph._verticesWithID: 

557 self._graph._verticesWithID[vertexID] = self 

558 else: 

559 raise DuplicateVertexError(f"Vertex ID '{vertexID}' already exists in this graph.") 

560 else: 

561 self._graph = subgraph._graph 

562 self._subgraph = subgraph 

563 self._component = Component(self._graph, vertices=(self,)) 

564 

565 if vertexID is None: 

566 subgraph._verticesWithoutID.append(self) 

567 elif vertexID not in subgraph._verticesWithID: 567 ↛ 570line 567 didn't jump to line 570 because the condition on line 567 was always true

568 subgraph._verticesWithID[vertexID] = self 

569 else: 

570 raise DuplicateVertexError(f"Vertex ID '{vertexID}' already exists in this subgraph.") 

571 

572 self._views = {} 

573 self._inboundEdges = [] 

574 self._outboundEdges = [] 

575 self._inboundLinks = [] 

576 self._outboundLinks = [] 

577 

578 def __del__(self): 

579 """ 

580 .. todo:: GRAPH::BaseEdge::del Needs documentation. 

581 

582 """ 

583 try: 

584 del self._views 

585 del self._inboundEdges 

586 del self._outboundEdges 

587 del self._inboundLinks 

588 del self._outboundLinks 

589 except AttributeError: 

590 pass 

591 

592 super().__del__() 

593 

594 def Delete(self) -> None: 

595 for edge in self._outboundEdges: 

596 edge._destination._inboundEdges.remove(edge) 

597 edge._Delete() 

598 for edge in self._inboundEdges: 

599 edge._source._outboundEdges.remove(edge) 

600 edge._Delete() 

601 for link in self._outboundLinks: 

602 link._destination._inboundLinks.remove(link) 

603 link._Delete() 

604 for link in self._inboundLinks: 

605 link._source._outboundLinks.remove(link) 

606 link._Delete() 

607 

608 if self._id is None: 

609 self._graph._verticesWithoutID.remove(self) 

610 else: 

611 del self._graph._verticesWithID[self._id] 

612 

613 # subgraph 

614 

615 # component 

616 

617 # views 

618 self._views = None 

619 self._inboundEdges = None 

620 self._outboundEdges = None 

621 self._inboundLinks = None 

622 self._outboundLinks = None 

623 

624 super().Delete() 

625 assert getrefcount(self) == 1 

626 

627 @readonly 

628 def Graph(self) -> 'Graph': 

629 """ 

630 Read-only property to access the graph, this vertex is associated to (:attr:`_graph`). 

631 

632 :returns: The graph this vertex is associated to. 

633 """ 

634 return self._graph 

635 

636 @readonly 

637 def Component(self) -> 'Component': 

638 """ 

639 Read-only property to access the component, this vertex is associated to (:attr:`_component`). 

640 

641 :returns: The component this vertex is associated to. 

642 """ 

643 return self._component 

644 

645 @readonly 

646 def InboundEdges(self) -> Tuple['Edge', ...]: 

647 """ 

648 Read-only property to get a tuple of inbound edges (:attr:`_inboundEdges`). 

649 

650 :returns: Tuple of inbound edges. 

651 """ 

652 return tuple(self._inboundEdges) 

653 

654 @readonly 

655 def OutboundEdges(self) -> Tuple['Edge', ...]: 

656 """ 

657 Read-only property to get a tuple of outbound edges (:attr:`_outboundEdges`). 

658 

659 :returns: Tuple of outbound edges. 

660 """ 

661 return tuple(self._outboundEdges) 

662 

663 @readonly 

664 def InboundLinks(self) -> Tuple['Link', ...]: 

665 """ 

666 Read-only property to get a tuple of inbound links (:attr:`_inboundLinks`). 

667 

668 :returns: Tuple of inbound links. 

669 """ 

670 return tuple(self._inboundLinks) 

671 

672 @readonly 

673 def OutboundLinks(self) -> Tuple['Link', ...]: 

674 """ 

675 Read-only property to get a tuple of outbound links (:attr:`_outboundLinks`). 

676 

677 :returns: Tuple of outbound links. 

678 """ 

679 return tuple(self._outboundLinks) 

680 

681 @readonly 

682 def EdgeCount(self) -> int: 

683 """ 

684 Read-only property to get the number of all edges (inbound and outbound). 

685 

686 :returns: Number of inbound and outbound edges. 

687 """ 

688 return len(self._inboundEdges) + len(self._outboundEdges) 

689 

690 @readonly 

691 def InboundEdgeCount(self) -> int: 

692 """ 

693 Read-only property to get the number of inbound edges. 

694 

695 :returns: Number of inbound edges. 

696 """ 

697 return len(self._inboundEdges) 

698 

699 @readonly 

700 def OutboundEdgeCount(self) -> int: 

701 """ 

702 Read-only property to get the number of outbound edges. 

703 

704 :returns: Number of outbound edges. 

705 """ 

706 return len(self._outboundEdges) 

707 

708 @readonly 

709 def LinkCount(self) -> int: 

710 """ 

711 Read-only property to get the number of all links (inbound and outbound). 

712 

713 :returns: Number of inbound and outbound links. 

714 """ 

715 return len(self._inboundLinks) + len(self._outboundLinks) 

716 

717 @readonly 

718 def InboundLinkCount(self) -> int: 

719 """ 

720 Read-only property to get the number of inbound links. 

721 

722 :returns: Number of inbound links. 

723 """ 

724 return len(self._inboundLinks) 

725 

726 @readonly 

727 def OutboundLinkCount(self) -> int: 

728 """ 

729 Read-only property to get the number of outbound links. 

730 

731 :returns: Number of outbound links. 

732 """ 

733 return len(self._outboundLinks) 

734 

735 @readonly 

736 def IsRoot(self) -> bool: 

737 """ 

738 Read-only property to check if this vertex is a root vertex in the graph. 

739 

740 A root has no inbound edges (no predecessor vertices). 

741 

742 :returns: ``True``, if this vertex is a root. 

743 

744 .. seealso:: 

745 

746 :meth:`IsLeaf` |br| 

747 |rarr| Check if a vertex is a leaf vertex in the graph. 

748 :meth:`Graph.IterateRoots <pyTooling.Graph.Graph.IterateRoots>` |br| 

749 |rarr| Iterate all roots of a graph. 

750 :meth:`Graph.IterateLeafs <pyTooling.Graph.Graph.IterateLeafs>` |br| 

751 |rarr| Iterate all leafs of a graph. 

752 """ 

753 return len(self._inboundEdges) == 0 

754 

755 @readonly 

756 def IsLeaf(self) -> bool: 

757 """ 

758 Read-only property to check if this vertex is a leaf vertex in the graph. 

759 

760 A leaf has no outbound edges (no successor vertices). 

761 

762 :returns: ``True``, if this vertex is a leaf. 

763 

764 .. seealso:: 

765 

766 :meth:`IsRoot` |br| 

767 |rarr| Check if a vertex is a root vertex in the graph. 

768 :meth:`Graph.IterateRoots <pyTooling.Graph.Graph.IterateRoots>` |br| 

769 |rarr| Iterate all roots of a graph. 

770 :meth:`Graph.IterateLeafs <pyTooling.Graph.Graph.IterateLeafs>` |br| 

771 |rarr| Iterate all leafs of a graph. 

772 """ 

773 return len(self._outboundEdges) == 0 

774 

775 @readonly 

776 def Predecessors(self) -> Tuple['Vertex', ...]: 

777 """ 

778 Read-only property to get a tuple of predecessor vertices. 

779 

780 :returns: Tuple of predecessor vertices. 

781 """ 

782 return tuple([edge.Source for edge in self._inboundEdges]) 

783 

784 @readonly 

785 def Successors(self) -> Tuple['Vertex', ...]: 

786 """ 

787 Read-only property to get a tuple of successor vertices. 

788 

789 :returns: Tuple of successor vertices. 

790 """ 

791 return tuple([edge.Destination for edge in self._outboundEdges]) 

792 

793 def EdgeToVertex( 

794 self, 

795 vertex: 'Vertex', 

796 edgeID: Nullable[EdgeIDType] = None, 

797 edgeWeight: Nullable[EdgeWeightType] = None, 

798 edgeValue: Nullable[VertexValueType] = None, 

799 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

800 ) -> 'Edge': 

801 """ 

802 Create an outbound edge from this vertex to the referenced vertex. 

803 

804 :param vertex: The vertex to be linked to. 

805 :param edgeID: The edge's optional ID for the new edge object. 

806 :param edgeWeight: The edge's optional weight for the new edge object. 

807 :param edgeValue: The edge's optional value for the new edge object. 

808 :param keyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new edge object. 

809 :returns: The edge object linking this vertex and the referenced vertex. 

810 

811 .. seealso:: 

812 

813 :meth:`EdgeFromVertex` |br| 

814 |rarr| Create an inbound edge from the referenced vertex to this vertex. 

815 :meth:`EdgeToNewVertex` |br| 

816 |rarr| Create a new vertex and link that vertex by an outbound edge from this vertex. 

817 :meth:`EdgeFromNewVertex` |br| 

818 |rarr| Create a new vertex and link that vertex by an inbound edge to this vertex. 

819 :meth:`LinkToVertex` |br| 

820 |rarr| Create an outbound link from this vertex to the referenced vertex. 

821 :meth:`LinkFromVertex` |br| 

822 |rarr| Create an inbound link from the referenced vertex to this vertex. 

823 

824 .. todo:: GRAPH::Vertex::EdgeToVertex Needs possible exceptions to be documented. 

825 """ 

826 if self._subgraph is vertex._subgraph: 

827 edge = Edge(self, vertex, edgeID, edgeValue, edgeWeight, keyValuePairs) 

828 

829 self._outboundEdges.append(edge) 

830 vertex._inboundEdges.append(edge) 

831 

832 if self._subgraph is None: 

833 # TODO: move into Edge? 

834 # TODO: keep _graph pointer in edge and then register edge on graph? 

835 if edgeID is None: 

836 self._graph._edgesWithoutID.append(edge) 

837 elif edgeID not in self._graph._edgesWithID: 

838 self._graph._edgesWithID[edgeID] = edge 

839 else: 

840 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

841 else: 

842 # TODO: keep _graph pointer in edge and then register edge on graph? 

843 if edgeID is None: 

844 self._subgraph._edgesWithoutID.append(edge) 

845 elif edgeID not in self._subgraph._edgesWithID: 

846 self._subgraph._edgesWithID[edgeID] = edge 

847 else: 

848 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this subgraph.") 

849 else: 

850 # FIXME: needs an error message 

851 raise GraphException() 

852 

853 return edge 

854 

855 def EdgeFromVertex( 

856 self, 

857 vertex: 'Vertex', 

858 edgeID: Nullable[EdgeIDType] = None, 

859 edgeWeight: Nullable[EdgeWeightType] = None, 

860 edgeValue: Nullable[VertexValueType] = None, 

861 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

862 ) -> 'Edge': 

863 """ 

864 Create an inbound edge from the referenced vertex to this vertex. 

865 

866 :param vertex: The vertex to be linked from. 

867 :param edgeID: The edge's optional ID for the new edge object. 

868 :param edgeWeight: The edge's optional weight for the new edge object. 

869 :param edgeValue: The edge's optional value for the new edge object. 

870 :param keyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new edge object. 

871 :returns: The edge object linking the referenced vertex and this vertex. 

872 

873 .. seealso:: 

874 

875 :meth:`EdgeToVertex` |br| 

876 |rarr| Create an outbound edge from this vertex to the referenced vertex. 

877 :meth:`EdgeToNewVertex` |br| 

878 |rarr| Create a new vertex and link that vertex by an outbound edge from this vertex. 

879 :meth:`EdgeFromNewVertex` |br| 

880 |rarr| Create a new vertex and link that vertex by an inbound edge to this vertex. 

881 :meth:`LinkToVertex` |br| 

882 |rarr| Create an outbound link from this vertex to the referenced vertex. 

883 :meth:`LinkFromVertex` |br| 

884 |rarr| Create an inbound link from the referenced vertex to this vertex. 

885 

886 .. todo:: GRAPH::Vertex::EdgeFromVertex Needs possible exceptions to be documented. 

887 """ 

888 if self._subgraph is vertex._subgraph: 888 ↛ 913line 888 didn't jump to line 913 because the condition on line 888 was always true

889 edge = Edge(vertex, self, edgeID, edgeValue, edgeWeight, keyValuePairs) 

890 

891 vertex._outboundEdges.append(edge) 

892 self._inboundEdges.append(edge) 

893 

894 if self._subgraph is None: 

895 # TODO: move into Edge? 

896 # TODO: keep _graph pointer in edge and then register edge on graph? 

897 if edgeID is None: 

898 self._graph._edgesWithoutID.append(edge) 

899 elif edgeID not in self._graph._edgesWithID: 

900 self._graph._edgesWithID[edgeID] = edge 

901 else: 

902 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

903 else: 

904 # TODO: keep _graph pointer in edge and then register edge on graph? 

905 if edgeID is None: 

906 self._subgraph._edgesWithoutID.append(edge) 

907 elif edgeID not in self._graph._edgesWithID: 

908 self._subgraph._edgesWithID[edgeID] = edge 

909 else: 

910 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

911 else: 

912 # FIXME: needs an error message 

913 raise GraphException() 

914 

915 return edge 

916 

917 def EdgeToNewVertex( 

918 self, 

919 vertexID: Nullable[VertexIDType] = None, 

920 vertexValue: Nullable[VertexValueType] = None, 

921 vertexWeight: Nullable[VertexWeightType] = None, 

922 vertexKeyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None, 

923 edgeID: Nullable[EdgeIDType] = None, 

924 edgeWeight: Nullable[EdgeWeightType] = None, 

925 edgeValue: Nullable[VertexValueType] = None, 

926 edgeKeyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

927 ) -> 'Edge': 

928 """ 

929 Create a new vertex and link that vertex by an outbound edge from this vertex. 

930 

931 :param vertexID: The new vertex' optional ID. 

932 :param vertexValue: The new vertex' optional value. 

933 :param vertexWeight: The new vertex' optional weight. 

934 :param vertexKeyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new vertex. 

935 :param edgeID: The edge's optional ID for the new edge object. 

936 :param edgeWeight: The edge's optional weight for the new edge object. 

937 :param edgeValue: The edge's optional value for the new edge object. 

938 :param edgeKeyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new edge object. 

939 :returns: The edge object linking this vertex and the created vertex. 

940 

941 .. seealso:: 

942 

943 :meth:`EdgeToVertex` |br| 

944 |rarr| Create an outbound edge from this vertex to the referenced vertex. 

945 :meth:`EdgeFromVertex` |br| 

946 |rarr| Create an inbound edge from the referenced vertex to this vertex. 

947 :meth:`EdgeFromNewVertex` |br| 

948 |rarr| Create a new vertex and link that vertex by an inbound edge to this vertex. 

949 :meth:`LinkToVertex` |br| 

950 |rarr| Create an outbound link from this vertex to the referenced vertex. 

951 :meth:`LinkFromVertex` |br| 

952 |rarr| Create an inbound link from the referenced vertex to this vertex. 

953 

954 .. todo:: GRAPH::Vertex::EdgeToNewVertex Needs possible exceptions to be documented. 

955 """ 

956 vertex = Vertex(vertexID, vertexValue, vertexWeight, vertexKeyValuePairs, graph=self._graph) # , component=self._component) 

957 

958 if self._subgraph is vertex._subgraph: 958 ↛ 983line 958 didn't jump to line 983 because the condition on line 958 was always true

959 edge = Edge(self, vertex, edgeID, edgeValue, edgeWeight, edgeKeyValuePairs) 

960 

961 self._outboundEdges.append(edge) 

962 vertex._inboundEdges.append(edge) 

963 

964 if self._subgraph is None: 964 ↛ 975line 964 didn't jump to line 975 because the condition on line 964 was always true

965 # TODO: move into Edge? 

966 # TODO: keep _graph pointer in edge and then register edge on graph? 

967 if edgeID is None: 967 ↛ 969line 967 didn't jump to line 969 because the condition on line 967 was always true

968 self._graph._edgesWithoutID.append(edge) 

969 elif edgeID not in self._graph._edgesWithID: 

970 self._graph._edgesWithID[edgeID] = edge 

971 else: 

972 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

973 else: 

974 # TODO: keep _graph pointer in edge and then register edge on graph? 

975 if edgeID is None: 

976 self._subgraph._edgesWithoutID.append(edge) 

977 elif edgeID not in self._graph._edgesWithID: 

978 self._subgraph._edgesWithID[edgeID] = edge 

979 else: 

980 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

981 else: 

982 # FIXME: needs an error message 

983 raise GraphException() 

984 

985 return edge 

986 

987 def EdgeFromNewVertex( 

988 self, 

989 vertexID: Nullable[VertexIDType] = None, 

990 vertexValue: Nullable[VertexValueType] = None, 

991 vertexWeight: Nullable[VertexWeightType] = None, 

992 vertexKeyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None, 

993 edgeID: Nullable[EdgeIDType] = None, 

994 edgeWeight: Nullable[EdgeWeightType] = None, 

995 edgeValue: Nullable[VertexValueType] = None, 

996 edgeKeyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

997 ) -> 'Edge': 

998 """ 

999 Create a new vertex and link that vertex by an inbound edge to this vertex. 

1000 

1001 :param vertexID: The new vertex' optional ID. 

1002 :param vertexValue: The new vertex' optional value. 

1003 :param vertexWeight: The new vertex' optional weight. 

1004 :param vertexKeyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new vertex. 

1005 :param edgeID: The edge's optional ID for the new edge object. 

1006 :param edgeWeight: The edge's optional weight for the new edge object. 

1007 :param edgeValue: The edge's optional value for the new edge object. 

1008 :param edgeKeyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new edge object. 

1009 :returns: The edge object linking this vertex and the created vertex. 

1010 

1011 .. seealso:: 

1012 

1013 :meth:`EdgeToVertex` |br| 

1014 |rarr| Create an outbound edge from this vertex to the referenced vertex. 

1015 :meth:`EdgeFromVertex` |br| 

1016 |rarr| Create an inbound edge from the referenced vertex to this vertex. 

1017 :meth:`EdgeToNewVertex` |br| 

1018 |rarr| Create a new vertex and link that vertex by an outbound edge from this vertex. 

1019 :meth:`LinkToVertex` |br| 

1020 |rarr| Create an outbound link from this vertex to the referenced vertex. 

1021 :meth:`LinkFromVertex` |br| 

1022 |rarr| Create an inbound link from the referenced vertex to this vertex. 

1023 

1024 .. todo:: GRAPH::Vertex::EdgeFromNewVertex Needs possible exceptions to be documented. 

1025 """ 

1026 vertex = Vertex(vertexID, vertexValue, vertexWeight, vertexKeyValuePairs, graph=self._graph) # , component=self._component) 

1027 

1028 if self._subgraph is vertex._subgraph: 1028 ↛ 1053line 1028 didn't jump to line 1053 because the condition on line 1028 was always true

1029 edge = Edge(vertex, self, edgeID, edgeValue, edgeWeight, edgeKeyValuePairs) 

1030 

1031 vertex._outboundEdges.append(edge) 

1032 self._inboundEdges.append(edge) 

1033 

1034 if self._subgraph is None: 1034 ↛ 1045line 1034 didn't jump to line 1045 because the condition on line 1034 was always true

1035 # TODO: move into Edge? 

1036 # TODO: keep _graph pointer in edge and then register edge on graph? 

1037 if edgeID is None: 1037 ↛ 1039line 1037 didn't jump to line 1039 because the condition on line 1037 was always true

1038 self._graph._edgesWithoutID.append(edge) 

1039 elif edgeID not in self._graph._edgesWithID: 

1040 self._graph._edgesWithID[edgeID] = edge 

1041 else: 

1042 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

1043 else: 

1044 # TODO: keep _graph pointer in edge and then register edge on graph? 

1045 if edgeID is None: 

1046 self._subgraph._edgesWithoutID.append(edge) 

1047 elif edgeID not in self._graph._edgesWithID: 

1048 self._subgraph._edgesWithID[edgeID] = edge 

1049 else: 

1050 raise DuplicateEdgeError(f"Edge ID '{edgeID}' already exists in this graph.") 

1051 else: 

1052 # FIXME: needs an error message 

1053 raise GraphException() 

1054 

1055 return edge 

1056 

1057 def LinkToVertex( 

1058 self, 

1059 vertex: 'Vertex', 

1060 linkID: Nullable[EdgeIDType] = None, 

1061 linkWeight: Nullable[EdgeWeightType] = None, 

1062 linkValue: Nullable[VertexValueType] = None, 

1063 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None, 

1064 ) -> 'Link': 

1065 """ 

1066 Create an outbound link from this vertex to the referenced vertex. 

1067 

1068 :param vertex: The vertex to be linked to. 

1069 :param edgeID: The edge's optional ID for the new link object. 

1070 :param edgeWeight: The edge's optional weight for the new link object. 

1071 :param edgeValue: The edge's optional value for the new link object. 

1072 :param keyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new link object. 

1073 :returns: The link object linking this vertex and the referenced vertex. 

1074 

1075 .. seealso:: 

1076 

1077 :meth:`EdgeToVertex` |br| 

1078 |rarr| Create an outbound edge from this vertex to the referenced vertex. 

1079 :meth:`EdgeFromVertex` |br| 

1080 |rarr| Create an inbound edge from the referenced vertex to this vertex. 

1081 :meth:`EdgeToNewVertex` |br| 

1082 |rarr| Create a new vertex and link that vertex by an outbound edge from this vertex. 

1083 :meth:`EdgeFromNewVertex` |br| 

1084 |rarr| Create a new vertex and link that vertex by an inbound edge to this vertex. 

1085 :meth:`LinkFromVertex` |br| 

1086 |rarr| Create an inbound link from the referenced vertex to this vertex. 

1087 

1088 .. todo:: GRAPH::Vertex::LinkToVertex Needs possible exceptions to be documented. 

1089 """ 

1090 if self._subgraph is vertex._subgraph: 1090 ↛ 1092line 1090 didn't jump to line 1092 because the condition on line 1090 was never true

1091 # FIXME: needs an error message 

1092 raise GraphException() 

1093 else: 

1094 link = Link(self, vertex, linkID, linkValue, linkWeight, keyValuePairs) 

1095 

1096 self._outboundLinks.append(link) 

1097 vertex._inboundLinks.append(link) 

1098 

1099 if self._subgraph is None: 

1100 # TODO: move into Edge? 

1101 # TODO: keep _graph pointer in link and then register link on graph? 

1102 if linkID is None: 1102 ↛ 1104line 1102 didn't jump to line 1104 because the condition on line 1102 was always true

1103 self._graph._linksWithoutID.append(link) 

1104 elif linkID not in self._graph._linksWithID: 

1105 self._graph._linksWithID[linkID] = link 

1106 else: 

1107 raise DuplicateEdgeError(f"Link ID '{linkID}' already exists in this graph.") 

1108 else: 

1109 # TODO: keep _graph pointer in link and then register link on graph? 

1110 if linkID is None: 1110 ↛ 1113line 1110 didn't jump to line 1113 because the condition on line 1110 was always true

1111 self._subgraph._linksWithoutID.append(link) 

1112 vertex._subgraph._linksWithoutID.append(link) 

1113 elif linkID not in self._graph._linksWithID: 

1114 self._subgraph._linksWithID[linkID] = link 

1115 vertex._subgraph._linksWithID[linkID] = link 

1116 else: 

1117 raise DuplicateEdgeError(f"Link ID '{linkID}' already exists in this graph.") 

1118 

1119 return link 

1120 

1121 def LinkFromVertex( 

1122 self, 

1123 vertex: 'Vertex', 

1124 linkID: Nullable[EdgeIDType] = None, 

1125 linkWeight: Nullable[EdgeWeightType] = None, 

1126 linkValue: Nullable[VertexValueType] = None, 

1127 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

1128 ) -> 'Edge': 

1129 """ 

1130 Create an inbound link from the referenced vertex to this vertex. 

1131 

1132 :param vertex: The vertex to be linked from. 

1133 :param edgeID: The edge's optional ID for the new link object. 

1134 :param edgeWeight: The edge's optional weight for the new link object. 

1135 :param edgeValue: The edge's optional value for the new link object. 

1136 :param keyValuePairs: An optional mapping (dictionary) of key-value-pairs for the new link object. 

1137 :returns: The link object linking the referenced vertex and this vertex. 

1138 

1139 .. seealso:: 

1140 

1141 :meth:`EdgeToVertex` |br| 

1142 |rarr| Create an outbound edge from this vertex to the referenced vertex. 

1143 :meth:`EdgeFromVertex` |br| 

1144 |rarr| Create an inbound edge from the referenced vertex to this vertex. 

1145 :meth:`EdgeToNewVertex` |br| 

1146 |rarr| Create a new vertex and link that vertex by an outbound edge from this vertex. 

1147 :meth:`EdgeFromNewVertex` |br| 

1148 |rarr| Create a new vertex and link that vertex by an inbound edge to this vertex. 

1149 :meth:`LinkToVertex` |br| 

1150 |rarr| Create an outbound link from this vertex to the referenced vertex. 

1151 

1152 .. todo:: GRAPH::Vertex::LinkFromVertex Needs possible exceptions to be documented. 

1153 """ 

1154 if self._subgraph is vertex._subgraph: 1154 ↛ 1156line 1154 didn't jump to line 1156 because the condition on line 1154 was never true

1155 # FIXME: needs an error message 

1156 raise GraphException() 

1157 else: 

1158 link = Link(vertex, self, linkID, linkValue, linkWeight, keyValuePairs) 

1159 

1160 vertex._outboundLinks.append(link) 

1161 self._inboundLinks.append(link) 

1162 

1163 if self._subgraph is None: 1163 ↛ 1166line 1163 didn't jump to line 1166 because the condition on line 1163 was never true

1164 # TODO: move into Edge? 

1165 # TODO: keep _graph pointer in link and then register link on graph? 

1166 if linkID is None: 

1167 self._graph._linksWithoutID.append(link) 

1168 elif linkID not in self._graph._linksWithID: 

1169 self._graph._linksWithID[linkID] = link 

1170 else: 

1171 raise DuplicateEdgeError(f"Link ID '{linkID}' already exists in this graph.") 

1172 else: 

1173 # TODO: keep _graph pointer in link and then register link on graph? 

1174 if linkID is None: 1174 ↛ 1177line 1174 didn't jump to line 1177 because the condition on line 1174 was always true

1175 self._subgraph._linksWithoutID.append(link) 

1176 vertex._subgraph._linksWithoutID.append(link) 

1177 elif linkID not in self._graph._linksWithID: 

1178 self._subgraph._linksWithID[linkID] = link 

1179 vertex._subgraph._linksWithID[linkID] = link 

1180 else: 

1181 raise DuplicateEdgeError(f"Link ID '{linkID}' already exists in this graph.") 

1182 

1183 return link 

1184 

1185 def HasEdgeToDestination(self, destination: 'Vertex') -> bool: 

1186 """ 

1187 Check if this vertex is linked to another vertex by any outbound edge. 

1188 

1189 :param destination: Destination vertex to check. 

1190 :returns: ``True``, if the destination vertex is a destination on any outbound edge. 

1191 

1192 .. seealso:: 

1193 

1194 :meth:`HasEdgeFromSource` |br| 

1195 |rarr| Check if this vertex is linked to another vertex by any inbound edge. 

1196 :meth:`HasLinkToDestination` |br| 

1197 |rarr| Check if this vertex is linked to another vertex by any outbound link. 

1198 :meth:`HasLinkFromSource` |br| 

1199 |rarr| Check if this vertex is linked to another vertex by any inbound link. 

1200 """ 

1201 for edge in self._outboundEdges: 

1202 if destination is edge.Destination: 1202 ↛ 1201line 1202 didn't jump to line 1201 because the condition on line 1202 was always true

1203 return True 

1204 

1205 return False 

1206 

1207 def HasEdgeFromSource(self, source: 'Vertex') -> bool: 

1208 """ 

1209 Check if this vertex is linked to another vertex by any inbound edge. 

1210 

1211 :param source: Source vertex to check. 

1212 :returns: ``True``, if the source vertex is a source on any inbound edge. 

1213 

1214 .. seealso:: 

1215 

1216 :meth:`HasEdgeToDestination` |br| 

1217 |rarr| Check if this vertex is linked to another vertex by any outbound edge. 

1218 :meth:`HasLinkToDestination` |br| 

1219 |rarr| Check if this vertex is linked to another vertex by any outbound link. 

1220 :meth:`HasLinkFromSource` |br| 

1221 |rarr| Check if this vertex is linked to another vertex by any inbound link. 

1222 """ 

1223 for edge in self._inboundEdges: 

1224 if source is edge.Source: 1224 ↛ 1223line 1224 didn't jump to line 1223 because the condition on line 1224 was always true

1225 return True 

1226 

1227 return False 

1228 

1229 def HasLinkToDestination(self, destination: 'Vertex') -> bool: 

1230 """ 

1231 Check if this vertex is linked to another vertex by any outbound link. 

1232 

1233 :param destination: Destination vertex to check. 

1234 :returns: ``True``, if the destination vertex is a destination on any outbound link. 

1235 

1236 .. seealso:: 

1237 

1238 :meth:`HasEdgeToDestination` |br| 

1239 |rarr| Check if this vertex is linked to another vertex by any outbound edge. 

1240 :meth:`HasEdgeFromSource` |br| 

1241 |rarr| Check if this vertex is linked to another vertex by any inbound edge. 

1242 :meth:`HasLinkFromSource` |br| 

1243 |rarr| Check if this vertex is linked to another vertex by any inbound link. 

1244 """ 

1245 for link in self._outboundLinks: 

1246 if destination is link.Destination: 1246 ↛ 1245line 1246 didn't jump to line 1245 because the condition on line 1246 was always true

1247 return True 

1248 

1249 return False 

1250 

1251 def HasLinkFromSource(self, source: 'Vertex') -> bool: 

1252 """ 

1253 Check if this vertex is linked to another vertex by any inbound link. 

1254 

1255 :param source: Source vertex to check. 

1256 :returns: ``True``, if the source vertex is a source on any inbound link. 

1257 

1258 .. seealso:: 

1259 

1260 :meth:`HasEdgeToDestination` |br| 

1261 |rarr| Check if this vertex is linked to another vertex by any outbound edge. 

1262 :meth:`HasEdgeFromSource` |br| 

1263 |rarr| Check if this vertex is linked to another vertex by any inbound edge. 

1264 :meth:`HasLinkToDestination` |br| 

1265 |rarr| Check if this vertex is linked to another vertex by any outbound link. 

1266 """ 

1267 for link in self._inboundLinks: 

1268 if source is link.Source: 1268 ↛ 1267line 1268 didn't jump to line 1267 because the condition on line 1268 was always true

1269 return True 

1270 

1271 return False 

1272 

1273 def DeleteEdgeTo(self, destination: 'Vertex'): 

1274 for edge in self._outboundEdges: 1274 ↛ 1278line 1274 didn't jump to line 1278 because the loop on line 1274 didn't complete

1275 if edge._destination is destination: 1275 ↛ 1274line 1275 didn't jump to line 1274 because the condition on line 1275 was always true

1276 break 

1277 else: 

1278 raise GraphException(f"No outbound edge found to '{destination!r}'.") 

1279 

1280 edge.Delete() 

1281 

1282 def DeleteEdgeFrom(self, source: 'Vertex'): 

1283 for edge in self._inboundEdges: 

1284 if edge._source is source: 

1285 break 

1286 else: 

1287 raise GraphException(f"No inbound edge found to '{source!r}'.") 

1288 

1289 edge.Delete() 

1290 

1291 def DeleteLinkTo(self, destination: 'Vertex'): 

1292 for link in self._outboundLinks: 

1293 if link._destination is destination: 

1294 break 

1295 else: 

1296 raise GraphException(f"No outbound link found to '{destination!r}'.") 

1297 

1298 link.Delete() 

1299 

1300 def DeleteLinkFrom(self, source: 'Vertex'): 

1301 for link in self._inboundLinks: 

1302 if link._source is source: 

1303 break 

1304 else: 

1305 raise GraphException(f"No inbound link found to '{source!r}'.") 

1306 

1307 link.Delete() 

1308 

1309 def Copy(self, graph: Graph, copyDict: bool = False, linkingKeyToOriginalVertex: Nullable[str] = None, linkingKeyFromOriginalVertex: Nullable[str] = None) -> 'Vertex': 

1310 """ 

1311 Creates a copy of this vertex in another graph. 

1312 

1313 Optionally, the vertex's attached attributes (key-value-pairs) can be copied and a linkage between both vertices 

1314 can be established. 

1315 

1316 :param graph: The graph, the vertex is created in. 

1317 :param copyDict: If ``True``, copy all attached attributes into the new vertex. 

1318 :param linkingKeyToOriginalVertex: If not ``None``, add a key-value-pair using this parameter as key from new vertex to the original vertex. 

1319 :param linkingKeyFromOriginalVertex: If not ``None``, add a key-value-pair using this parameter as key from original vertex to the new vertex. 

1320 :returns: The newly created vertex. 

1321 :raises GraphException: If source graph and destination graph are the same. 

1322 """ 

1323 if graph is self._graph: 

1324 raise GraphException("Graph to copy this vertex to, is the same graph.") 

1325 

1326 vertex = Vertex(self._id, self._value, self._weight, graph=graph) 

1327 if copyDict: 

1328 vertex._dict = self._dict.copy() 

1329 

1330 if linkingKeyToOriginalVertex is not None: 

1331 vertex._dict[linkingKeyToOriginalVertex] = self 

1332 if linkingKeyFromOriginalVertex is not None: 

1333 self._dict[linkingKeyFromOriginalVertex] = vertex 

1334 

1335 return vertex 

1336 

1337 def IterateOutboundEdges(self, predicate: Nullable[Callable[['Edge'], bool]] = None) -> Generator['Edge', None, None]: 

1338 """ 

1339 Iterate all or selected outbound edges of this vertex. 

1340 

1341 If parameter ``predicate`` is not None, the given filter function is used to skip edges in the generator. 

1342 

1343 :param predicate: Filter function accepting any edge and returning a boolean. 

1344 :returns: A generator to iterate all outbound edges. 

1345 """ 

1346 if predicate is None: 

1347 for edge in self._outboundEdges: 

1348 yield edge 

1349 else: 

1350 for edge in self._outboundEdges: 

1351 if predicate(edge): 

1352 yield edge 

1353 

1354 def IterateInboundEdges(self, predicate: Nullable[Callable[['Edge'], bool]] = None) -> Generator['Edge', None, None]: 

1355 """ 

1356 Iterate all or selected inbound edges of this vertex. 

1357 

1358 If parameter ``predicate`` is not None, the given filter function is used to skip edges in the generator. 

1359 

1360 :param predicate: Filter function accepting any edge and returning a boolean. 

1361 :returns: A generator to iterate all inbound edges. 

1362 """ 

1363 if predicate is None: 

1364 for edge in self._inboundEdges: 

1365 yield edge 

1366 else: 

1367 for edge in self._inboundEdges: 

1368 if predicate(edge): 

1369 yield edge 

1370 

1371 def IterateOutboundLinks(self, predicate: Nullable[Callable[['Link'], bool]] = None) -> Generator['Link', None, None]: 

1372 """ 

1373 Iterate all or selected outbound links of this vertex. 

1374 

1375 If parameter ``predicate`` is not None, the given filter function is used to skip links in the generator. 

1376 

1377 :param predicate: Filter function accepting any link and returning a boolean. 

1378 :returns: A generator to iterate all outbound links. 

1379 """ 

1380 if predicate is None: 

1381 for link in self._outboundLinks: 

1382 yield link 

1383 else: 

1384 for link in self._outboundLinks: 

1385 if predicate(link): 

1386 yield link 

1387 

1388 def IterateInboundLinks(self, predicate: Nullable[Callable[['Link'], bool]] = None) -> Generator['Link', None, None]: 

1389 """ 

1390 Iterate all or selected inbound links of this vertex. 

1391 

1392 If parameter ``predicate`` is not None, the given filter function is used to skip links in the generator. 

1393 

1394 :param predicate: Filter function accepting any link and returning a boolean. 

1395 :returns: A generator to iterate all inbound links. 

1396 """ 

1397 if predicate is None: 

1398 for link in self._inboundLinks: 

1399 yield link 

1400 else: 

1401 for link in self._inboundLinks: 

1402 if predicate(link): 

1403 yield link 

1404 

1405 def IterateSuccessorVertices(self, predicate: Nullable[Callable[['Edge'], bool]] = None) -> Generator['Vertex', None, None]: 

1406 """ 

1407 Iterate all or selected successor vertices of this vertex. 

1408 

1409 If parameter ``predicate`` is not None, the given filter function is used to skip successors in the generator. 

1410 

1411 :param predicate: Filter function accepting any edge and returning a boolean. 

1412 :returns: A generator to iterate all successor vertices. 

1413 """ 

1414 if predicate is None: 

1415 for edge in self._outboundEdges: 

1416 yield edge.Destination 

1417 else: 

1418 for edge in self._outboundEdges: 

1419 if predicate(edge): 

1420 yield edge.Destination 

1421 

1422 def IteratePredecessorVertices(self, predicate: Nullable[Callable[['Edge'], bool]] = None) -> Generator['Vertex', None, None]: 

1423 """ 

1424 Iterate all or selected predecessor vertices of this vertex. 

1425 

1426 If parameter ``predicate`` is not None, the given filter function is used to skip predecessors in the generator. 

1427 

1428 :param predicate: Filter function accepting any edge and returning a boolean. 

1429 :returns: A generator to iterate all predecessor vertices. 

1430 """ 

1431 if predicate is None: 

1432 for edge in self._inboundEdges: 

1433 yield edge.Source 

1434 else: 

1435 for edge in self._inboundEdges: 

1436 if predicate(edge): 

1437 yield edge.Source 

1438 

1439 def IterateVerticesBFS(self) -> Generator['Vertex', None, None]: 

1440 """ 

1441 A generator to iterate all reachable vertices starting from this node in breadth-first search (BFS) order. 

1442 

1443 :returns: A generator to iterate vertices traversed in BFS order. 

1444 

1445 .. seealso:: 

1446 

1447 :meth:`IterateVerticesDFS` |br| 

1448 |rarr| Iterate all reachable vertices **depth-first search** order. 

1449 """ 

1450 visited: Set[Vertex] = set() 

1451 queue: Deque[Vertex] = deque() 

1452 

1453 yield self 

1454 visited.add(self) 

1455 for edge in self._outboundEdges: 

1456 nextVertex = edge.Destination 

1457 if nextVertex is not self: 1457 ↛ 1455line 1457 didn't jump to line 1455 because the condition on line 1457 was always true

1458 queue.appendleft(nextVertex) 

1459 visited.add(nextVertex) 

1460 

1461 while queue: 

1462 vertex = queue.pop() 

1463 yield vertex 

1464 for edge in vertex._outboundEdges: 

1465 nextVertex = edge.Destination 

1466 if nextVertex not in visited: 

1467 queue.appendleft(nextVertex) 

1468 visited.add(nextVertex) 

1469 

1470 def IterateVerticesDFS(self) -> Generator['Vertex', None, None]: 

1471 """ 

1472 A generator to iterate all reachable vertices starting from this node in depth-first search (DFS) order. 

1473 

1474 :returns: A generator to iterate vertices traversed in DFS order. 

1475 

1476 .. seealso:: 

1477 

1478 :meth:`IterateVerticesBFS` |br| 

1479 |rarr| Iterate all reachable vertices **breadth-first search** order. 

1480 

1481 Wikipedia - https://en.wikipedia.org/wiki/Depth-first_search 

1482 """ 

1483 visited: Set[Vertex] = set() 

1484 stack: List[typing_Iterator[Edge]] = list() 

1485 

1486 yield self 

1487 visited.add(self) 

1488 stack.append(iter(self._outboundEdges)) 

1489 

1490 while True: 

1491 try: 

1492 edge = next(stack[-1]) 

1493 nextVertex = edge._destination 

1494 if nextVertex not in visited: 

1495 visited.add(nextVertex) 

1496 yield nextVertex 

1497 if len(nextVertex._outboundEdges) != 0: 

1498 stack.append(iter(nextVertex._outboundEdges)) 

1499 except StopIteration: 

1500 stack.pop() 

1501 

1502 if len(stack) == 0: 

1503 return 

1504 

1505 def IterateAllOutboundPathsAsVertexList(self) -> Generator[Tuple['Vertex', ...], None, None]: 

1506 if len(self._outboundEdges) == 0: 

1507 yield (self, ) 

1508 return 

1509 

1510 visited: Set[Vertex] = set() 

1511 vertexStack: List[Vertex] = list() 

1512 iteratorStack: List[typing_Iterator[Edge]] = list() 

1513 

1514 visited.add(self) 

1515 vertexStack.append(self) 

1516 iteratorStack.append(iter(self._outboundEdges)) 

1517 

1518 while True: 

1519 try: 

1520 edge = next(iteratorStack[-1]) 

1521 nextVertex = edge._destination 

1522 if nextVertex in visited: 

1523 ex = CycleError(f"Loop detected.") 

1524 ex.add_note(f"First loop is:") 

1525 for i, vertex in enumerate(vertexStack): 

1526 ex.add_note(f" {i}: {vertex!r}") 

1527 raise ex 

1528 

1529 vertexStack.append(nextVertex) 

1530 if len(nextVertex._outboundEdges) == 0: 

1531 yield tuple(vertexStack) 

1532 vertexStack.pop() 

1533 else: 

1534 iteratorStack.append(iter(nextVertex._outboundEdges)) 

1535 

1536 except StopIteration: 

1537 vertexStack.pop() 

1538 iteratorStack.pop() 

1539 

1540 if len(vertexStack) == 0: 

1541 return 

1542 

1543 def ShortestPathToByHops(self, destination: 'Vertex') -> Generator['Vertex', None, None]: 

1544 """ 

1545 Compute the shortest path (by hops) between this vertex and the destination vertex. 

1546 

1547 A generator is return to iterate all vertices along the path including source and destination vertex. 

1548 

1549 The search algorithm is breadth-first search (BFS) based. The found solution, if any, is not unique but deterministic 

1550 as long as the graph was not modified (e.g. ordering of edges on vertices). 

1551 

1552 :param destination: The destination vertex to reach. 

1553 :returns: A generator to iterate all vertices on the path found between this vertex and the destination vertex. 

1554 """ 

1555 # Trivial case if start is destination 

1556 if self is destination: 1556 ↛ 1557line 1556 didn't jump to line 1557 because the condition on line 1556 was never true

1557 yield self 

1558 return 

1559 

1560 # Local struct to create multiple linked-lists forming a paths from current node back to the starting point 

1561 # (actually a tree). Each node holds a reference to the vertex it represents. 

1562 # Hint: slotted classes are faster than '@dataclasses.dataclass'. 

1563 class Node(metaclass=ExtendedType, slots=True): 

1564 parent: 'Node' 

1565 ref: Vertex 

1566 

1567 def __init__(self, parent: 'Node', ref: Vertex) -> None: 

1568 self.parent = parent 

1569 self.ref = ref 

1570 

1571 def __str__(self): 

1572 return f"Vertex: {self.ref.ID}" 

1573 

1574 # Initially add all reachable vertices to a queue if vertices to be processed. 

1575 startNode = Node(None, self) 

1576 visited: Set[Vertex] = set() 

1577 queue: Deque[Node] = deque() 

1578 

1579 # Add starting vertex and all its children to the processing list. 

1580 # If a child is the destination, break immediately else go into 'else' branch and use BFS algorithm. 

1581 visited.add(self) 

1582 for edge in self._outboundEdges: 

1583 nextVertex = edge.Destination 

1584 if nextVertex is destination: 1584 ↛ 1586line 1584 didn't jump to line 1586 because the condition on line 1584 was never true

1585 # Child is destination, so construct the last node for path traversal and break from loop. 

1586 destinationNode = Node(startNode, nextVertex) 

1587 break 

1588 if nextVertex is not self: 1588 ↛ 1582line 1588 didn't jump to line 1582 because the condition on line 1588 was always true

1589 # Ignore backward-edges and side-edges. 

1590 # Here self-edges, because there is only the starting vertex in the list of visited edges. 

1591 visited.add(nextVertex) 

1592 queue.appendleft(Node(startNode, nextVertex)) 

1593 else: 

1594 # Process queue until destination is found or no further vertices are reachable. 

1595 while queue: 

1596 node = queue.pop() 

1597 for edge in node.ref._outboundEdges: 

1598 nextVertex = edge.Destination 

1599 # Next reachable vertex is destination, so construct the last node for path traversal and break from loop. 

1600 if nextVertex is destination: 

1601 destinationNode = Node(node, nextVertex) 

1602 break 

1603 # Ignore backward-edges and side-edges. 

1604 if nextVertex not in visited: 

1605 visited.add(nextVertex) 

1606 queue.appendleft(Node(node, nextVertex)) 

1607 # Next 3 lines realize a double-break if break was called in inner loop, otherwise continue with outer loop. 

1608 else: 

1609 continue 

1610 break 

1611 else: 

1612 # All reachable vertices have been processed, but destination was not among them. 

1613 raise DestinationNotReachable(f"Destination is not reachable.") 

1614 

1615 # Reverse order of linked list from destinationNode to startNode 

1616 currentNode = destinationNode 

1617 previousNode = destinationNode.parent 

1618 currentNode.parent = None 

1619 while previousNode is not None: 

1620 node = previousNode.parent 

1621 previousNode.parent = currentNode 

1622 currentNode = previousNode 

1623 previousNode = node 

1624 

1625 # Scan reversed linked-list and yield referenced vertices 

1626 yield startNode.ref 

1627 node = startNode.parent 

1628 while node is not None: 

1629 yield node.ref 

1630 node = node.parent 

1631 

1632 def ShortestPathToByWeight(self, destination: 'Vertex') -> Generator['Vertex', None, None]: 

1633 """ 

1634 Compute the shortest path (by edge weight) between this vertex and the destination vertex. 

1635 

1636 A generator is return to iterate all vertices along the path including source and destination vertex. 

1637 

1638 The search algorithm is based on Dijkstra algorithm and using :mod:`heapq`. The found solution, if any, is not 

1639 unique but deterministic as long as the graph was not modified (e.g. ordering of edges on vertices). 

1640 

1641 :param destination: The destination vertex to reach. 

1642 :returns: A generator to iterate all vertices on the path found between this vertex and the destination vertex. 

1643 """ 

1644 # Improvements: both-sided Dijkstra (search from start and destination to reduce discovered area. 

1645 

1646 # Trivial case if start is destination 

1647 if self is destination: 1647 ↛ 1648line 1647 didn't jump to line 1648 because the condition on line 1647 was never true

1648 yield self 

1649 return 

1650 

1651 # Local struct to create multiple-linked lists forming a paths from current node back to the starting point 

1652 # (actually a tree). Each node holds the overall weight from start to current node and a reference to the vertex it 

1653 # represents. 

1654 # Hint: slotted classes are faster than '@dataclasses.dataclass'. 

1655 class Node(metaclass=ExtendedType, slots=True): 

1656 parent: 'Node' 

1657 distance: EdgeWeightType 

1658 ref: Vertex 

1659 

1660 def __init__(self, parent: 'Node', distance: EdgeWeightType, ref: Vertex) -> None: 

1661 self.parent = parent 

1662 self.distance = distance 

1663 self.ref = ref 

1664 

1665 def __lt__(self, other): 

1666 return self.distance < other.distance 

1667 

1668 def __str__(self): 

1669 return f"Vertex: {self.ref.ID}" 

1670 

1671 visited: Set['Vertex'] = set() 

1672 startNode = Node(None, 0, self) 

1673 priorityQueue = [startNode] 

1674 

1675 # Add starting vertex and all its children to the processing list. 

1676 # If a child is the destination, break immediately else go into 'else' branch and use Dijkstra algorithm. 

1677 visited.add(self) 

1678 for edge in self._outboundEdges: 

1679 nextVertex = edge.Destination 

1680 # Child is destination, so construct the last node for path traversal and break from loop. 

1681 if nextVertex is destination: 1681 ↛ 1682line 1681 didn't jump to line 1682 because the condition on line 1681 was never true

1682 destinationNode = Node(startNode, edge._weight, nextVertex) 

1683 break 

1684 # Ignore backward-edges and side-edges. 

1685 # Here self-edges, because there is only the starting vertex in the list of visited edges. 

1686 if nextVertex is not self: 1686 ↛ 1678line 1686 didn't jump to line 1678 because the condition on line 1686 was always true

1687 visited.add(nextVertex) 

1688 heapq.heappush(priorityQueue, Node(startNode, edge._weight, nextVertex)) 

1689 else: 

1690 # Process priority queue until destination is found or no further vertices are reachable. 

1691 while priorityQueue: 1691 ↛ 1709line 1691 didn't jump to line 1709 because the condition on line 1691 was always true

1692 node = heapq.heappop(priorityQueue) 

1693 for edge in node.ref._outboundEdges: 

1694 nextVertex = edge.Destination 

1695 # Next reachable vertex is destination, so construct the last node for path traversal and break from loop. 

1696 if nextVertex is destination: 

1697 destinationNode = Node(node, node.distance + edge._weight, nextVertex) 

1698 break 

1699 # Ignore backward-edges and side-edges. 

1700 if nextVertex not in visited: 

1701 visited.add(nextVertex) 

1702 heapq.heappush(priorityQueue, Node(node, node.distance + edge._weight, nextVertex)) 

1703 # Next 3 lines realize a double-break if break was called in inner loop, otherwise continue with outer loop. 

1704 else: 

1705 continue 

1706 break 

1707 else: 

1708 # All reachable vertices have been processed, but destination was not among them. 

1709 raise DestinationNotReachable(f"Destination is not reachable.") 

1710 

1711 # Reverse order of linked-list from destinationNode to startNode 

1712 currentNode = destinationNode 

1713 previousNode = destinationNode.parent 

1714 currentNode.parent = None 

1715 while previousNode is not None: 

1716 node = previousNode.parent 

1717 previousNode.parent = currentNode 

1718 currentNode = previousNode 

1719 previousNode = node 

1720 

1721 # Scan reversed linked-list and yield referenced vertices 

1722 yield startNode.ref, startNode.distance 

1723 node = startNode.parent 

1724 while node is not None: 

1725 yield node.ref, node.distance 

1726 node = node.parent 

1727 

1728 # Other possible algorithms: 

1729 # * Bellman-Ford 

1730 # * Floyd-Warshall 

1731 

1732 # def PathExistsTo(self, destination: 'Vertex'): 

1733 # raise NotImplementedError() 

1734 # # DFS 

1735 # # Union find 

1736 # 

1737 # def MaximumFlowTo(self, destination: 'Vertex'): 

1738 # raise NotImplementedError() 

1739 # # Ford-Fulkerson algorithm 

1740 # # Edmons-Karp algorithm 

1741 # # Dinic's algorithm 

1742 

1743 def ConvertToTree(self) -> Node: 

1744 """ 

1745 Converts all reachable vertices from this starting vertex to a tree of :class:`~pyTooling.Tree.Node` instances. 

1746 

1747 The tree is traversed using depths-first-search. 

1748 

1749 :returns: 

1750 """ 

1751 visited: Set[Vertex] = set() 

1752 stack: List[Tuple[Node, typing_Iterator[Edge]]] = list() 

1753 

1754 root = Node(nodeID=self._id, value=self._value) 

1755 root._dict = self._dict.copy() 

1756 

1757 visited.add(self) 

1758 stack.append((root, iter(self._outboundEdges))) 

1759 

1760 while True: 

1761 try: 

1762 edge = next(stack[-1][1]) 

1763 nextVertex = edge._destination 

1764 if nextVertex not in visited: 1764 ↛ 1770line 1764 didn't jump to line 1770 because the condition on line 1764 was always true

1765 node = Node(nextVertex._id, nextVertex._value, parent=stack[-1][0]) 

1766 visited.add(nextVertex) 

1767 if len(nextVertex._outboundEdges) != 0: 

1768 stack.append((node, iter(nextVertex._outboundEdges))) 

1769 else: 

1770 raise NotATreeError(f"The directed subgraph is not a tree.") 

1771 # TODO: compute cycle: 

1772 # a) branch 1 is described in stack 

1773 # b) branch 2 can be found by walking from joint to root in the tree 

1774 except StopIteration: 

1775 stack.pop() 

1776 

1777 if len(stack) == 0: 

1778 return root 

1779 

1780 def __repr__(self) -> str: 

1781 """ 

1782 Returns a detailed string representation of the vertex. 

1783 

1784 :returns: The detailed string representation of the vertex. 

1785 """ 

1786 vertexID = value = "" 

1787 sep = ": " 

1788 if self._id is not None: 

1789 vertexID = f"{sep}vertexID='{self._id}'" 

1790 sep = "; " 

1791 if self._value is not None: 1791 ↛ 1792line 1791 didn't jump to line 1792 because the condition on line 1791 was never true

1792 value = f"{sep}value='{self._value}'" 

1793 

1794 return f"<vertex{vertexID}{value}>" 

1795 

1796 def __str__(self) -> str: 

1797 """ 

1798 Return a string representation of the vertex. 

1799 

1800 Order of resolution: 

1801 

1802 1. If :attr:`_value` is not None, return the string representation of :attr:`_value`. 

1803 2. If :attr:`_id` is not None, return the string representation of :attr:`_id`. 

1804 3. Else, return :meth:`__repr__`. 

1805 

1806 :returns: The resolved string representation of the vertex. 

1807 """ 

1808 if self._value is not None: 1808 ↛ 1809line 1808 didn't jump to line 1809 because the condition on line 1808 was never true

1809 return str(self._value) 

1810 elif self._id is not None: 1810 ↛ 1811line 1810 didn't jump to line 1811 because the condition on line 1810 was never true

1811 return str(self._id) 

1812 else: 

1813 return self.__repr__() 

1814 

1815 

1816@export 

1817class BaseEdge( 

1818 BaseWithIDValueAndWeight[EdgeIDType, EdgeValueType, EdgeWeightType, EdgeDictKeyType, EdgeDictValueType], 

1819 Generic[EdgeIDType, EdgeValueType, EdgeWeightType, EdgeDictKeyType, EdgeDictValueType] 

1820): 

1821 """ 

1822 An **edge** can have a unique ID, a value, a weight and attached meta information as key-value-pairs. All edges are 

1823 directed. 

1824 """ 

1825 _source: Vertex 

1826 _destination: Vertex 

1827 

1828 def __init__( 

1829 self, 

1830 source: Vertex, 

1831 destination: Vertex, 

1832 edgeID: Nullable[EdgeIDType] = None, 

1833 value: Nullable[EdgeValueType] = None, 

1834 weight: Nullable[EdgeWeightType] = None, 

1835 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

1836 ) -> None: 

1837 """ 

1838 .. todo:: GRAPH::BaseEdge::init Needs documentation. 

1839 

1840 :param source: The source of the new edge. 

1841 :param destination: The destination of the new edge. 

1842 :param edgeID: The optional unique ID for the new edge. 

1843 :param value: The optional value for the new edge. 

1844 :param weight: The optional weight for the new edge. 

1845 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

1846 """ 

1847 super().__init__(edgeID, value, weight, keyValuePairs) 

1848 

1849 self._source = source 

1850 self._destination = destination 

1851 

1852 component = source._component 

1853 if component is not destination._component: 

1854 # TODO: should it be divided into with/without ID? 

1855 oldComponent = destination._component 

1856 for vertex in oldComponent._vertices: 

1857 vertex._component = component 

1858 component._vertices.add(vertex) 

1859 component._graph._components.remove(oldComponent) 

1860 del oldComponent 

1861 

1862 @readonly 

1863 def Source(self) -> Vertex: 

1864 """ 

1865 Read-only property to get the source (:attr:`_source`) of an edge. 

1866 

1867 :returns: The source of an edge. 

1868 """ 

1869 return self._source 

1870 

1871 @readonly 

1872 def Destination(self) -> Vertex: 

1873 """ 

1874 Read-only property to get the destination (:attr:`_destination`) of an edge. 

1875 

1876 :returns: The destination of an edge. 

1877 """ 

1878 return self._destination 

1879 

1880 def Reverse(self) -> None: 

1881 """Reverse the direction of this edge.""" 

1882 swap = self._source 

1883 self._source = self._destination 

1884 self._destination = swap 

1885 

1886 

1887@export 

1888class Edge( 

1889 BaseEdge[EdgeIDType, EdgeValueType, EdgeWeightType, EdgeDictKeyType, EdgeDictValueType], 

1890 Generic[EdgeIDType, EdgeValueType, EdgeWeightType, EdgeDictKeyType, EdgeDictValueType] 

1891): 

1892 """ 

1893 An **edge** can have a unique ID, a value, a weight and attached meta information as key-value-pairs. All edges are 

1894 directed. 

1895 """ 

1896 

1897 def __init__( 

1898 self, 

1899 source: Vertex, 

1900 destination: Vertex, 

1901 edgeID: Nullable[EdgeIDType] = None, 

1902 value: Nullable[EdgeValueType] = None, 

1903 weight: Nullable[EdgeWeightType] = None, 

1904 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

1905 ) -> None: 

1906 """ 

1907 .. todo:: GRAPH::Edge::init Needs documentation. 

1908 

1909 :param source: The source of the new edge. 

1910 :param destination: The destination of the new edge. 

1911 :param edgeID: The optional unique ID for the new edge. 

1912 :param value: The optional value for the new edge. 

1913 :param weight: The optional weight for the new edge. 

1914 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

1915 """ 

1916 if not isinstance(source, Vertex): 

1917 ex = TypeError("Parameter 'source' is not of type 'Vertex'.") 

1918 ex.add_note(f"Got type '{getFullyQualifiedName(source)}'.") 

1919 raise ex 

1920 if not isinstance(destination, Vertex): 

1921 ex = TypeError("Parameter 'destination' is not of type 'Vertex'.") 

1922 ex.add_note(f"Got type '{getFullyQualifiedName(destination)}'.") 

1923 raise ex 

1924 if edgeID is not None and not isinstance(edgeID, Hashable): 

1925 ex = TypeError("Parameter 'edgeID' is not of type 'EdgeIDType'.") 

1926 ex.add_note(f"Got type '{getFullyQualifiedName(edgeID)}'.") 

1927 raise ex 

1928 # if value is not None and not isinstance(value, Vertex): 

1929 # raise TypeError("Parameter 'value' is not of type 'EdgeValueType'.") 

1930 if weight is not None and not isinstance(weight, (int, float)): 

1931 ex = TypeError("Parameter 'weight' is not of type 'EdgeWeightType'.") 

1932 ex.add_note(f"Got type '{getFullyQualifiedName(weight)}'.") 

1933 raise ex 

1934 if source._graph is not destination._graph: 

1935 raise NotInSameGraph(f"Source vertex and destination vertex are not in same graph.") 

1936 

1937 super().__init__(source, destination, edgeID, value, weight, keyValuePairs) 

1938 

1939 def Delete(self) -> None: 

1940 # Remove from Source and Destination 

1941 self._source._outboundEdges.remove(self) 

1942 self._destination._inboundEdges.remove(self) 

1943 

1944 # Remove from Graph and Subgraph 

1945 if self._id is None: 1945 ↛ 1950line 1945 didn't jump to line 1950 because the condition on line 1945 was always true

1946 self._source._graph._edgesWithoutID.remove(self) 

1947 if self._source._subgraph is not None: 1947 ↛ 1948line 1947 didn't jump to line 1948 because the condition on line 1947 was never true

1948 self._source._subgraph._edgesWithoutID.remove(self) 

1949 else: 

1950 del self._source._graph._edgesWithID[self._id] 

1951 if self._source._subgraph is not None: 

1952 del self._source._subgraph._edgesWithID[self] 

1953 

1954 self._Delete() 

1955 

1956 def _Delete(self) -> None: 

1957 super().Delete() 

1958 

1959 def Reverse(self) -> None: 

1960 """Reverse the direction of this edge.""" 

1961 self._source._outboundEdges.remove(self) 

1962 self._source._inboundEdges.append(self) 

1963 self._destination._inboundEdges.remove(self) 

1964 self._destination._outboundEdges.append(self) 

1965 

1966 super().Reverse() 

1967 

1968 

1969@export 

1970class Link( 

1971 BaseEdge[LinkIDType, LinkValueType, LinkWeightType, LinkDictKeyType, LinkDictValueType], 

1972 Generic[LinkIDType, LinkValueType, LinkWeightType, LinkDictKeyType, LinkDictValueType] 

1973): 

1974 """ 

1975 A **link** can have a unique ID, a value, a weight and attached meta information as key-value-pairs. All links are 

1976 directed. 

1977 """ 

1978 

1979 def __init__( 

1980 self, 

1981 source: Vertex, 

1982 destination: Vertex, 

1983 linkID: LinkIDType = None, 

1984 value: LinkValueType = None, 

1985 weight: Nullable[LinkWeightType] = None, 

1986 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

1987 ) -> None: 

1988 """ 

1989 .. todo:: GRAPH::Edge::init Needs documentation. 

1990 

1991 :param source: The source of the new link. 

1992 :param destination: The destination of the new link. 

1993 :param linkID: The optional unique ID for the new link. 

1994 :param value: The optional value for the new v. 

1995 :param weight: The optional weight for the new link. 

1996 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

1997 """ 

1998 if not isinstance(source, Vertex): 

1999 ex = TypeError("Parameter 'source' is not of type 'Vertex'.") 

2000 ex.add_note(f"Got type '{getFullyQualifiedName(source)}'.") 

2001 raise ex 

2002 if not isinstance(destination, Vertex): 

2003 ex = TypeError("Parameter 'destination' is not of type 'Vertex'.") 

2004 ex.add_note(f"Got type '{getFullyQualifiedName(destination)}'.") 

2005 raise ex 

2006 if linkID is not None and not isinstance(linkID, Hashable): 

2007 ex = TypeError("Parameter 'linkID' is not of type 'LinkIDType'.") 

2008 ex.add_note(f"Got type '{getFullyQualifiedName(linkID)}'.") 

2009 raise ex 

2010 # if value is not None and not isinstance(value, Vertex): 

2011 # raise TypeError("Parameter 'value' is not of type 'EdgeValueType'.") 

2012 if weight is not None and not isinstance(weight, (int, float)): 

2013 ex = TypeError("Parameter 'weight' is not of type 'EdgeWeightType'.") 

2014 ex.add_note(f"Got type '{getFullyQualifiedName(weight)}'.") 

2015 raise ex 

2016 if source._graph is not destination._graph: 

2017 raise NotInSameGraph(f"Source vertex and destination vertex are not in same graph.") 

2018 

2019 super().__init__(source, destination, linkID, value, weight, keyValuePairs) 

2020 

2021 def Delete(self) -> None: 

2022 self._source._outboundEdges.remove(self) 

2023 self._destination._inboundEdges.remove(self) 

2024 

2025 if self._id is None: 

2026 self._source._graph._linksWithoutID.remove(self) 

2027 else: 

2028 del self._source._graph._linksWithID[self._id] 

2029 

2030 self._Delete() 

2031 assert getrefcount(self) == 1 

2032 

2033 def _Delete(self) -> None: 

2034 super().Delete() 

2035 

2036 def Reverse(self) -> None: 

2037 """Reverse the direction of this link.""" 

2038 self._source._outboundEdges.remove(self) 

2039 self._source._inboundEdges.append(self) 

2040 self._destination._inboundEdges.remove(self) 

2041 self._destination._outboundEdges.append(self) 

2042 

2043 super().Reverse() 

2044 

2045 

2046@export 

2047class BaseGraph( 

2048 BaseWithName[GraphDictKeyType, GraphDictValueType], 

2049 Generic[ 

2050 GraphDictKeyType, GraphDictValueType, 

2051 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2052 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2053 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2054 ] 

2055): 

2056 """ 

2057 .. todo:: GRAPH::BaseGraph Needs documentation. 

2058 

2059 """ 

2060 

2061 _verticesWithID: Dict[VertexIDType, Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2062 _verticesWithoutID: List[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2063 _edgesWithID: Dict[EdgeIDType, Edge[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]] 

2064 _edgesWithoutID: List[Edge[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType]] 

2065 _linksWithID: Dict[EdgeIDType, Link[LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2066 _linksWithoutID: List[Link[LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2067 

2068 def __init__( 

2069 self, 

2070 name: Nullable[str] = None, 

2071 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

2072 #, vertices: Nullable[Iterable[Vertex]] = None) -> None: 

2073 ): 

2074 """ 

2075 .. todo:: GRAPH::BaseGraph::init Needs documentation. 

2076 

2077 :param name: The optional name of the graph. 

2078 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

2079 """ 

2080 super().__init__(name, keyValuePairs) 

2081 

2082 self._verticesWithoutID = [] 

2083 self._verticesWithID = {} 

2084 self._edgesWithoutID = [] 

2085 self._edgesWithID = {} 

2086 self._linksWithoutID = [] 

2087 self._linksWithID = {} 

2088 

2089 def __del__(self): 

2090 """ 

2091 .. todo:: GRAPH::BaseGraph::del Needs documentation. 

2092 

2093 """ 

2094 try: 

2095 del self._verticesWithoutID 

2096 del self._verticesWithID 

2097 del self._edgesWithoutID 

2098 del self._edgesWithID 

2099 del self._linksWithoutID 

2100 del self._linksWithID 

2101 except AttributeError: 

2102 pass 

2103 

2104 super().__del__() 

2105 

2106 @readonly 

2107 def VertexCount(self) -> int: 

2108 """Read-only property to access the number of vertices in this graph. 

2109 

2110 :returns: The number of vertices in this graph.""" 

2111 return len(self._verticesWithoutID) + len(self._verticesWithID) 

2112 

2113 @readonly 

2114 def EdgeCount(self) -> int: 

2115 """Read-only property to access the number of edges in this graph. 

2116 

2117 :returns: The number of edges in this graph.""" 

2118 return len(self._edgesWithoutID) + len(self._edgesWithID) 

2119 

2120 @readonly 

2121 def LinkCount(self) -> int: 

2122 """Read-only property to access the number of links in this graph. 

2123 

2124 :returns: The number of links in this graph.""" 

2125 return len(self._linksWithoutID) + len(self._linksWithID) 

2126 

2127 def IterateVertices(self, predicate: Nullable[Callable[[Vertex], bool]] = None) -> Generator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2128 """ 

2129 Iterate all or selected vertices of a graph. 

2130 

2131 If parameter ``predicate`` is not None, the given filter function is used to skip vertices in the generator. 

2132 

2133 :param predicate: Filter function accepting any vertex and returning a boolean. 

2134 :returns: A generator to iterate all vertices. 

2135 """ 

2136 if predicate is None: 

2137 yield from self._verticesWithoutID 

2138 yield from self._verticesWithID.values() 

2139 

2140 else: 

2141 for vertex in self._verticesWithoutID: 

2142 if predicate(vertex): 

2143 yield vertex 

2144 

2145 for vertex in self._verticesWithID.values(): 

2146 if predicate(vertex): 

2147 yield vertex 

2148 

2149 def IterateRoots(self, predicate: Nullable[Callable[[Vertex], bool]] = None) -> Generator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2150 """ 

2151 Iterate all or selected roots (vertices without inbound edges / without predecessors) of a graph. 

2152 

2153 If parameter ``predicate`` is not None, the given filter function is used to skip vertices in the generator. 

2154 

2155 :param predicate: Filter function accepting any vertex and returning a boolean. 

2156 :returns: A generator to iterate all vertices without inbound edges. 

2157 

2158 .. seealso:: 

2159 

2160 :meth:`IterateLeafs` |br| 

2161 |rarr| Iterate leafs of a graph. 

2162 :meth:`Vertex.IsRoot <pyTooling.Graph.Vertex.IsRoot>` |br| 

2163 |rarr| Check if a vertex is a root vertex in the graph. 

2164 :meth:`Vertex.IsLeaf <pyTooling.Graph.Vertex.IsLeaf>` |br| 

2165 |rarr| Check if a vertex is a leaf vertex in the graph. 

2166 """ 

2167 if predicate is None: 

2168 for vertex in self._verticesWithoutID: 

2169 if len(vertex._inboundEdges) == 0: 

2170 yield vertex 

2171 

2172 for vertex in self._verticesWithID.values(): 2172 ↛ 2173line 2172 didn't jump to line 2173 because the loop on line 2172 never started

2173 if len(vertex._inboundEdges) == 0: 

2174 yield vertex 

2175 else: 

2176 for vertex in self._verticesWithoutID: 

2177 if len(vertex._inboundEdges) == 0 and predicate(vertex): 

2178 yield vertex 

2179 

2180 for vertex in self._verticesWithID.values(): 2180 ↛ 2181line 2180 didn't jump to line 2181 because the loop on line 2180 never started

2181 if len(vertex._inboundEdges) == 0 and predicate(vertex): 

2182 yield vertex 

2183 

2184 def IterateLeafs(self, predicate: Nullable[Callable[[Vertex], bool]] = None) -> Generator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2185 """ 

2186 Iterate all or selected leafs (vertices without outbound edges / without successors) of a graph. 

2187 

2188 If parameter ``predicate`` is not None, the given filter function is used to skip vertices in the generator. 

2189 

2190 :param predicate: Filter function accepting any vertex and returning a boolean. 

2191 :returns: A generator to iterate all vertices without outbound edges. 

2192 

2193 .. seealso:: 

2194 

2195 :meth:`IterateRoots` |br| 

2196 |rarr| Iterate roots of a graph. 

2197 :meth:`Vertex.IsRoot <pyTooling.Graph.Vertex.IsRoot>` |br| 

2198 |rarr| Check if a vertex is a root vertex in the graph. 

2199 :meth:`Vertex.IsLeaf <pyTooling.Graph.Vertex.IsLeaf>` |br| 

2200 |rarr| Check if a vertex is a leaf vertex in the graph. 

2201 """ 

2202 if predicate is None: 

2203 for vertex in self._verticesWithoutID: 

2204 if len(vertex._outboundEdges) == 0: 

2205 yield vertex 

2206 

2207 for vertex in self._verticesWithID.values(): 

2208 if len(vertex._outboundEdges) == 0: 

2209 yield vertex 

2210 else: 

2211 for vertex in self._verticesWithoutID: 

2212 if len(vertex._outboundEdges) == 0 and predicate(vertex): 

2213 yield vertex 

2214 

2215 for vertex in self._verticesWithID.values(): 

2216 if len(vertex._outboundEdges) == 0 and predicate(vertex): 2216 ↛ 2217line 2216 didn't jump to line 2217 because the condition on line 2216 was never true

2217 yield vertex 

2218 

2219 # def IterateBFS(self, predicate: Nullable[Callable[[Vertex], bool]] = None) -> Generator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2220 # raise NotImplementedError() 

2221 # 

2222 # def IterateDFS(self, predicate: Nullable[Callable[[Vertex], bool]] = None) -> Generator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2223 # raise NotImplementedError() 

2224 

2225 def IterateTopologically(self, predicate: Nullable[Callable[[Vertex], bool]] = None) -> Generator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2226 """ 

2227 Iterate all or selected vertices in topological order. 

2228 

2229 If parameter ``predicate`` is not None, the given filter function is used to skip vertices in the generator. 

2230 

2231 :param predicate: Filter function accepting any vertex and returning a boolean. 

2232 :returns: A generator to iterate all vertices in topological order. 

2233 :except CycleError: Raised if graph is cyclic, thus topological sorting isn't possible. 

2234 """ 

2235 outboundEdgeCounts = {} 

2236 leafVertices = [] 

2237 

2238 for vertex in self._verticesWithoutID: 

2239 if (count := len(vertex._outboundEdges)) == 0: 

2240 leafVertices.append(vertex) 

2241 else: 

2242 outboundEdgeCounts[vertex] = count 

2243 

2244 for vertex in self._verticesWithID.values(): 

2245 if (count := len(vertex._outboundEdges)) == 0: 

2246 leafVertices.append(vertex) 

2247 else: 

2248 outboundEdgeCounts[vertex] = count 

2249 

2250 if not leafVertices: 2250 ↛ 2251line 2250 didn't jump to line 2251 because the condition on line 2250 was never true

2251 raise CycleError(f"Graph has no leafs. Thus, no topological sorting exists.") 

2252 

2253 overallCount = len(outboundEdgeCounts) + len(leafVertices) 

2254 

2255 def removeVertex(vertex: Vertex): 

2256 nonlocal overallCount 

2257 overallCount -= 1 

2258 for inboundEdge in vertex._inboundEdges: 

2259 sourceVertex = inboundEdge.Source 

2260 count = outboundEdgeCounts[sourceVertex] - 1 

2261 outboundEdgeCounts[sourceVertex] = count 

2262 if count == 0: 

2263 leafVertices.append(sourceVertex) 

2264 

2265 if predicate is None: 

2266 for vertex in leafVertices: 

2267 yield vertex 

2268 

2269 removeVertex(vertex) 

2270 else: 

2271 for vertex in leafVertices: 

2272 if predicate(vertex): 

2273 yield vertex 

2274 

2275 removeVertex(vertex) 

2276 

2277 if overallCount == 0: 2277 ↛ 2279line 2277 didn't jump to line 2279 because the condition on line 2277 was always true

2278 return 

2279 elif overallCount > 0: 

2280 raise CycleError(f"Graph has remaining vertices. Thus, the graph has at least one cycle.") 

2281 

2282 raise InternalError(f"Graph data structure is corrupted.") # pragma: no cover 

2283 

2284 def IterateEdges(self, predicate: Nullable[Callable[[Edge], bool]] = None) -> Generator[Edge[EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType], None, None]: 

2285 """ 

2286 Iterate all or selected edges of a graph. 

2287 

2288 If parameter ``predicate`` is not None, the given filter function is used to skip edges in the generator. 

2289 

2290 :param predicate: Filter function accepting any edge and returning a boolean. 

2291 :returns: A generator to iterate all edges. 

2292 """ 

2293 if predicate is None: 

2294 yield from self._edgesWithoutID 

2295 yield from self._edgesWithID.values() 

2296 

2297 else: 

2298 for edge in self._edgesWithoutID: 

2299 if predicate(edge): 

2300 yield edge 

2301 

2302 for edge in self._edgesWithID.values(): 

2303 if predicate(edge): 

2304 yield edge 

2305 

2306 def IterateLinks(self, predicate: Nullable[Callable[[Link], bool]] = None) -> Generator[Link[LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType], None, None]: 

2307 """ 

2308 Iterate all or selected links of a graph. 

2309 

2310 If parameter ``predicate`` is not None, the given filter function is used to skip links in the generator. 

2311 

2312 :param predicate: Filter function accepting any link and returning a boolean. 

2313 :returns: A generator to iterate all links. 

2314 """ 

2315 if predicate is None: 2315 ↛ 2320line 2315 didn't jump to line 2320 because the condition on line 2315 was always true

2316 yield from self._linksWithoutID 

2317 yield from self._linksWithID.values() 

2318 

2319 else: 

2320 for link in self._linksWithoutID: 

2321 if predicate(link): 

2322 yield link 

2323 

2324 for link in self._linksWithID.values(): 

2325 if predicate(link): 

2326 yield link 

2327 

2328 def ReverseEdges(self, predicate: Nullable[Callable[[Edge], bool]] = None) -> None: 

2329 """ 

2330 Reverse all or selected edges of a graph. 

2331 

2332 If parameter ``predicate`` is not None, the given filter function is used to skip edges. 

2333 

2334 :param predicate: Filter function accepting any edge and returning a boolean. 

2335 """ 

2336 if predicate is None: 

2337 for edge in self._edgesWithoutID: 

2338 swap = edge._source 

2339 edge._source = edge._destination 

2340 edge._destination = swap 

2341 

2342 for edge in self._edgesWithID.values(): 

2343 swap = edge._source 

2344 edge._source = edge._destination 

2345 edge._destination = swap 

2346 

2347 for vertex in self._verticesWithoutID: 

2348 swap = vertex._inboundEdges 

2349 vertex._inboundEdges = vertex._outboundEdges 

2350 vertex._outboundEdges = swap 

2351 

2352 for vertex in self._verticesWithID.values(): 

2353 swap = vertex._inboundEdges 

2354 vertex._inboundEdges = vertex._outboundEdges 

2355 vertex._outboundEdges = swap 

2356 else: 

2357 for edge in self._edgesWithoutID: 

2358 if predicate(edge): 

2359 edge.Reverse() 

2360 

2361 for edge in self._edgesWithID.values(): 

2362 if predicate(edge): 

2363 edge.Reverse() 

2364 

2365 def ReverseLinks(self, predicate: Nullable[Callable[[Link], bool]] = None) -> None: 

2366 """ 

2367 Reverse all or selected links of a graph. 

2368 

2369 If parameter ``predicate`` is not None, the given filter function is used to skip links. 

2370 

2371 :param predicate: Filter function accepting any link and returning a boolean. 

2372 """ 

2373 if predicate is None: 

2374 for link in self._linksWithoutID: 

2375 swap = link._source 

2376 link._source = link._destination 

2377 link._destination = swap 

2378 

2379 for link in self._linksWithID.values(): 

2380 swap = link._source 

2381 link._source = link._destination 

2382 link._destination = swap 

2383 

2384 for vertex in self._verticesWithoutID: 

2385 swap = vertex._inboundLinks 

2386 vertex._inboundLinks = vertex._outboundLinks 

2387 vertex._outboundLinks = swap 

2388 

2389 for vertex in self._verticesWithID.values(): 

2390 swap = vertex._inboundLinks 

2391 vertex._inboundLinks = vertex._outboundLinks 

2392 vertex._outboundLinks = swap 

2393 else: 

2394 for link in self._linksWithoutID: 

2395 if predicate(link): 

2396 link.Reverse() 

2397 

2398 for link in self._linksWithID.values(): 

2399 if predicate(link): 

2400 link.Reverse() 

2401 

2402 def RemoveEdges(self, predicate: Nullable[Callable[[Edge], bool]] = None): 

2403 """ 

2404 Remove all or selected edges of a graph. 

2405 

2406 If parameter ``predicate`` is not None, the given filter function is used to skip edges. 

2407 

2408 :param predicate: Filter function accepting any edge and returning a boolean. 

2409 """ 

2410 if predicate is None: 

2411 for edge in self._edgesWithoutID: 

2412 edge._Delete() 

2413 

2414 for edge in self._edgesWithID.values(): 

2415 edge._Delete() 

2416 

2417 self._edgesWithoutID = [] 

2418 self._edgesWithID = {} 

2419 

2420 for vertex in self._verticesWithoutID: 

2421 vertex._inboundEdges = [] 

2422 vertex._outboundEdges = [] 

2423 

2424 for vertex in self._verticesWithID.values(): 

2425 vertex._inboundEdges = [] 

2426 vertex._outboundEdges = [] 

2427 

2428 else: 

2429 delEdges = [edge for edge in self._edgesWithID.values() if predicate(edge)] 

2430 for edge in delEdges: 

2431 del self._edgesWithID[edge._id] 

2432 

2433 edge._source._outboundEdges.remove(edge) 

2434 edge._destination._inboundEdges.remove(edge) 

2435 edge._Delete() 

2436 

2437 for edge in self._edgesWithoutID: 

2438 if predicate(edge): 

2439 self._edgesWithoutID.remove(edge) 

2440 

2441 edge._source._outboundEdges.remove(edge) 

2442 edge._destination._inboundEdges.remove(edge) 

2443 edge._Delete() 

2444 

2445 def RemoveLinks(self, predicate: Nullable[Callable[[Link], bool]] = None): 

2446 """ 

2447 Remove all or selected links of a graph. 

2448 

2449 If parameter ``predicate`` is not None, the given filter function is used to skip links. 

2450 

2451 :param predicate: Filter function accepting any link and returning a boolean. 

2452 """ 

2453 if predicate is None: 

2454 for link in self._linksWithoutID: 

2455 link._Delete() 

2456 

2457 for link in self._linksWithID.values(): 

2458 link._Delete() 

2459 

2460 self._linksWithoutID = [] 

2461 self._linksWithID = {} 

2462 

2463 for vertex in self._verticesWithoutID: 

2464 vertex._inboundLinks = [] 

2465 vertex._outboundLinks = [] 

2466 

2467 for vertex in self._verticesWithID.values(): 

2468 vertex._inboundLinks = [] 

2469 vertex._outboundLinks = [] 

2470 

2471 else: 

2472 delLinks = [link for link in self._linksWithID.values() if predicate(link)] 

2473 for link in delLinks: 

2474 del self._linksWithID[link._id] 

2475 

2476 link._source._outboundLinks.remove(link) 

2477 link._destination._inboundLinks.remove(link) 

2478 link._Delete() 

2479 

2480 for link in self._linksWithoutID: 

2481 if predicate(link): 

2482 self._linksWithoutID.remove(link) 

2483 

2484 link._source._outboundLinks.remove(link) 

2485 link._destination._inboundLinks.remove(link) 

2486 link._Delete() 

2487 

2488 def HasCycle(self) -> bool: 

2489 """ 

2490 .. todo:: GRAPH::BaseGraph::HasCycle Needs documentation. 

2491 

2492 """ 

2493 # IsAcyclic ? 

2494 

2495 # Handle trivial case if graph is empty 

2496 if len(self._verticesWithID) + len(self._verticesWithoutID) == 0: 2496 ↛ 2497line 2496 didn't jump to line 2497 because the condition on line 2496 was never true

2497 return False 

2498 

2499 outboundEdgeCounts = {} 

2500 leafVertices = [] 

2501 

2502 for vertex in self._verticesWithoutID: 

2503 if (count := len(vertex._outboundEdges)) == 0: 

2504 leafVertices.append(vertex) 

2505 else: 

2506 outboundEdgeCounts[vertex] = count 

2507 

2508 for vertex in self._verticesWithID.values(): 

2509 if (count := len(vertex._outboundEdges)) == 0: 

2510 leafVertices.append(vertex) 

2511 else: 

2512 outboundEdgeCounts[vertex] = count 

2513 

2514 # If there are no leafs, then each vertex has at least one inbound and one outbound edges. Thus, there is a cycle. 

2515 if not leafVertices: 2515 ↛ 2516line 2515 didn't jump to line 2516 because the condition on line 2515 was never true

2516 return True 

2517 

2518 overallCount = len(outboundEdgeCounts) + len(leafVertices) 

2519 

2520 for vertex in leafVertices: 

2521 overallCount -= 1 

2522 for inboundEdge in vertex._inboundEdges: 

2523 sourceVertex = inboundEdge.Source 

2524 count = outboundEdgeCounts[sourceVertex] - 1 

2525 outboundEdgeCounts[sourceVertex] = count 

2526 if count == 0: 

2527 leafVertices.append(sourceVertex) 

2528 

2529 # If all vertices were processed, no cycle exists. 

2530 if overallCount == 0: 

2531 return False 

2532 # If there are remaining vertices, then a cycle exists. 

2533 elif overallCount > 0: 

2534 return True 

2535 

2536 raise InternalError(f"Graph data structure is corrupted.") # pragma: no cover 

2537 

2538 

2539@export 

2540class Subgraph( 

2541 BaseGraph[ 

2542 SubgraphDictKeyType, SubgraphDictValueType, 

2543 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2544 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2545 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2546 ], 

2547 Generic[ 

2548 SubgraphDictKeyType, SubgraphDictValueType, 

2549 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2550 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2551 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2552 ] 

2553): 

2554 """ 

2555 .. todo:: GRAPH::Subgraph Needs documentation. 

2556 

2557 """ 

2558 

2559 _graph: 'Graph' 

2560 

2561 def __init__( 

2562 self, 

2563 graph: 'Graph', 

2564 name: Nullable[str] = None, 

2565 # vertices: Nullable[Iterable[Vertex]] = None, 

2566 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

2567 ) -> None: 

2568 """ 

2569 .. todo:: GRAPH::Subgraph::init Needs documentation. 

2570 

2571 :param graph: The reference to the graph. 

2572 :param name: The optional name of the new sub-graph. 

2573 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

2574 """ 

2575 if graph is None: 2575 ↛ 2576line 2575 didn't jump to line 2576 because the condition on line 2575 was never true

2576 raise ValueError("Parameter 'graph' is None.") 

2577 if not isinstance(graph, Graph): 2577 ↛ 2578line 2577 didn't jump to line 2578 because the condition on line 2577 was never true

2578 ex = TypeError("Parameter 'graph' is not of type 'Graph'.") 

2579 ex.add_note(f"Got type '{getFullyQualifiedName(graph)}'.") 

2580 raise ex 

2581 

2582 super().__init__(name, keyValuePairs) 

2583 

2584 graph._subgraphs.add(self) 

2585 

2586 self._graph = graph 

2587 

2588 def __del__(self): 

2589 """ 

2590 .. todo:: GRAPH::Subgraph::del Needs documentation. 

2591 

2592 """ 

2593 super().__del__() 

2594 

2595 @readonly 

2596 def Graph(self) -> 'Graph': 

2597 """ 

2598 Read-only property to access the graph, this subgraph is associated to (:attr:`_graph`). 

2599 

2600 :returns: The graph this subgraph is associated to. 

2601 """ 

2602 return self._graph 

2603 

2604 def __str__(self) -> str: 

2605 """ 

2606 .. todo:: GRAPH::Subgraph::str Needs documentation. 

2607 

2608 """ 

2609 return self._name if self._name is not None else "Unnamed subgraph" 

2610 

2611 

2612@export 

2613class View( 

2614 BaseWithVertices[ 

2615 ViewDictKeyType, ViewDictValueType, 

2616 GraphDictKeyType, GraphDictValueType, 

2617 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2618 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2619 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2620 ], 

2621 Generic[ 

2622 ViewDictKeyType, ViewDictValueType, 

2623 GraphDictKeyType, GraphDictValueType, 

2624 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2625 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2626 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2627 ] 

2628): 

2629 """ 

2630 .. todo:: GRAPH::View Needs documentation. 

2631 

2632 """ 

2633 

2634 def __init__( 

2635 self, 

2636 graph: 'Graph', 

2637 name: Nullable[str] = None, 

2638 vertices: Nullable[Iterable[Vertex]] = None, 

2639 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

2640 ) -> None: 

2641 """ 

2642 .. todo:: GRAPH::View::init Needs documentation. 

2643 

2644 :param graph: The reference to the graph. 

2645 :param name: The optional name of the new view. 

2646 :param vertices: The optional list of vertices in the new view. 

2647 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

2648 """ 

2649 super().__init__(graph, name, vertices, keyValuePairs) 

2650 

2651 graph._views.add(self) 

2652 

2653 def __del__(self): 

2654 """ 

2655 .. todo:: GRAPH::View::del Needs documentation. 

2656 

2657 """ 

2658 super().__del__() 

2659 

2660 def __str__(self) -> str: 

2661 """ 

2662 .. todo:: GRAPH::View::str Needs documentation. 

2663 

2664 """ 

2665 return self._name if self._name is not None else "Unnamed view" 

2666 

2667 

2668@export 

2669class Component( 

2670 BaseWithVertices[ 

2671 ComponentDictKeyType, ComponentDictValueType, 

2672 GraphDictKeyType, GraphDictValueType, 

2673 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2674 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2675 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2676 ], 

2677 Generic[ 

2678 ComponentDictKeyType, ComponentDictValueType, 

2679 GraphDictKeyType, GraphDictValueType, 

2680 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2681 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2682 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2683 ] 

2684): 

2685 """ 

2686 .. todo:: GRAPH::Component Needs documentation. 

2687 

2688 """ 

2689 

2690 def __init__( 

2691 self, 

2692 graph: 'Graph', 

2693 name: Nullable[str] = None, 

2694 vertices: Nullable[Iterable[Vertex]] = None, 

2695 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

2696 ) -> None: 

2697 """ 

2698 .. todo:: GRAPH::Component::init Needs documentation. 

2699 

2700 :param graph: The reference to the graph. 

2701 :param name: The optional name of the new component. 

2702 :param vertices: The optional list of vertices in the new component. 

2703 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

2704 """ 

2705 super().__init__(graph, name, vertices, keyValuePairs) 

2706 

2707 graph._components.add(self) 

2708 

2709 def __del__(self): 

2710 """ 

2711 .. todo:: GRAPH::Component::del Needs documentation. 

2712 

2713 """ 

2714 super().__del__() 

2715 

2716 def __str__(self) -> str: 

2717 """ 

2718 .. todo:: GRAPH::Component::str Needs documentation. 

2719 

2720 """ 

2721 return self._name if self._name is not None else "Unnamed component" 

2722 

2723 

2724@export 

2725class Graph( 

2726 BaseGraph[ 

2727 GraphDictKeyType, GraphDictValueType, 

2728 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2729 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2730 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2731 ], 

2732 Generic[ 

2733 GraphDictKeyType, GraphDictValueType, 

2734 ComponentDictKeyType, ComponentDictValueType, 

2735 SubgraphDictKeyType, SubgraphDictValueType, 

2736 ViewDictKeyType, ViewDictValueType, 

2737 VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, 

2738 EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, 

2739 LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType 

2740 ] 

2741): 

2742 """ 

2743 A **graph** data structure is represented by an instance of :class:`~pyTooling.Graph.Graph` holding references to 

2744 all nodes. Nodes are instances of :class:`~pyTooling.Graph.Vertex` classes and directed links between nodes are 

2745 made of :class:`~pyTooling.Graph.Edge` instances. A graph can have attached meta information as key-value-pairs. 

2746 """ 

2747 _subgraphs: Set[Subgraph[SubgraphDictKeyType, SubgraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2748 _views: Set[View[ViewDictKeyType, ViewDictValueType, GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2749 _components: Set[Component[ComponentDictKeyType, ComponentDictValueType, GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]] 

2750 

2751 def __init__( 

2752 self, 

2753 name: Nullable[str] = None, 

2754 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None 

2755 ) -> None: 

2756 """ 

2757 .. todo:: GRAPH::Graph::init Needs documentation. 

2758 

2759 :param name: The optional name of the new graph. 

2760 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs.# 

2761 """ 

2762 super().__init__(name, keyValuePairs) 

2763 

2764 self._subgraphs = set() 

2765 self._views = set() 

2766 self._components = set() 

2767 

2768 def __del__(self): 

2769 """ 

2770 .. todo:: GRAPH::Graph::del Needs documentation. 

2771 

2772 """ 

2773 try: 

2774 del self._subgraphs 

2775 del self._views 

2776 del self._components 

2777 except AttributeError: 

2778 pass 

2779 

2780 super().__del__() 

2781 

2782 @readonly 

2783 def Subgraphs(self) -> Set[Subgraph]: 

2784 """Read-only property to access the subgraphs in this graph (:attr:`_subgraphs`). 

2785 

2786 :returns: The set of subgraphs in this graph.""" 

2787 return self._subgraphs 

2788 

2789 @readonly 

2790 def Views(self) -> Set[View]: 

2791 """Read-only property to access the views in this graph (:attr:`_views`). 

2792 

2793 :returns: The set of views in this graph.""" 

2794 return self._views 

2795 

2796 @readonly 

2797 def Components(self) -> Set[Component]: 

2798 """Read-only property to access the components in this graph (:attr:`_components`). 

2799 

2800 :returns: The set of components in this graph.""" 

2801 return self._components 

2802 

2803 @readonly 

2804 def SubgraphCount(self) -> int: 

2805 """Read-only property to access the number of subgraphs in this graph. 

2806 

2807 :returns: The number of subgraphs in this graph.""" 

2808 return len(self._subgraphs) 

2809 

2810 @readonly 

2811 def ViewCount(self) -> int: 

2812 """Read-only property to access the number of views in this graph. 

2813 

2814 :returns: The number of views in this graph.""" 

2815 return len(self._views) 

2816 

2817 @readonly 

2818 def ComponentCount(self) -> int: 

2819 """Read-only property to access the number of components in this graph. 

2820 

2821 :returns: The number of components in this graph.""" 

2822 return len(self._components) 

2823 

2824 def __iter__(self) -> typing_Iterator[Vertex[GraphDictKeyType, GraphDictValueType, VertexIDType, VertexWeightType, VertexValueType, VertexDictKeyType, VertexDictValueType, EdgeIDType, EdgeWeightType, EdgeValueType, EdgeDictKeyType, EdgeDictValueType, LinkIDType, LinkWeightType, LinkValueType, LinkDictKeyType, LinkDictValueType]]: 

2825 """ 

2826 .. todo:: GRAPH::Graph::iter Needs documentation. 

2827 

2828 """ 

2829 def gen(): 

2830 yield from self._verticesWithoutID 

2831 yield from self._verticesWithID 

2832 return iter(gen()) 

2833 

2834 def HasVertexByID(self, vertexID: Nullable[VertexIDType]) -> bool: 

2835 """ 

2836 .. todo:: GRAPH::Graph::HasVertexByID Needs documentation. 

2837 

2838 """ 

2839 if vertexID is None: 

2840 return len(self._verticesWithoutID) >= 1 

2841 else: 

2842 return vertexID in self._verticesWithID 

2843 

2844 def HasVertexByValue(self, value: Nullable[VertexValueType]) -> bool: 

2845 """ 

2846 .. todo:: GRAPH::Graph::HasVertexByValue Needs documentation. 

2847 

2848 """ 

2849 return any(vertex._value == value for vertex in chain(self._verticesWithoutID, self._verticesWithID.values())) 

2850 

2851 def GetVertexByID(self, vertexID: Nullable[VertexIDType]) -> Vertex: 

2852 """ 

2853 .. todo:: GRAPH::Graph::GetVertexByID Needs documentation. 

2854 

2855 """ 

2856 if vertexID is None: 

2857 if (l := len(self._verticesWithoutID)) == 1: 

2858 return self._verticesWithoutID[0] 

2859 elif l == 0: 

2860 raise KeyError(f"Found no vertex with ID `None`.") 

2861 else: 

2862 raise KeyError(f"Found multiple vertices with ID `None`.") 

2863 else: 

2864 return self._verticesWithID[vertexID] 

2865 

2866 def GetVertexByValue(self, value: Nullable[VertexValueType]) -> Vertex: 

2867 """ 

2868 .. todo:: GRAPH::Graph::GetVertexByValue Needs documentation. 

2869 

2870 """ 

2871 # FIXME: optimize: iterate only until first item is found and check for a second to produce error 

2872 vertices = [vertex for vertex in chain(self._verticesWithoutID, self._verticesWithID.values()) if vertex._value == value] 

2873 if (l := len(vertices)) == 1: 

2874 return vertices[0] 

2875 elif l == 0: 

2876 raise KeyError(f"Found no vertex with Value == `{value}`.") 

2877 else: 

2878 raise KeyError(f"Found multiple vertices with Value == `{value}`.") 

2879 

2880 def CopyGraph(self) -> 'Graph': 

2881 raise NotImplementedError() 

2882 

2883 def CopyVertices(self, predicate: Nullable[Callable[[Vertex], bool]] = None, copyGraphDict: bool = True, copyVertexDict: bool = True) -> 'Graph': 

2884 """ 

2885 Create a new graph and copy all or selected vertices of the original graph. 

2886 

2887 If parameter ``predicate`` is not None, the given filter function is used to skip vertices. 

2888 

2889 :param predicate: Filter function accepting any vertex and returning a boolean. 

2890 :param copyGraphDict: If ``True``, copy all graph attached attributes into the new graph. 

2891 :param copyVertexDict: If ``True``, copy all vertex attached attributes into the new vertices. 

2892 """ 

2893 graph = Graph(self._name) 

2894 if copyGraphDict: 

2895 graph._dict = self._dict.copy() 

2896 

2897 if predicate is None: 

2898 for vertex in self._verticesWithoutID: 

2899 v = Vertex(None, vertex._value, graph=graph) 

2900 if copyVertexDict: 

2901 v._dict = vertex._dict.copy() 

2902 

2903 for vertexID, vertex in self._verticesWithID.items(): 

2904 v = Vertex(vertexID, vertex._value, graph=graph) 

2905 if copyVertexDict: 

2906 v._dict = vertex._dict.copy() 

2907 else: 

2908 for vertex in self._verticesWithoutID: 

2909 if predicate(vertex): 

2910 v = Vertex(None, vertex._value, graph=graph) 

2911 if copyVertexDict: 2911 ↛ 2908line 2911 didn't jump to line 2908 because the condition on line 2911 was always true

2912 v._dict = vertex._dict.copy() 

2913 

2914 for vertexID, vertex in self._verticesWithID.items(): 

2915 if predicate(vertex): 

2916 v = Vertex(vertexID, vertex._value, graph=graph) 

2917 if copyVertexDict: 2917 ↛ 2918line 2917 didn't jump to line 2918 because the condition on line 2917 was never true

2918 v._dict = vertex._dict.copy() 

2919 

2920 return graph 

2921 

2922 # class Iterator(): 

2923 # visited = [False for _ in range(self.__len__())] 

2924 

2925 # def CheckForNegativeCycles(self): 

2926 # raise NotImplementedError() 

2927 # # Bellman-Ford 

2928 # # Floyd-Warshall 

2929 # 

2930 # def IsStronglyConnected(self): 

2931 # raise NotImplementedError() 

2932 # 

2933 # def GetStronglyConnectedComponents(self): 

2934 # raise NotImplementedError() 

2935 # # Tarjan's and Kosaraju's algorithm 

2936 # 

2937 # def TravelingSalesmanProblem(self): 

2938 # raise NotImplementedError() 

2939 # # Held-Karp 

2940 # # branch and bound 

2941 # 

2942 # def GetBridges(self): 

2943 # raise NotImplementedError() 

2944 # 

2945 # def GetArticulationPoints(self): 

2946 # raise NotImplementedError() 

2947 # 

2948 # def MinimumSpanningTree(self): 

2949 # raise NotImplementedError() 

2950 # # Kruskal 

2951 # # Prim's algorithm 

2952 # # Buruvka's algorithm 

2953 

2954 def __repr__(self) -> str: 

2955 """ 

2956 .. todo:: GRAPH::Graph::repr Needs documentation. 

2957 

2958 """ 

2959 statistics = f", vertices: {self.VertexCount}, edges: {self.EdgeCount}" 

2960 if self._name is None: 

2961 return f"<graph: unnamed graph{statistics}>" 

2962 else: 

2963 return f"<graph: '{self._name}'{statistics}>" 

2964 

2965 def __str__(self) -> str: 

2966 """ 

2967 .. todo:: GRAPH::Graph::str Needs documentation. 

2968 

2969 """ 

2970 if self._name is None: 

2971 return f"Graph: unnamed graph" 

2972 else: 

2973 return f"Graph: '{self._name}'"