gridity 0.2.2__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,59 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ build:
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest, windows-latest, macos-latest]
17
+
18
+ runs-on: ${{ matrix.os }}
19
+
20
+ steps:
21
+ - uses: actions/checkout@v5
22
+
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.13"
26
+
27
+ - name: Install maturin
28
+ run: pip install maturin==1.14.1
29
+
30
+ - name: Build wheels
31
+ env:
32
+ PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1"
33
+ run: |
34
+ maturin build --release --sdist --interpreter python3.11 python3.12 python3.13
35
+
36
+ - uses: actions/upload-artifact@v5
37
+ with:
38
+ name: wheels-${{ matrix.os }}
39
+ path: target/wheels/*
40
+
41
+
42
+ publish:
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+
46
+ permissions:
47
+ id-token: write
48
+
49
+ environment:
50
+ name: pypi
51
+
52
+ steps:
53
+ - uses: actions/download-artifact@v5
54
+ with:
55
+ pattern: wheels-*
56
+ merge-multiple: true
57
+ path: dist
58
+
59
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,19 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+
6
+ /target
7
+ *.pyd
8
+ *.so
9
+
10
+ # Models
11
+ *.pt
12
+ *.pth
13
+ *.ckpt
14
+ *.bin
15
+ *.safetensors
16
+
17
+ # IDE
18
+ .idea/
19
+ .vscode/
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Python 3.13" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
7
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/GRiD.iml" filepath="$PROJECT_DIR$/.idea/GRiD.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,280 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "approx"
7
+ version = "0.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
10
+ dependencies = [
11
+ "num-traits",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "autocfg"
16
+ version = "1.5.1"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
19
+
20
+ [[package]]
21
+ name = "cfg-if"
22
+ version = "1.0.4"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
25
+
26
+ [[package]]
27
+ name = "gridity"
28
+ version = "0.2.2"
29
+ dependencies = [
30
+ "approx",
31
+ "ndarray",
32
+ "numpy",
33
+ "pyo3",
34
+ ]
35
+
36
+ [[package]]
37
+ name = "heck"
38
+ version = "0.5.0"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
41
+
42
+ [[package]]
43
+ name = "indoc"
44
+ version = "2.0.7"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
47
+ dependencies = [
48
+ "rustversion",
49
+ ]
50
+
51
+ [[package]]
52
+ name = "libc"
53
+ version = "0.2.186"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
56
+
57
+ [[package]]
58
+ name = "matrixmultiply"
59
+ version = "0.3.10"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
62
+ dependencies = [
63
+ "autocfg",
64
+ "rawpointer",
65
+ ]
66
+
67
+ [[package]]
68
+ name = "memoffset"
69
+ version = "0.9.1"
70
+ source = "registry+https://github.com/rust-lang/crates.io-index"
71
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
72
+ dependencies = [
73
+ "autocfg",
74
+ ]
75
+
76
+ [[package]]
77
+ name = "ndarray"
78
+ version = "0.16.1"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
81
+ dependencies = [
82
+ "matrixmultiply",
83
+ "num-complex",
84
+ "num-integer",
85
+ "num-traits",
86
+ "portable-atomic",
87
+ "portable-atomic-util",
88
+ "rawpointer",
89
+ ]
90
+
91
+ [[package]]
92
+ name = "num-complex"
93
+ version = "0.4.6"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
96
+ dependencies = [
97
+ "num-traits",
98
+ ]
99
+
100
+ [[package]]
101
+ name = "num-integer"
102
+ version = "0.1.46"
103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
104
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
105
+ dependencies = [
106
+ "num-traits",
107
+ ]
108
+
109
+ [[package]]
110
+ name = "num-traits"
111
+ version = "0.2.19"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
114
+ dependencies = [
115
+ "autocfg",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "numpy"
120
+ version = "0.22.1"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e"
123
+ dependencies = [
124
+ "libc",
125
+ "ndarray",
126
+ "num-complex",
127
+ "num-integer",
128
+ "num-traits",
129
+ "pyo3",
130
+ "rustc-hash",
131
+ ]
132
+
133
+ [[package]]
134
+ name = "once_cell"
135
+ version = "1.21.4"
136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
137
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
138
+
139
+ [[package]]
140
+ name = "portable-atomic"
141
+ version = "1.13.1"
142
+ source = "registry+https://github.com/rust-lang/crates.io-index"
143
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
144
+
145
+ [[package]]
146
+ name = "portable-atomic-util"
147
+ version = "0.2.7"
148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
149
+ checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
150
+ dependencies = [
151
+ "portable-atomic",
152
+ ]
153
+
154
+ [[package]]
155
+ name = "proc-macro2"
156
+ version = "1.0.106"
157
+ source = "registry+https://github.com/rust-lang/crates.io-index"
158
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
159
+ dependencies = [
160
+ "unicode-ident",
161
+ ]
162
+
163
+ [[package]]
164
+ name = "pyo3"
165
+ version = "0.22.6"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
168
+ dependencies = [
169
+ "cfg-if",
170
+ "indoc",
171
+ "libc",
172
+ "memoffset",
173
+ "once_cell",
174
+ "portable-atomic",
175
+ "pyo3-build-config",
176
+ "pyo3-ffi",
177
+ "pyo3-macros",
178
+ "unindent",
179
+ ]
180
+
181
+ [[package]]
182
+ name = "pyo3-build-config"
183
+ version = "0.22.6"
184
+ source = "registry+https://github.com/rust-lang/crates.io-index"
185
+ checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
186
+ dependencies = [
187
+ "once_cell",
188
+ "target-lexicon",
189
+ ]
190
+
191
+ [[package]]
192
+ name = "pyo3-ffi"
193
+ version = "0.22.6"
194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
195
+ checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
196
+ dependencies = [
197
+ "libc",
198
+ "pyo3-build-config",
199
+ ]
200
+
201
+ [[package]]
202
+ name = "pyo3-macros"
203
+ version = "0.22.6"
204
+ source = "registry+https://github.com/rust-lang/crates.io-index"
205
+ checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
206
+ dependencies = [
207
+ "proc-macro2",
208
+ "pyo3-macros-backend",
209
+ "quote",
210
+ "syn",
211
+ ]
212
+
213
+ [[package]]
214
+ name = "pyo3-macros-backend"
215
+ version = "0.22.6"
216
+ source = "registry+https://github.com/rust-lang/crates.io-index"
217
+ checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
218
+ dependencies = [
219
+ "heck",
220
+ "proc-macro2",
221
+ "pyo3-build-config",
222
+ "quote",
223
+ "syn",
224
+ ]
225
+
226
+ [[package]]
227
+ name = "quote"
228
+ version = "1.0.45"
229
+ source = "registry+https://github.com/rust-lang/crates.io-index"
230
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
231
+ dependencies = [
232
+ "proc-macro2",
233
+ ]
234
+
235
+ [[package]]
236
+ name = "rawpointer"
237
+ version = "0.2.1"
238
+ source = "registry+https://github.com/rust-lang/crates.io-index"
239
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
240
+
241
+ [[package]]
242
+ name = "rustc-hash"
243
+ version = "1.1.0"
244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
245
+ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
246
+
247
+ [[package]]
248
+ name = "rustversion"
249
+ version = "1.0.22"
250
+ source = "registry+https://github.com/rust-lang/crates.io-index"
251
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
252
+
253
+ [[package]]
254
+ name = "syn"
255
+ version = "2.0.117"
256
+ source = "registry+https://github.com/rust-lang/crates.io-index"
257
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
258
+ dependencies = [
259
+ "proc-macro2",
260
+ "quote",
261
+ "unicode-ident",
262
+ ]
263
+
264
+ [[package]]
265
+ name = "target-lexicon"
266
+ version = "0.12.16"
267
+ source = "registry+https://github.com/rust-lang/crates.io-index"
268
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
269
+
270
+ [[package]]
271
+ name = "unicode-ident"
272
+ version = "1.0.24"
273
+ source = "registry+https://github.com/rust-lang/crates.io-index"
274
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
275
+
276
+ [[package]]
277
+ name = "unindent"
278
+ version = "0.2.4"
279
+ source = "registry+https://github.com/rust-lang/crates.io-index"
280
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "gridity"
3
+ version = "0.2.2"
4
+ edition = "2021"
5
+ readme = "README.md"
6
+
7
+ [lib]
8
+ name = "gridity"
9
+ crate-type = ["cdylib"]
10
+
11
+ [dependencies]
12
+ pyo3 = { version = "0.22", features = ["extension-module"] }
13
+ numpy = "0.22"
14
+ ndarray = "0.16"
15
+
16
+ [profile.release]
17
+ opt-level = 3
18
+ lto = true
19
+ codegen-units = 1
20
+
21
+ [dev-dependencies]
22
+ approx = "0.5"
23
+
24
+ [[bin]]
25
+ name = "grid_sim_demo"
26
+ path = "src/main.rs"
gridity-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: gridity
3
+ Version: 0.2.2
4
+ Summary: Rust + PyO3 simulation library
5
+ Requires-Python: >=3.8
@@ -0,0 +1,42 @@
1
+ # GRiD — Rigidity-Informed Deep Graph Embeddings
2
+
3
+ Dimension reduction via rigidity theory, with a learned GNN-based
4
+ independence scorer replacing classical Gaussian elimination.
5
+
6
+ ## Requirements
7
+ - Rust
8
+ - Python 3.13
9
+
10
+ ## Setup
11
+
12
+ ```bash
13
+ # 1. Create and activate a Python 3.13 virtual environment
14
+ python3.13 -m venv .venv
15
+ .venv\Scripts\activate # Windows
16
+ source .venv/bin/activate # macOS / Linux
17
+
18
+ # 2. Install build tool
19
+ pip install maturin
20
+
21
+ # 3. Compile the Rust simulation and install the Python bridge
22
+ maturin develop
23
+
24
+ # 4. Verify everything works
25
+ python python/test_bridge.py
26
+ ```
27
+
28
+ ## Running the standalone Rust demo (no Python needed)
29
+ ```bash
30
+ cargo run
31
+ cargo test
32
+ ```
33
+
34
+ ## If you are having problems compiling the code, it might be because of:
35
+ - Your current python version (pyo3 mismatch)
36
+
37
+ ### To solve it:
38
+ Just before compiling the code:
39
+ ```bash
40
+ $env:PYO3_PYTHON = "Your Python 3.13 path"
41
+ ```
42
+ And you should be fine
@@ -0,0 +1,193 @@
1
+ """
2
+ Pebble Game algorithm for 2D rigidity independence.
3
+ Deterministic & O(n^2)
4
+
5
+ (Jacobs & Hendrickson (1997)
6
+ "An Algorithm for Two-Dimensional Rigidity Percolation")
7
+ author: Sezer
8
+ """
9
+
10
+ from typing import List, Tuple
11
+
12
+ # Core pebble search
13
+ def _find_pebble(
14
+ v: int,
15
+ visited: set,
16
+ pebbles: List[int],
17
+ out_edges: List[List[int]],
18
+ changes: List[tuple],
19
+ ) -> bool:
20
+ """
21
+ DFS: try to find a free pebble reachable from v by following directed edges.
22
+ All state changes are recorded in 'changes' so they can be rolled back if the
23
+ overall edge turns out to be redundant.
24
+
25
+ A free pebble is reachable from v if:
26
+ - v itself has a pebble, OR
27
+ - v has an outgoing directed edge v->u and a pebble is reachable from u
28
+ (in which case we reverse the edge u->v to move the pebble toward v)
29
+ """
30
+ if v in visited:
31
+ return False
32
+ visited.add(v)
33
+
34
+ # Base case
35
+ if pebbles[v] > 0:
36
+ pebbles[v] -= 1
37
+ changes.append(('pebble', v))
38
+ return True
39
+
40
+ for i in range(len(out_edges[v])):
41
+ u = out_edges[v][i]
42
+ if _find_pebble(u, visited, pebbles, out_edges, changes):
43
+ out_edges[v].pop(i)
44
+ out_edges[u].append(v)
45
+ changes.append(('edge', v, u))
46
+ return True
47
+
48
+ return False
49
+
50
+ def _rollback(changes: List[tuple], pebbles: List[int], out_edges: List[List[int]]):
51
+ """
52
+ Undo all state changes recorded during a failed edge attempt
53
+ Applied in reverse order so each change is cleanly undone
54
+ """
55
+ for change in reversed(changes):
56
+ if change[0] == 'pebble':
57
+ pebbles[change[1]] += 1
58
+ elif change[0] == 'gain_pebble':
59
+ pebbles[change[1]] -= 1
60
+ elif change[0] == 'edge':
61
+ v, u = change[1], change[2]
62
+ out_edges[u].remove(v)
63
+ out_edges[v].append(u)
64
+
65
+ def pebble_game(
66
+ n: int,
67
+ candidate_edges: List[Tuple[int, int]],
68
+ d: int = 2,
69
+ ) -> Tuple[List[int], List[Tuple[int, int]]]:
70
+ """
71
+ Pebble Game algorithm for finding independent edges in a rigidity framework.
72
+
73
+ Only supports d=2
74
+ For d=3, falls back to gaussian elimination
75
+
76
+ Args:
77
+ n : number of nodes
78
+ candidate_edges : edges in upper-triangular order (i < j)
79
+ d : embedding dimension (must be 2)
80
+
81
+ Returns:
82
+ labels : list[int] 1=independent, 0=redundant
83
+ accepted : list[(i, j)] the independent edges only
84
+
85
+ Complexity: O(n^2)
86
+ """
87
+ assert d == 2
88
+
89
+ pebbles = [2] * n
90
+ # Directed graph: out_edges[v] = list of nodes v points to
91
+ # A directed edge v->u means "v has borrowed a pebble from u's region"
92
+ out_edges: List[List[int]] = [[] for _ in range(n)]
93
+
94
+ labels = []
95
+ accepted = []
96
+
97
+ for (u, v) in candidate_edges:
98
+ changes = []
99
+
100
+ while pebbles[u] + pebbles[v] < 4:
101
+
102
+ pu, pv = pebbles[u], pebbles[v]
103
+ pebbles[u], pebbles[v] = 0, 0
104
+
105
+ progressed = False
106
+
107
+ if pu < 2 and _find_pebble(u, set(), pebbles, out_edges, changes):
108
+ pu += 1
109
+ changes.append(('gain_pebble', u))
110
+ progressed = True
111
+ elif pv < 2 and _find_pebble(v, set(), pebbles, out_edges, changes):
112
+ pv += 1
113
+ changes.append(('gain_pebble', v))
114
+ progressed = True
115
+
116
+ pebbles[u], pebbles[v] = pu, pv
117
+
118
+ if not progressed:
119
+ break
120
+
121
+ if pebbles[u] + pebbles[v] >= 4:
122
+ # Independent: consume exactly 1 pebble to cover this edge
123
+ pebbles[v] -= 1
124
+ out_edges[v].append(u)
125
+ labels.append(1)
126
+ accepted.append((u, v))
127
+ else:
128
+ # Redundant: undo everything collected during this attempt
129
+ _rollback(changes, pebbles, out_edges)
130
+ labels.append(0)
131
+
132
+ return labels, accepted
133
+
134
+ def gaussian_elimination(
135
+ n: int,
136
+ candidate_edges: List[Tuple[int, int]],
137
+ d: int= 2,
138
+ ) -> Tuple[List[int], List[Tuple[int, int]]]:
139
+ """
140
+ Classical Gaussian elimination rank check for edge independence.
141
+ Works for both d=2 and d=3.
142
+
143
+ This mirrors exactly what the Rust simulation does internally,
144
+ implementing it just for comparison
145
+ """
146
+ ncols = n * d
147
+ pivot_rows: List[List[float]] = []
148
+ pivot_cols: List[int] = []
149
+ tol = 1e-9
150
+
151
+ labels = []
152
+ accepted = []
153
+
154
+ def add_row(row: List[float]) -> bool:
155
+ """Returns True if row is linearly independent of existing rows."""
156
+ r = row[:]
157
+
158
+ for k, col in enumerate(pivot_cols):
159
+ factor = r[col]
160
+ if abs(factor) > tol:
161
+ for j in range(ncols):
162
+ r[j] -= factor * pivot_rows[k][j]
163
+ # Find new pivot
164
+ new_pivot = next((j for j in range(ncols) if abs(r[j]) > tol), None)
165
+ if new_pivot is None:
166
+ return False # redundant
167
+ # Normalise
168
+ scale = r[new_pivot]
169
+ r = [v / scale for v in r]
170
+ # Back-substitute into existing rows
171
+ for k in range(len(pivot_rows)):
172
+ factor = pivot_rows[k][new_pivot]
173
+ if abs(factor) > tol:
174
+ for j in range(ncols):
175
+ pivot_rows[k][j] -= factor * r[j]
176
+ pivot_rows.append(r)
177
+ pivot_cols.append(new_pivot)
178
+ return True
179
+
180
+ for (i, j) in candidate_edges:
181
+ row = [0.0] * ncols
182
+ for dim in range(d):
183
+ row[d * i + dim] = 1.0
184
+ row[d * j + dim] = -1.0
185
+ # This version without coords only checks structural independence
186
+ # Use grid.simulate() for the geometry-aware version
187
+ independent = add_row(row)
188
+ labels.append(1 if independent else 0)
189
+ if independent:
190
+ accepted.append((i, j))
191
+
192
+ return labels, accepted
193
+
Binary file