scikit-network 0.30.0__cp39-cp39-win_amd64.whl → 0.32.1__cp39-cp39-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of scikit-network might be problematic. Click here for more details.

Files changed (187) hide show
  1. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/AUTHORS.rst +3 -0
  2. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/METADATA +31 -3
  3. scikit_network-0.32.1.dist-info/RECORD +228 -0
  4. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/WHEEL +1 -1
  5. sknetwork/__init__.py +1 -1
  6. sknetwork/base.py +67 -0
  7. sknetwork/classification/base.py +24 -24
  8. sknetwork/classification/base_rank.py +17 -25
  9. sknetwork/classification/diffusion.py +35 -35
  10. sknetwork/classification/knn.py +24 -21
  11. sknetwork/classification/metrics.py +1 -1
  12. sknetwork/classification/pagerank.py +10 -10
  13. sknetwork/classification/propagation.py +23 -20
  14. sknetwork/classification/tests/test_diffusion.py +13 -3
  15. sknetwork/classification/vote.cp39-win_amd64.pyd +0 -0
  16. sknetwork/classification/vote.cpp +14482 -10351
  17. sknetwork/classification/vote.pyx +1 -3
  18. sknetwork/clustering/__init__.py +3 -1
  19. sknetwork/clustering/base.py +36 -40
  20. sknetwork/clustering/kcenters.py +253 -0
  21. sknetwork/clustering/leiden.py +241 -0
  22. sknetwork/clustering/leiden_core.cp39-win_amd64.pyd +0 -0
  23. sknetwork/clustering/leiden_core.cpp +31564 -0
  24. sknetwork/clustering/leiden_core.pyx +124 -0
  25. sknetwork/clustering/louvain.py +133 -102
  26. sknetwork/clustering/louvain_core.cp39-win_amd64.pyd +0 -0
  27. sknetwork/clustering/louvain_core.cpp +22457 -18792
  28. sknetwork/clustering/louvain_core.pyx +86 -96
  29. sknetwork/clustering/postprocess.py +2 -2
  30. sknetwork/clustering/propagation_clustering.py +15 -19
  31. sknetwork/clustering/tests/test_API.py +8 -4
  32. sknetwork/clustering/tests/test_kcenters.py +92 -0
  33. sknetwork/clustering/tests/test_leiden.py +34 -0
  34. sknetwork/clustering/tests/test_louvain.py +3 -4
  35. sknetwork/data/__init__.py +2 -1
  36. sknetwork/data/base.py +28 -0
  37. sknetwork/data/load.py +38 -37
  38. sknetwork/data/models.py +18 -18
  39. sknetwork/data/parse.py +54 -33
  40. sknetwork/data/test_graphs.py +2 -2
  41. sknetwork/data/tests/test_API.py +1 -1
  42. sknetwork/data/tests/test_base.py +14 -0
  43. sknetwork/data/tests/test_load.py +1 -1
  44. sknetwork/data/tests/test_parse.py +9 -12
  45. sknetwork/data/tests/test_test_graphs.py +1 -2
  46. sknetwork/data/toy_graphs.py +18 -18
  47. sknetwork/embedding/__init__.py +0 -1
  48. sknetwork/embedding/base.py +21 -20
  49. sknetwork/embedding/force_atlas.py +3 -2
  50. sknetwork/embedding/louvain_embedding.py +2 -2
  51. sknetwork/embedding/random_projection.py +5 -3
  52. sknetwork/embedding/spectral.py +0 -73
  53. sknetwork/embedding/tests/test_API.py +4 -28
  54. sknetwork/embedding/tests/test_louvain_embedding.py +4 -9
  55. sknetwork/embedding/tests/test_random_projection.py +2 -2
  56. sknetwork/embedding/tests/test_spectral.py +5 -8
  57. sknetwork/embedding/tests/test_svd.py +1 -1
  58. sknetwork/gnn/base.py +4 -4
  59. sknetwork/gnn/base_layer.py +3 -3
  60. sknetwork/gnn/gnn_classifier.py +45 -89
  61. sknetwork/gnn/layer.py +1 -1
  62. sknetwork/gnn/loss.py +1 -1
  63. sknetwork/gnn/optimizer.py +4 -3
  64. sknetwork/gnn/tests/test_base_layer.py +4 -4
  65. sknetwork/gnn/tests/test_gnn_classifier.py +12 -35
  66. sknetwork/gnn/utils.py +8 -8
  67. sknetwork/hierarchy/base.py +29 -2
  68. sknetwork/hierarchy/louvain_hierarchy.py +45 -41
  69. sknetwork/hierarchy/paris.cp39-win_amd64.pyd +0 -0
  70. sknetwork/hierarchy/paris.cpp +27369 -22852
  71. sknetwork/hierarchy/paris.pyx +7 -9
  72. sknetwork/hierarchy/postprocess.py +16 -16
  73. sknetwork/hierarchy/tests/test_API.py +1 -1
  74. sknetwork/hierarchy/tests/test_algos.py +5 -0
  75. sknetwork/hierarchy/tests/test_metrics.py +1 -1
  76. sknetwork/linalg/__init__.py +1 -1
  77. sknetwork/linalg/diteration.cp39-win_amd64.pyd +0 -0
  78. sknetwork/linalg/diteration.cpp +13474 -9454
  79. sknetwork/linalg/diteration.pyx +0 -2
  80. sknetwork/linalg/eig_solver.py +1 -1
  81. sknetwork/linalg/{normalization.py → normalizer.py} +18 -15
  82. sknetwork/linalg/operators.py +1 -1
  83. sknetwork/linalg/ppr_solver.py +1 -1
  84. sknetwork/linalg/push.cp39-win_amd64.pyd +0 -0
  85. sknetwork/linalg/push.cpp +22993 -18807
  86. sknetwork/linalg/push.pyx +0 -2
  87. sknetwork/linalg/svd_solver.py +1 -1
  88. sknetwork/linalg/tests/test_normalization.py +3 -7
  89. sknetwork/linalg/tests/test_operators.py +4 -8
  90. sknetwork/linalg/tests/test_ppr.py +1 -1
  91. sknetwork/linkpred/base.py +13 -2
  92. sknetwork/linkpred/nn.py +6 -6
  93. sknetwork/log.py +19 -0
  94. sknetwork/path/__init__.py +4 -3
  95. sknetwork/path/dag.py +54 -0
  96. sknetwork/path/distances.py +98 -0
  97. sknetwork/path/search.py +13 -47
  98. sknetwork/path/shortest_path.py +37 -162
  99. sknetwork/path/tests/test_dag.py +37 -0
  100. sknetwork/path/tests/test_distances.py +62 -0
  101. sknetwork/path/tests/test_search.py +26 -11
  102. sknetwork/path/tests/test_shortest_path.py +31 -36
  103. sknetwork/ranking/__init__.py +0 -1
  104. sknetwork/ranking/base.py +13 -8
  105. sknetwork/ranking/betweenness.cp39-win_amd64.pyd +0 -0
  106. sknetwork/ranking/betweenness.cpp +5709 -3017
  107. sknetwork/ranking/betweenness.pyx +0 -2
  108. sknetwork/ranking/closeness.py +7 -10
  109. sknetwork/ranking/pagerank.py +14 -14
  110. sknetwork/ranking/postprocess.py +12 -3
  111. sknetwork/ranking/tests/test_API.py +2 -4
  112. sknetwork/ranking/tests/test_betweenness.py +3 -3
  113. sknetwork/ranking/tests/test_closeness.py +3 -7
  114. sknetwork/ranking/tests/test_pagerank.py +11 -5
  115. sknetwork/ranking/tests/test_postprocess.py +5 -0
  116. sknetwork/regression/base.py +19 -2
  117. sknetwork/regression/diffusion.py +24 -10
  118. sknetwork/regression/tests/test_diffusion.py +8 -0
  119. sknetwork/test_base.py +35 -0
  120. sknetwork/test_log.py +15 -0
  121. sknetwork/topology/__init__.py +7 -8
  122. sknetwork/topology/cliques.cp39-win_amd64.pyd +0 -0
  123. sknetwork/topology/{kcliques.cpp → cliques.cpp} +23412 -20276
  124. sknetwork/topology/cliques.pyx +149 -0
  125. sknetwork/topology/core.cp39-win_amd64.pyd +0 -0
  126. sknetwork/topology/{kcore.cpp → core.cpp} +21732 -18867
  127. sknetwork/topology/core.pyx +90 -0
  128. sknetwork/topology/cycles.py +243 -0
  129. sknetwork/topology/minheap.cp39-win_amd64.pyd +0 -0
  130. sknetwork/{utils → topology}/minheap.cpp +19452 -15368
  131. sknetwork/{utils → topology}/minheap.pxd +1 -3
  132. sknetwork/{utils → topology}/minheap.pyx +1 -3
  133. sknetwork/topology/structure.py +3 -43
  134. sknetwork/topology/tests/test_cliques.py +11 -11
  135. sknetwork/topology/tests/test_core.py +19 -0
  136. sknetwork/topology/tests/test_cycles.py +65 -0
  137. sknetwork/topology/tests/test_structure.py +2 -16
  138. sknetwork/topology/tests/test_triangles.py +11 -15
  139. sknetwork/topology/tests/test_wl.py +72 -0
  140. sknetwork/topology/triangles.cp39-win_amd64.pyd +0 -0
  141. sknetwork/topology/triangles.cpp +5056 -2696
  142. sknetwork/topology/triangles.pyx +74 -89
  143. sknetwork/topology/weisfeiler_lehman.py +56 -86
  144. sknetwork/topology/weisfeiler_lehman_core.cp39-win_amd64.pyd +0 -0
  145. sknetwork/topology/weisfeiler_lehman_core.cpp +14727 -10622
  146. sknetwork/topology/weisfeiler_lehman_core.pyx +0 -2
  147. sknetwork/utils/__init__.py +1 -31
  148. sknetwork/utils/check.py +2 -2
  149. sknetwork/utils/format.py +5 -3
  150. sknetwork/utils/membership.py +2 -2
  151. sknetwork/utils/tests/test_check.py +3 -3
  152. sknetwork/utils/tests/test_format.py +3 -1
  153. sknetwork/utils/values.py +1 -1
  154. sknetwork/visualization/__init__.py +2 -2
  155. sknetwork/visualization/dendrograms.py +55 -7
  156. sknetwork/visualization/graphs.py +292 -72
  157. sknetwork/visualization/tests/test_dendrograms.py +9 -9
  158. sknetwork/visualization/tests/test_graphs.py +71 -62
  159. scikit_network-0.30.0.dist-info/RECORD +0 -227
  160. sknetwork/embedding/louvain_hierarchy.py +0 -142
  161. sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
  162. sknetwork/path/metrics.py +0 -148
  163. sknetwork/path/tests/test_metrics.py +0 -29
  164. sknetwork/ranking/harmonic.py +0 -82
  165. sknetwork/topology/dag.py +0 -74
  166. sknetwork/topology/dag_core.cp39-win_amd64.pyd +0 -0
  167. sknetwork/topology/dag_core.cpp +0 -23350
  168. sknetwork/topology/dag_core.pyx +0 -38
  169. sknetwork/topology/kcliques.cp39-win_amd64.pyd +0 -0
  170. sknetwork/topology/kcliques.pyx +0 -193
  171. sknetwork/topology/kcore.cp39-win_amd64.pyd +0 -0
  172. sknetwork/topology/kcore.pyx +0 -120
  173. sknetwork/topology/tests/test_cores.py +0 -21
  174. sknetwork/topology/tests/test_dag.py +0 -26
  175. sknetwork/topology/tests/test_wl_coloring.py +0 -49
  176. sknetwork/topology/tests/test_wl_kernel.py +0 -31
  177. sknetwork/utils/base.py +0 -35
  178. sknetwork/utils/minheap.cp39-win_amd64.pyd +0 -0
  179. sknetwork/utils/simplex.py +0 -140
  180. sknetwork/utils/tests/test_base.py +0 -28
  181. sknetwork/utils/tests/test_bunch.py +0 -16
  182. sknetwork/utils/tests/test_projection_simplex.py +0 -33
  183. sknetwork/utils/tests/test_verbose.py +0 -15
  184. sknetwork/utils/verbose.py +0 -37
  185. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/LICENSE +0 -0
  186. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/top_level.txt +0 -0
  187. /sknetwork/{utils → data}/timeout.py +0 -0
sknetwork/path/metrics.py DELETED
@@ -1,148 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Created on May, 2020
5
- @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
- """
7
- from typing import Union, Optional
8
-
9
- import numpy as np
10
- from scipy import sparse
11
-
12
- from sknetwork.path.shortest_path import get_distances
13
-
14
-
15
- def get_diameter(adjacency: Union[sparse.csr_matrix, np.ndarray],
16
- n_sources: Optional[Union[int, float]] = None,
17
- unweighted: bool = False, n_jobs: Optional[int] = None) -> int:
18
- """Lower bound on the diameter of a graph which is the length of the longest
19
- shortest path between two nodes.
20
-
21
-
22
- Parameters
23
- ----------
24
- adjacency :
25
- Adjacency matrix of the graph.
26
- n_sources :
27
- Number of node sources to use for approximation.
28
-
29
- * If None, compute exact diameter.
30
- * If int, sample n_sample source nodes at random.
31
- * If float, sample (n_samples * n) source nodes at random.
32
- unweighted:
33
- Whether or not the graph is unweighted.
34
- n_jobs :
35
- If an integer value is given, denotes the number of workers to use (-1
36
- means the maximum number will be used).
37
- If ``None``, no parallel computations are made.
38
-
39
- Returns
40
- -------
41
- diameter : int
42
-
43
- Examples
44
- --------
45
- >>> from sknetwork.data import house
46
- >>> adjacency = house()
47
- >>> d_exact = get_diameter(adjacency)
48
- >>> d_exact
49
- 2
50
- >>> d_approx = get_diameter(adjacency, 2)
51
- >>> d_approx <= d_exact
52
- True
53
- >>> d_approx = get_diameter(adjacency, 0.5)
54
- >>> d_approx <= d_exact
55
- True
56
-
57
- Notes
58
- -----
59
- This is a basic implementation that computes distances between nodes and
60
- returns the maximum.
61
- """
62
- n = adjacency.shape[0]
63
- if n_sources is None or n_sources == n:
64
- sources = np.arange(n)
65
- else:
66
- if np.issubdtype(type(n_sources), np.floating) and n_sources < 1.:
67
- n_sources = int(n_sources * n)
68
- if np.issubdtype(type(n_sources), np.integer) and n_sources <= n:
69
- sources = np.random.choice(n, n_sources, replace=False)
70
- else:
71
- raise ValueError("n_sources must be either None, an integer smaller"
72
- "than the number of nodes or a float"
73
- "smaller than 1.")
74
- dists = get_distances(adjacency, sources, method='D', return_predecessors=False,
75
- unweighted=unweighted, n_jobs=n_jobs).astype(int)
76
- return dists.max()
77
-
78
-
79
- def get_radius(adjacency: Union[sparse.csr_matrix, np.ndarray],
80
- n_sources: Optional[Union[int, float]] = None,
81
- unweighted: bool = False, n_jobs: Optional[int] = None) -> int:
82
- """Computes the radius of the graph which. The radius of the graph is the
83
- minimum eccentricity of the graph.
84
-
85
- Parameters
86
- ----------
87
- adjacency :
88
- Adjacency matrix of the graph.
89
- n_sources :
90
- Number of node sources to use for approximation.
91
-
92
- * If None, compute exact diameter.
93
- * If int, sample n_sample source nodes at random.
94
- * If float, sample (n_samples * n) source nodes at random.
95
- unweighted:
96
- Whether the graph is unweighted.
97
- n_jobs :
98
- If an integer value is given, denotes the number of workers to use (-1
99
- means the maximum number will be used).
100
- If ``None``, no parallel computations are made.
101
-
102
- Returns
103
- -------
104
- radius : int
105
-
106
- Notes
107
- -----
108
- This is a basic implementation that computes distances between nodes and
109
- returns the maximum.
110
- """
111
-
112
- # Get the nodes.
113
- dists = get_distances(adjacency, sources=n_sources, method='D',
114
- return_predecessors=False,
115
- unweighted=unweighted, n_jobs=n_jobs).astype(int)
116
- # Get the eccentricities of each node.
117
- eccentricities = dists.max(axis=1)
118
-
119
- return eccentricities.min()
120
-
121
-
122
- def get_eccentricity(adjacency: Union[sparse.csr_matrix, np.ndarray], node: int,
123
- unweighted: bool = False,
124
- n_jobs: Optional[int] = None) -> int:
125
- """Computes the eccentricity of a node. The eccentricity of a node, u, is the
126
- maximum length of the shortest paths from u to the other nodes in the graph.
127
-
128
- Parameters
129
- ----------
130
- adjacency :
131
- Adjacency matrix of the graph.
132
- node:
133
- The node to compute the eccentricity for.
134
- unweighted:
135
- Whether or not the graph is unweighted.
136
- n_jobs :
137
- If an integer value is given, denotes the number of workers to use (-1
138
- means the maximum number will be used).
139
- If ``None``, no parallel computations are made.
140
-
141
- Returns
142
- -------
143
- eccentricity : int
144
- """
145
-
146
- dists = get_distances(adjacency, node, method='D', return_predecessors=False,
147
- unweighted=unweighted, n_jobs=n_jobs).astype(int)
148
- return dists.max()
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """"tests for metrics.py"""
4
- import unittest
5
-
6
- from sknetwork.data import house
7
- from sknetwork.path import get_diameter, get_eccentricity, get_radius
8
-
9
-
10
-
11
- class TestMetrics(unittest.TestCase):
12
-
13
- def test_diameter_1(self):
14
- adjacency = house()
15
- with self.assertRaises(ValueError):
16
- get_diameter(adjacency, 2.5)
17
- def test_diameter_2(self):
18
- adjacency = house()
19
- self.assertEqual(get_diameter(adjacency), 2)
20
- def test_eccentricity_1(self):
21
- adjacency = house()
22
- self.assertEqual(get_eccentricity(adjacency, 1), 2)
23
- def test_radius_1(self):
24
- adjacency = house()
25
- self.assertEqual(get_radius(adjacency), 2)
26
- def test_radius_2(self):
27
- adjacency = house()
28
- self.assertEqual(get_radius(adjacency,[0,1]), 2)
29
-
@@ -1,82 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Created on November 19 2019
5
- @author: Quentin Lutz <qlutz@enst.fr>
6
- """
7
- from typing import Union, Optional
8
-
9
- import numpy as np
10
- from scipy import sparse
11
-
12
- from sknetwork.path.shortest_path import get_distances
13
- from sknetwork.ranking.base import BaseRanking
14
- from sknetwork.utils.check import check_format, check_square
15
-
16
-
17
- class Harmonic(BaseRanking):
18
- """Harmonic centrality of each node in a connected graph, corresponding to the average inverse length of
19
- the shortest paths from that node to all the other ones.
20
-
21
- For a directed graph, the harmonic centrality is computed in terms of outgoing paths.
22
-
23
- Parameters
24
- ----------
25
- n_jobs:
26
- If an integer value is given, denotes the number of workers to use (-1 means the maximum number will be used).
27
- If ``None``, no parallel computations are made.
28
-
29
- Attributes
30
- ----------
31
- scores_ : np.ndarray
32
- Score of each node.
33
-
34
- Example
35
- -------
36
- >>> from sknetwork.ranking import Harmonic
37
- >>> from sknetwork.data import house
38
- >>> harmonic = Harmonic()
39
- >>> adjacency = house()
40
- >>> scores = harmonic.fit_predict(adjacency)
41
- >>> np.round(scores, 2)
42
- array([3. , 3.5, 3. , 3. , 3.5])
43
-
44
- References
45
- ----------
46
- Marchiori, M., & Latora, V. (2000).
47
- `Harmony in the small-world.
48
- <https://arxiv.org/pdf/cond-mat/0008357.pdf>`_
49
- Physica A: Statistical Mechanics and its Applications, 285(3-4), 539-546.
50
- """
51
-
52
- def __init__(self, n_jobs: Optional[int] = None):
53
- super(Harmonic, self).__init__()
54
-
55
- self.n_jobs = n_jobs
56
-
57
- def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'Harmonic':
58
- """Harmonic centrality for connected graphs.
59
-
60
- Parameters
61
- ----------
62
- adjacency :
63
- Adjacency matrix of the graph.
64
-
65
- Returns
66
- -------
67
- self: :class:`Harmonic`
68
- """
69
- adjacency = check_format(adjacency)
70
- check_square(adjacency)
71
- n = adjacency.shape[0]
72
- indices = np.arange(n)
73
-
74
- dists = get_distances(adjacency, n_jobs=self.n_jobs, sources=indices)
75
-
76
- np.fill_diagonal(dists, 1)
77
- inv = (1 / dists)
78
- np.fill_diagonal(inv, 0)
79
-
80
- self.scores_ = inv.dot(np.ones(n))
81
-
82
- return self
sknetwork/topology/dag.py DELETED
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Created on Jun 3, 2020
5
- @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
- """
7
- import numpy as np
8
- from scipy import sparse
9
- from sknetwork.topology.dag_core import fit_core
10
-
11
- from sknetwork.utils.base import Algorithm
12
-
13
-
14
- class DAG(Algorithm):
15
- """Build a Directed Acyclic Graph from an adjacency.
16
-
17
- * Graphs
18
- * DiGraphs
19
-
20
- Parameters
21
- ----------
22
- ordering : str
23
- A method to sort the nodes.
24
-
25
- * If None, the default order is the index.
26
- * If ``'degree'``, the nodes are sorted by ascending degree.
27
-
28
- Attributes
29
- ----------
30
- indptr_ : np.ndarray
31
- Pointer index as for CSR format.
32
- indices_ : np.ndarray
33
- Indices as for CSR format.
34
- """
35
- def __init__(self, ordering: str = None):
36
- super(DAG, self).__init__()
37
- self.ordering = ordering
38
- self.indptr_ = None
39
- self.indices_ = None
40
-
41
- def fit(self, adjacency: sparse.csr_matrix, sorted_nodes=None):
42
- """Fit algorithm to the data.
43
-
44
- Parameters
45
- ----------
46
- adjacency :
47
- Adjacency matrix of the graph.
48
- sorted_nodes : np.ndarray
49
- An order on the nodes such that the DAG only contains edges (i, j) such that
50
- ``sorted_nodes[i] < sorted_nodes[j]``.
51
- """
52
- indptr = adjacency.indptr.astype(np.int32)
53
- indices = adjacency.indices.astype(np.int32)
54
-
55
- if sorted_nodes is not None:
56
- if adjacency.shape[0] != sorted_nodes.shape[0]:
57
- raise ValueError('Dimensions mismatch between adjacency and sorted_nodes.')
58
- else:
59
- sorted_nodes = sorted_nodes.astype(np.int32)
60
- else:
61
- if self.ordering is None:
62
- sorted_nodes = np.arange(adjacency.shape[0]).astype(np.int32)
63
- elif self.ordering == 'degree':
64
- degrees = indptr[1:] - indptr[:-1]
65
- sorted_nodes = np.argsort(degrees).astype(np.int32)
66
- else:
67
- raise ValueError('Unknown ordering of nodes.')
68
-
69
- ix = np.zeros(adjacency.shape[0], dtype=np.int32)
70
- dag_indptr, dag_indices = fit_core(indptr, indices, sorted_nodes, ix)
71
- self.indptr_ = np.asarray(dag_indptr)
72
- self.indices_ = np.asarray(dag_indices)
73
-
74
- return self