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.
- gridity-0.2.2/.github/workflows/publish.yml +59 -0
- gridity-0.2.2/.gitignore +19 -0
- gridity-0.2.2/.idea/.gitignore +8 -0
- gridity-0.2.2/.idea/GRiD.iml +8 -0
- gridity-0.2.2/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- gridity-0.2.2/.idea/misc.xml +7 -0
- gridity-0.2.2/.idea/modules.xml +8 -0
- gridity-0.2.2/.idea/vcs.xml +6 -0
- gridity-0.2.2/Cargo.lock +280 -0
- gridity-0.2.2/Cargo.toml +26 -0
- gridity-0.2.2/PKG-INFO +5 -0
- gridity-0.2.2/README.md +42 -0
- gridity-0.2.2/gnn/algorithms.py +193 -0
- gridity-0.2.2/gnn/best_model.pt +0 -0
- gridity-0.2.2/gnn/compare.py +221 -0
- gridity-0.2.2/gnn/dataset.py +90 -0
- gridity-0.2.2/gnn/model.py +117 -0
- gridity-0.2.2/gnn/train.py +100 -0
- gridity-0.2.2/pyproject.toml +12 -0
- gridity-0.2.2/python/test_bridge.py +158 -0
- gridity-0.2.2/src/lib.rs +436 -0
- gridity-0.2.2/src/main.rs +269 -0
- gridity-0.2.2/src/node.rs +123 -0
- gridity-0.2.2/src/pebble.rs +167 -0
- gridity-0.2.2/src/rank.rs +191 -0
- gridity-0.2.2/src/reconstruct.rs +230 -0
- gridity-0.2.2/src/rigidity.rs +101 -0
- gridity-0.2.2/src/simulation.rs +229 -0
- gridity-0.2.2/src/traversal.rs +60 -0
|
@@ -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
|
gridity-0.2.2/.gitignore
ADDED
|
@@ -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,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>
|
gridity-0.2.2/Cargo.lock
ADDED
|
@@ -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"
|
gridity-0.2.2/Cargo.toml
ADDED
|
@@ -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
gridity-0.2.2/README.md
ADDED
|
@@ -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
|