neural-spatial-nets 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: neural_spatial_nets
3
+ Version: 0.1.0
4
+ Summary: A Python library for spatial and Hopfield neural networks.
5
+ Author-email: SK SAMIUL HQ <sksamiul5045@gmail.com>
6
+ Project-URL: Homepage, https://github.com/sksamiul5045-lang/neural_spatial_nets
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # neural_spatial_nets
16
+
17
+ A Python package for implementing spatial, Euclidean, and Hopfield neural networks.
18
+
19
+ ## Installation
20
+
21
+ You can install this package locally for development:
22
+
23
+ ```bash
24
+ pip install -e .
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from neural_spatial_nets import SpatialNetwork, Euclidean_network, HP_network
31
+ ```
@@ -0,0 +1,17 @@
1
+ # neural_spatial_nets
2
+
3
+ A Python package for implementing spatial, Euclidean, and Hopfield neural networks.
4
+
5
+ ## Installation
6
+
7
+ You can install this package locally for development:
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ from neural_spatial_nets import SpatialNetwork, Euclidean_network, HP_network
17
+ ```
@@ -0,0 +1,80 @@
1
+ import numpy as np
2
+
3
+ class Euclidean_network:
4
+
5
+ def __init__(self, N, d, beta, m):
6
+
7
+ self.N = N
8
+ self.d = d
9
+ self.beta = beta
10
+ self.m = m
11
+
12
+ self.positions = np.random.rand(N, d)
13
+
14
+ self.degree = np.zeros(N, dtype=int)
15
+ self.edges = []
16
+
17
+ # Initial complete graph of m nodes
18
+ for i in range(m):
19
+ for j in range(i + 1, m):
20
+ self.edges.append((i, j))
21
+ self.degree[i] += 1
22
+ self.degree[j] += 1
23
+
24
+ self.grow_network()
25
+
26
+ self.A = self.generate_adjacency_matrix()
27
+
28
+ def compute_distances(self,a,b):
29
+ return np.linalg.norm(self.positions[a] - self.positions[b])
30
+
31
+
32
+ def generate_adjacency_matrix(self):
33
+ adjacency_matrix = np.zeros((self.N, self.N), dtype=int)
34
+ for u,v in self.edges:
35
+ adjacency_matrix[u][v] = 1
36
+ adjacency_matrix[v][u] = 1
37
+ return adjacency_matrix
38
+
39
+ def link_length(self):
40
+ length = []
41
+ for u,v in self.edges:
42
+ length.append(self.compute_distances(u,v))
43
+ return np.array(length)
44
+
45
+ def grow_network(self):
46
+
47
+ for t in range(self.m, self.N):
48
+
49
+ weight = np.zeros(t)
50
+
51
+ for i in range(t):
52
+
53
+ l = self.compute_distances(t, i)
54
+
55
+ # avoid division by zero
56
+ l = max(l, 1e-12)
57
+
58
+ weight[i] = self.degree[i] * (l ** self.beta)
59
+
60
+ prob = weight / weight.sum()
61
+
62
+ targets = np.random.choice(
63
+ np.arange(t),
64
+ size=self.m,
65
+ replace=False,
66
+ p=prob
67
+ )
68
+
69
+ for target in targets:
70
+
71
+ self.edges.append((t, target))
72
+
73
+ self.degree[t] += 1
74
+ self.degree[target] += 1
75
+
76
+
77
+ def degree_distribution(self):
78
+ k, counts = np.unique(self.degree, return_counts=True)
79
+ Pk = counts / counts.sum()
80
+ return k, Pk
@@ -0,0 +1,97 @@
1
+ import numpy as np
2
+ import networkx as nx
3
+ import matplotlib.pyplot as plt
4
+
5
+ class HP_network:
6
+
7
+ def __init__(self, Network):
8
+ self.Network = Network
9
+ self.N = len(Network)
10
+ self.A = self.adjacency_matrix()
11
+ self.pattern = None
12
+ self.J = None
13
+
14
+ def adjacency_matrix(self):
15
+ return nx.adjacency_matrix(
16
+ self.Network
17
+ ).toarray().astype(float)
18
+
19
+ def train(self, patterns):
20
+
21
+ self.pattern = np.asarray(patterns)
22
+
23
+ self.J = np.zeros((self.N, self.N))
24
+
25
+ for p in self.pattern:
26
+ self.J += np.outer(p, p)
27
+
28
+ k_mean = np.mean(np.sum(self.A, axis=1))
29
+
30
+ self.J /= k_mean
31
+ self.J *= self.A
32
+
33
+ np.fill_diagonal(self.J, 0)
34
+
35
+ def energy(self, state):
36
+ return -0.5 * state @ self.J @ state
37
+
38
+ def overlap(self, target, state):
39
+ return np.dot(target, state) / self.N
40
+
41
+ def update_async(self, state):
42
+
43
+ new_state = state.copy()
44
+
45
+ for i in np.random.permutation(self.N):
46
+
47
+ h_i = self.J[i] @ new_state
48
+
49
+ if h_i >= 0:
50
+ new_state[i] = 1
51
+ else:
52
+ new_state[i] = -1
53
+
54
+ return new_state
55
+
56
+ def recall(self, initial_state, target_state, max_iter):
57
+
58
+ state = initial_state.copy()
59
+
60
+ energies = [self.energy(state)]
61
+ overlaps = [self.overlap(target_state, state)]
62
+ magnetizations = [np.mean(state)]
63
+ times = [0]
64
+
65
+ retrieval_time = max_iter
66
+
67
+ for t in range(1, max_iter + 1):
68
+
69
+ new_state = self.update_async(state)
70
+
71
+ energies.append(self.energy(new_state))
72
+ overlaps.append(self.overlap(target_state, new_state))
73
+ magnetizations.append(np.mean(new_state))
74
+ times.append(t)
75
+
76
+ if np.array_equal(new_state, state):
77
+ retrieval_time = t
78
+ state = new_state
79
+ break
80
+
81
+ state = new_state
82
+
83
+ return state, energies, overlaps, magnetizations, retrieval_time
84
+
85
+ def add_noise(self, pattern, noise_lv=0.10):
86
+
87
+ noisy = pattern.copy()
88
+
89
+ flip = np.random.choice(
90
+ self.N,
91
+ size=int(noise_lv * self.N),
92
+ replace=False
93
+ )
94
+
95
+ noisy[flip] *= -1
96
+
97
+ return noisy
@@ -0,0 +1,6 @@
1
+ from .spatial_network import SpatialNetwork
2
+ from .euclidean import Euclidean_network
3
+ from .hopfield import HP_network
4
+
5
+ # Defines what gets imported when someone uses "from neural_spatial_nets import *"
6
+ __all__ = ["SpatialNetwork", "Euclidean_network", "HP_network"]
@@ -0,0 +1,270 @@
1
+ import numpy as np
2
+ import networkx as nx
3
+
4
+
5
+ class SpatialNetwork:
6
+
7
+ def __init__(self, N, M=0, delta=0, dimension=1, seed=None):
8
+
9
+ self.N = N
10
+ self.M = M
11
+ self.delta = delta
12
+ self.dimension = dimension
13
+
14
+ self.rng = np.random.default_rng(seed)
15
+
16
+ self.network = nx.Graph()
17
+
18
+ self.positions = None
19
+ self.A = None
20
+
21
+ # Dimensions of the lattice
22
+ if self.dimension == 1:
23
+
24
+ self.L = N
25
+
26
+ elif self.dimension == 2:
27
+
28
+ self.Lx, self.Ly = self.get_dimensions()
29
+
30
+ else:
31
+
32
+ raise ValueError(
33
+ "dimension must be 1 or 2"
34
+ )
35
+
36
+
37
+ # ========================================================
38
+ # 2D lattice dimensions
39
+ # ========================================================
40
+
41
+ def get_dimensions(self):
42
+
43
+ Lx = int(np.sqrt(self.N))
44
+
45
+ while Lx > 1 and self.N % Lx != 0:
46
+
47
+ Lx -= 1
48
+
49
+ Ly = self.N // Lx
50
+
51
+ if Lx == 1:
52
+
53
+ raise ValueError(
54
+ "N must have two factors for a 2D periodic lattice"
55
+ )
56
+
57
+ return Lx, Ly
58
+
59
+
60
+ # ========================================================
61
+ # 2D coordinate -> node number
62
+ # ========================================================
63
+
64
+ def node(self, x, y):
65
+
66
+ return x * self.Ly + y
67
+
68
+
69
+ # ========================================================
70
+ # Distance
71
+ # ========================================================
72
+
73
+ def distance(self, i, j):
74
+
75
+ # -------------------------
76
+ # 1D periodic distance
77
+ # -------------------------
78
+
79
+ if self.dimension == 1:
80
+
81
+ d = abs(i - j)
82
+
83
+ return min(d,self.N - d)
84
+ # -------------------------
85
+ # 2D periodic distance
86
+ # -------------------------
87
+
88
+ elif self.dimension == 2:
89
+
90
+ x1, y1 = self.positions[i]
91
+ x2, y2 = self.positions[j]
92
+
93
+ dx = abs(x1 - x2)
94
+ dy = abs(y1 - y2)
95
+
96
+ dx = min(dx,self.Lx - dx)
97
+
98
+ dy = min(dy,self.Ly - dy)
99
+
100
+ return np.sqrt(dx**2 + dy**2)
101
+
102
+
103
+ # ========================================================
104
+ # Distance-dependent weight
105
+ # ========================================================
106
+
107
+ def probability(self, r):
108
+
109
+ if self.delta == 0:
110
+
111
+ return 1.0
112
+
113
+ return float(r) ** (-self.delta)
114
+
115
+
116
+ # ========================================================
117
+ # Construct regular lattice
118
+ # ========================================================
119
+
120
+ def regular_lattice(self):
121
+
122
+ # ----------------------------------------------------
123
+ # 1D periodic ring
124
+ # ----------------------------------------------------
125
+
126
+ if self.dimension == 1:
127
+
128
+ self.positions = np.arange(self.N).reshape(-1, 1)
129
+
130
+ self.network.add_nodes_from(range(self.N))
131
+ for i in range(self.N):
132
+
133
+ j = (i + 1) % self.N
134
+
135
+ self.network.add_edge(i,j)
136
+
137
+ # ----------------------------------------------------
138
+ # 2D periodic square lattice
139
+ # ----------------------------------------------------
140
+
141
+ elif self.dimension == 2:
142
+
143
+ self.positions = np.array([
144
+ (x, y)
145
+ for x in range(self.Lx)
146
+ for y in range(self.Ly)
147
+ ])
148
+
149
+ self.network.add_nodes_from(range(self.N))
150
+
151
+ for x in range(self.Lx):
152
+
153
+ for y in range(self.Ly):
154
+
155
+ i = self.node(x,y)
156
+
157
+ # Right neighbour
158
+ j = self.node((x + 1) % self.Lx , y)
159
+
160
+ self.network.add_edge(i,j)
161
+
162
+ # Up neighbour
163
+ j = self.node(x,(y + 1) % self.Ly)
164
+
165
+ self.network.add_edge(i,j)
166
+
167
+
168
+ # ========================================================
169
+ # Add M extra bonds
170
+ # ========================================================
171
+
172
+ def add_links(self):
173
+
174
+ pairs = []
175
+ weights = []
176
+
177
+ # ----------------------------------------------------
178
+ # Find all possible non-existing pairs
179
+ # ----------------------------------------------------
180
+
181
+ for i in range(self.N):
182
+
183
+ for j in range(i + 1, self.N):
184
+
185
+ # Do not select existing lattice bonds
186
+ if self.network.has_edge(i, j):
187
+
188
+ continue
189
+
190
+ # Distance
191
+ r = self.distance(i, j)
192
+
193
+ # No self loops
194
+ if r == 0:
195
+
196
+ continue
197
+
198
+ # Distance-dependent weight
199
+ w = self.probability(r)
200
+
201
+ pairs.append((i, j))
202
+
203
+ weights.append(w)
204
+
205
+
206
+ # ----------------------------------------------------
207
+ # Normalize probabilities
208
+ # ----------------------------------------------------
209
+
210
+ weights = np.asarray(weights,dtype=float)
211
+
212
+ weights /= weights.sum()
213
+
214
+ # ----------------------------------------------------
215
+ # Select M different pairs
216
+ # ----------------------------------------------------
217
+
218
+ M = min(self.M,len(pairs))
219
+
220
+ selected = self.rng.choice(
221
+ len(pairs),
222
+ size=M,
223
+ replace=False,
224
+ p=weights
225
+ )
226
+
227
+
228
+ # ----------------------------------------------------
229
+ # Add extra bonds
230
+ # ----------------------------------------------------
231
+
232
+ for k in selected:
233
+
234
+ i, j = pairs[k]
235
+
236
+ self.network.add_edge(i,j)
237
+
238
+
239
+ # ========================================================
240
+ # Generate network
241
+ # ========================================================
242
+
243
+ def generate(self):
244
+
245
+ self.regular_lattice()
246
+
247
+ if self.M > 0:
248
+
249
+ self.add_links()
250
+
251
+ self.A = nx.to_numpy_array(
252
+ self.network,
253
+ dtype=int)
254
+
255
+ return self.network
256
+
257
+
258
+ # ========================================================
259
+ # Adjacency matrix
260
+ # ========================================================
261
+
262
+ def adjacency_matrix(self):
263
+
264
+ if self.A is None:
265
+
266
+ self.generate()
267
+
268
+ return self.A
269
+
270
+
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: neural_spatial_nets
3
+ Version: 0.1.0
4
+ Summary: A Python library for spatial and Hopfield neural networks.
5
+ Author-email: SK SAMIUL HQ <sksamiul5045@gmail.com>
6
+ Project-URL: Homepage, https://github.com/sksamiul5045-lang/neural_spatial_nets
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # neural_spatial_nets
16
+
17
+ A Python package for implementing spatial, Euclidean, and Hopfield neural networks.
18
+
19
+ ## Installation
20
+
21
+ You can install this package locally for development:
22
+
23
+ ```bash
24
+ pip install -e .
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from neural_spatial_nets import SpatialNetwork, Euclidean_network, HP_network
31
+ ```
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ neural_spatial_nets/Euclidean.py
5
+ neural_spatial_nets/Hopfield.py
6
+ neural_spatial_nets/__init__.py
7
+ neural_spatial_nets/spatial_network.py
8
+ neural_spatial_nets.egg-info/PKG-INFO
9
+ neural_spatial_nets.egg-info/SOURCES.txt
10
+ neural_spatial_nets.egg-info/dependency_links.txt
11
+ neural_spatial_nets.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ neural_spatial_nets
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "neural_spatial_nets"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="SK SAMIUL HQ", email="sksamiul5045@gmail.com" },
10
+ ]
11
+ description = "A Python library for spatial and Hopfield neural networks."
12
+ readme = "README.md"
13
+ requires-python = ">=3.7"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ [project.urls]
21
+ "Homepage" = "https://github.com/sksamiul5045-lang/neural_spatial_nets"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+