@graphty/graph-samples 0.0.1 → 0.1.1
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.
- package/LICENSE +21 -0
- package/NOTICE +123 -0
- package/README.md +190 -1
- package/dist/chunks/build-DdCe60t3.js +47 -0
- package/dist/chunks/build-DdCe60t3.js.map +1 -0
- package/dist/chunks/meta-B6toBzaa.js +19 -0
- package/dist/chunks/meta-B6toBzaa.js.map +1 -0
- package/dist/chunks/meta-BM_YIhlA.js +28 -0
- package/dist/chunks/meta-BM_YIhlA.js.map +1 -0
- package/dist/chunks/meta-BMmtUpPx.js +30 -0
- package/dist/chunks/meta-BMmtUpPx.js.map +1 -0
- package/dist/chunks/meta-BXC7oi86.js +22 -0
- package/dist/chunks/meta-BXC7oi86.js.map +1 -0
- package/dist/chunks/meta-BaFPvhc1.js +19 -0
- package/dist/chunks/meta-BaFPvhc1.js.map +1 -0
- package/dist/chunks/meta-C58v1Oy0.js +28 -0
- package/dist/chunks/meta-C58v1Oy0.js.map +1 -0
- package/dist/chunks/meta-CQRXyexG.js +23 -0
- package/dist/chunks/meta-CQRXyexG.js.map +1 -0
- package/dist/chunks/meta-DrgM1eS0.js +23 -0
- package/dist/chunks/meta-DrgM1eS0.js.map +1 -0
- package/dist/chunks/meta-IhoaDXAV.js +19 -0
- package/dist/chunks/meta-IhoaDXAV.js.map +1 -0
- package/dist/chunks/meta-MQPbBUUB.js +29 -0
- package/dist/chunks/meta-MQPbBUUB.js.map +1 -0
- package/dist/chunks/meta-Qp4HNYAZ.js +24 -0
- package/dist/chunks/meta-Qp4HNYAZ.js.map +1 -0
- package/dist/chunks/meta-mns0uFIW.js +19 -0
- package/dist/chunks/meta-mns0uFIW.js.map +1 -0
- package/dist/datasets/celegans-neural.d.ts +1 -0
- package/dist/datasets/celegans-neural.js +7359 -0
- package/dist/datasets/celegans-neural.js.map +1 -0
- package/dist/datasets/contiguous-usa.d.ts +1 -0
- package/dist/datasets/contiguous-usa.js +501 -0
- package/dist/datasets/contiguous-usa.js.map +1 -0
- package/dist/datasets/davis-southern-women.d.ts +1 -0
- package/dist/datasets/davis-southern-women.js +235 -0
- package/dist/datasets/davis-southern-women.js.map +1 -0
- package/dist/datasets/dolphins.d.ts +1 -0
- package/dist/datasets/dolphins.js +406 -0
- package/dist/datasets/dolphins.js.map +1 -0
- package/dist/datasets/florentine-families.d.ts +1 -0
- package/dist/datasets/florentine-families.js +75 -0
- package/dist/datasets/florentine-families.js.map +1 -0
- package/dist/datasets/football.d.ts +1 -0
- package/dist/datasets/football.js +1508 -0
- package/dist/datasets/football.js.map +1 -0
- package/dist/datasets/karate.d.ts +1 -0
- package/dist/datasets/karate.js +295 -0
- package/dist/datasets/karate.js.map +1 -0
- package/dist/datasets/knuth-miles.d.ts +1 -0
- package/dist/datasets/knuth-miles.js +24933 -0
- package/dist/datasets/knuth-miles.js.map +1 -0
- package/dist/datasets/les-miserables.d.ts +1 -0
- package/dist/datasets/les-miserables.js +860 -0
- package/dist/datasets/les-miserables.js.map +1 -0
- package/dist/datasets/openflights.d.ts +1 -0
- package/dist/datasets/openflights.js +130277 -0
- package/dist/datasets/openflights.js.map +1 -0
- package/dist/datasets/political-blogs.d.ts +1 -0
- package/dist/datasets/political-blogs.js +42551 -0
- package/dist/datasets/political-blogs.js.map +1 -0
- package/dist/datasets/political-books.d.ts +1 -0
- package/dist/datasets/political-books.js +1124 -0
- package/dist/datasets/political-books.js.map +1 -0
- package/dist/generators.d.ts +1 -0
- package/dist/generators.js +4496 -0
- package/dist/generators.js.map +1 -0
- package/dist/graph-samples.d.ts +1 -0
- package/dist/graph-samples.js +142 -0
- package/dist/graph-samples.js.map +1 -0
- package/dist/src/datasets/build.d.ts +89 -0
- package/dist/src/datasets/build.d.ts.map +1 -0
- package/dist/src/datasets/build.js +53 -0
- package/dist/src/datasets/build.js.map +1 -0
- package/dist/src/datasets/catalog.d.ts +8 -0
- package/dist/src/datasets/catalog.d.ts.map +1 -0
- package/dist/src/datasets/catalog.js +34 -0
- package/dist/src/datasets/catalog.js.map +1 -0
- package/dist/src/datasets/celegans-neural/data.d.ts +3 -0
- package/dist/src/datasets/celegans-neural/data.d.ts.map +1 -0
- package/dist/src/datasets/celegans-neural/data.js +573 -0
- package/dist/src/datasets/celegans-neural/data.js.map +1 -0
- package/dist/src/datasets/celegans-neural/index.d.ts +11 -0
- package/dist/src/datasets/celegans-neural/index.d.ts.map +1 -0
- package/dist/src/datasets/celegans-neural/index.js +14 -0
- package/dist/src/datasets/celegans-neural/index.js.map +1 -0
- package/dist/src/datasets/celegans-neural/meta.d.ts +4 -0
- package/dist/src/datasets/celegans-neural/meta.d.ts.map +1 -0
- package/dist/src/datasets/celegans-neural/meta.js +21 -0
- package/dist/src/datasets/celegans-neural/meta.js.map +1 -0
- package/dist/src/datasets/contiguous-usa/data.d.ts +3 -0
- package/dist/src/datasets/contiguous-usa/data.d.ts.map +1 -0
- package/dist/src/datasets/contiguous-usa/data.js +162 -0
- package/dist/src/datasets/contiguous-usa/data.js.map +1 -0
- package/dist/src/datasets/contiguous-usa/index.d.ts +11 -0
- package/dist/src/datasets/contiguous-usa/index.d.ts.map +1 -0
- package/dist/src/datasets/contiguous-usa/index.js +14 -0
- package/dist/src/datasets/contiguous-usa/index.js.map +1 -0
- package/dist/src/datasets/contiguous-usa/meta.d.ts +4 -0
- package/dist/src/datasets/contiguous-usa/meta.d.ts.map +1 -0
- package/dist/src/datasets/contiguous-usa/meta.js +27 -0
- package/dist/src/datasets/contiguous-usa/meta.js.map +1 -0
- package/dist/src/datasets/davis-southern-women/data.d.ts +3 -0
- package/dist/src/datasets/davis-southern-women/data.d.ts.map +1 -0
- package/dist/src/datasets/davis-southern-women/data.js +57 -0
- package/dist/src/datasets/davis-southern-women/data.js.map +1 -0
- package/dist/src/datasets/davis-southern-women/index.d.ts +11 -0
- package/dist/src/datasets/davis-southern-women/index.d.ts.map +1 -0
- package/dist/src/datasets/davis-southern-women/index.js +14 -0
- package/dist/src/datasets/davis-southern-women/index.js.map +1 -0
- package/dist/src/datasets/davis-southern-women/meta.d.ts +4 -0
- package/dist/src/datasets/davis-southern-women/meta.d.ts.map +1 -0
- package/dist/src/datasets/davis-southern-women/meta.js +17 -0
- package/dist/src/datasets/davis-southern-women/meta.js.map +1 -0
- package/dist/src/datasets/dolphins/data.d.ts +3 -0
- package/dist/src/datasets/dolphins/data.d.ts.map +1 -0
- package/dist/src/datasets/dolphins/data.js +93 -0
- package/dist/src/datasets/dolphins/data.js.map +1 -0
- package/dist/src/datasets/dolphins/index.d.ts +11 -0
- package/dist/src/datasets/dolphins/index.d.ts.map +1 -0
- package/dist/src/datasets/dolphins/index.js +14 -0
- package/dist/src/datasets/dolphins/index.js.map +1 -0
- package/dist/src/datasets/dolphins/meta.d.ts +4 -0
- package/dist/src/datasets/dolphins/meta.d.ts.map +1 -0
- package/dist/src/datasets/dolphins/meta.js +17 -0
- package/dist/src/datasets/dolphins/meta.js.map +1 -0
- package/dist/src/datasets/florentine-families/data.d.ts +3 -0
- package/dist/src/datasets/florentine-families/data.d.ts.map +1 -0
- package/dist/src/datasets/florentine-families/data.js +31 -0
- package/dist/src/datasets/florentine-families/data.js.map +1 -0
- package/dist/src/datasets/florentine-families/index.d.ts +11 -0
- package/dist/src/datasets/florentine-families/index.d.ts.map +1 -0
- package/dist/src/datasets/florentine-families/index.js +14 -0
- package/dist/src/datasets/florentine-families/index.js.map +1 -0
- package/dist/src/datasets/florentine-families/meta.d.ts +4 -0
- package/dist/src/datasets/florentine-families/meta.d.ts.map +1 -0
- package/dist/src/datasets/florentine-families/meta.js +17 -0
- package/dist/src/datasets/florentine-families/meta.js.map +1 -0
- package/dist/src/datasets/football/data.d.ts +3 -0
- package/dist/src/datasets/football/data.d.ts.map +1 -0
- package/dist/src/datasets/football/data.js +210 -0
- package/dist/src/datasets/football/data.js.map +1 -0
- package/dist/src/datasets/football/index.d.ts +11 -0
- package/dist/src/datasets/football/index.d.ts.map +1 -0
- package/dist/src/datasets/football/index.js +14 -0
- package/dist/src/datasets/football/index.js.map +1 -0
- package/dist/src/datasets/football/meta.d.ts +4 -0
- package/dist/src/datasets/football/meta.d.ts.map +1 -0
- package/dist/src/datasets/football/meta.js +17 -0
- package/dist/src/datasets/football/meta.js.map +1 -0
- package/dist/src/datasets/hosted.d.ts +7 -0
- package/dist/src/datasets/hosted.d.ts.map +1 -0
- package/dist/src/datasets/hosted.js +80 -0
- package/dist/src/datasets/hosted.js.map +1 -0
- package/dist/src/datasets/karate/data.d.ts +3 -0
- package/dist/src/datasets/karate/data.d.ts.map +1 -0
- package/dist/src/datasets/karate/data.js +30 -0
- package/dist/src/datasets/karate/data.js.map +1 -0
- package/dist/src/datasets/karate/index.d.ts +11 -0
- package/dist/src/datasets/karate/index.d.ts.map +1 -0
- package/dist/src/datasets/karate/index.js +14 -0
- package/dist/src/datasets/karate/index.js.map +1 -0
- package/dist/src/datasets/karate/meta.d.ts +4 -0
- package/dist/src/datasets/karate/meta.d.ts.map +1 -0
- package/dist/src/datasets/karate/meta.js +22 -0
- package/dist/src/datasets/karate/meta.js.map +1 -0
- package/dist/src/datasets/knuth-miles/data.d.ts +3 -0
- package/dist/src/datasets/knuth-miles/data.d.ts.map +1 -0
- package/dist/src/datasets/knuth-miles/data.js +1203 -0
- package/dist/src/datasets/knuth-miles/data.js.map +1 -0
- package/dist/src/datasets/knuth-miles/index.d.ts +11 -0
- package/dist/src/datasets/knuth-miles/index.d.ts.map +1 -0
- package/dist/src/datasets/knuth-miles/index.js +14 -0
- package/dist/src/datasets/knuth-miles/index.js.map +1 -0
- package/dist/src/datasets/knuth-miles/meta.d.ts +4 -0
- package/dist/src/datasets/knuth-miles/meta.d.ts.map +1 -0
- package/dist/src/datasets/knuth-miles/meta.js +26 -0
- package/dist/src/datasets/knuth-miles/meta.js.map +1 -0
- package/dist/src/datasets/les-miserables/data.d.ts +3 -0
- package/dist/src/datasets/les-miserables/data.d.ts.map +1 -0
- package/dist/src/datasets/les-miserables/data.js +117 -0
- package/dist/src/datasets/les-miserables/data.js.map +1 -0
- package/dist/src/datasets/les-miserables/index.d.ts +11 -0
- package/dist/src/datasets/les-miserables/index.d.ts.map +1 -0
- package/dist/src/datasets/les-miserables/index.js +14 -0
- package/dist/src/datasets/les-miserables/index.js.map +1 -0
- package/dist/src/datasets/les-miserables/meta.d.ts +4 -0
- package/dist/src/datasets/les-miserables/meta.d.ts.map +1 -0
- package/dist/src/datasets/les-miserables/meta.js +21 -0
- package/dist/src/datasets/les-miserables/meta.js.map +1 -0
- package/dist/src/datasets/openflights/data.d.ts +3 -0
- package/dist/src/datasets/openflights/data.d.ts.map +1 -0
- package/dist/src/datasets/openflights/data.js +15457 -0
- package/dist/src/datasets/openflights/data.js.map +1 -0
- package/dist/src/datasets/openflights/index.d.ts +11 -0
- package/dist/src/datasets/openflights/index.d.ts.map +1 -0
- package/dist/src/datasets/openflights/index.js +14 -0
- package/dist/src/datasets/openflights/index.js.map +1 -0
- package/dist/src/datasets/openflights/meta.d.ts +4 -0
- package/dist/src/datasets/openflights/meta.d.ts.map +1 -0
- package/dist/src/datasets/openflights/meta.js +28 -0
- package/dist/src/datasets/openflights/meta.js.map +1 -0
- package/dist/src/datasets/political-blogs/data.d.ts +3 -0
- package/dist/src/datasets/political-blogs/data.d.ts.map +1 -0
- package/dist/src/datasets/political-blogs/data.js +4857 -0
- package/dist/src/datasets/political-blogs/data.js.map +1 -0
- package/dist/src/datasets/political-blogs/index.d.ts +11 -0
- package/dist/src/datasets/political-blogs/index.d.ts.map +1 -0
- package/dist/src/datasets/political-blogs/index.js +14 -0
- package/dist/src/datasets/political-blogs/index.js.map +1 -0
- package/dist/src/datasets/political-blogs/meta.d.ts +4 -0
- package/dist/src/datasets/political-blogs/meta.d.ts.map +1 -0
- package/dist/src/datasets/political-blogs/meta.js +26 -0
- package/dist/src/datasets/political-blogs/meta.js.map +1 -0
- package/dist/src/datasets/political-books/data.d.ts +3 -0
- package/dist/src/datasets/political-books/data.d.ts.map +1 -0
- package/dist/src/datasets/political-books/data.js +165 -0
- package/dist/src/datasets/political-books/data.js.map +1 -0
- package/dist/src/datasets/political-books/index.d.ts +11 -0
- package/dist/src/datasets/political-books/index.d.ts.map +1 -0
- package/dist/src/datasets/political-books/index.js +14 -0
- package/dist/src/datasets/political-books/index.js.map +1 -0
- package/dist/src/datasets/political-books/meta.d.ts +4 -0
- package/dist/src/datasets/political-books/meta.d.ts.map +1 -0
- package/dist/src/datasets/political-books/meta.js +20 -0
- package/dist/src/datasets/political-books/meta.js.map +1 -0
- package/dist/src/datasets/remote.d.ts +27 -0
- package/dist/src/datasets/remote.d.ts.map +1 -0
- package/dist/src/datasets/remote.js +30 -0
- package/dist/src/datasets/remote.js.map +1 -0
- package/dist/src/element.d.ts +20 -0
- package/dist/src/element.d.ts.map +1 -0
- package/dist/src/element.js +27 -0
- package/dist/src/element.js.map +1 -0
- package/dist/src/generators/bipartite.d.ts +51 -0
- package/dist/src/generators/bipartite.d.ts.map +1 -0
- package/dist/src/generators/bipartite.js +99 -0
- package/dist/src/generators/bipartite.js.map +1 -0
- package/dist/src/generators/block-model.d.ts +75 -0
- package/dist/src/generators/block-model.d.ts.map +1 -0
- package/dist/src/generators/block-model.js +116 -0
- package/dist/src/generators/block-model.js.map +1 -0
- package/dist/src/generators/classic.d.ts +208 -0
- package/dist/src/generators/classic.d.ts.map +1 -0
- package/dist/src/generators/classic.js +437 -0
- package/dist/src/generators/classic.js.map +1 -0
- package/dist/src/generators/degree-sequence.d.ts +273 -0
- package/dist/src/generators/degree-sequence.d.ts.map +1 -0
- package/dist/src/generators/degree-sequence.js +646 -0
- package/dist/src/generators/degree-sequence.js.map +1 -0
- package/dist/src/generators/directed.d.ts +141 -0
- package/dist/src/generators/directed.d.ts.map +1 -0
- package/dist/src/generators/directed.js +329 -0
- package/dist/src/generators/directed.js.map +1 -0
- package/dist/src/generators/erdos-renyi.d.ts +58 -0
- package/dist/src/generators/erdos-renyi.d.ts.map +1 -0
- package/dist/src/generators/erdos-renyi.js +154 -0
- package/dist/src/generators/erdos-renyi.js.map +1 -0
- package/dist/src/generators/flow.d.ts +153 -0
- package/dist/src/generators/flow.d.ts.map +1 -0
- package/dist/src/generators/flow.js +346 -0
- package/dist/src/generators/flow.js.map +1 -0
- package/dist/src/generators/geometric.d.ts +133 -0
- package/dist/src/generators/geometric.d.ts.map +1 -0
- package/dist/src/generators/geometric.js +495 -0
- package/dist/src/generators/geometric.js.map +1 -0
- package/dist/src/generators/growth.d.ts +187 -0
- package/dist/src/generators/growth.d.ts.map +1 -0
- package/dist/src/generators/growth.js +445 -0
- package/dist/src/generators/growth.js.map +1 -0
- package/dist/src/generators/hyperbolic.d.ts +84 -0
- package/dist/src/generators/hyperbolic.d.ts.map +1 -0
- package/dist/src/generators/hyperbolic.js +153 -0
- package/dist/src/generators/hyperbolic.js.map +1 -0
- package/dist/src/generators/index.d.ts +25 -0
- package/dist/src/generators/index.d.ts.map +1 -0
- package/dist/src/generators/index.js +24 -0
- package/dist/src/generators/index.js.map +1 -0
- package/dist/src/generators/lattices.d.ts +87 -0
- package/dist/src/generators/lattices.d.ts.map +1 -0
- package/dist/src/generators/lattices.js +237 -0
- package/dist/src/generators/lattices.js.map +1 -0
- package/dist/src/generators/lfr.d.ts +55 -0
- package/dist/src/generators/lfr.d.ts.map +1 -0
- package/dist/src/generators/lfr.js +178 -0
- package/dist/src/generators/lfr.js.map +1 -0
- package/dist/src/generators/named.d.ts +23 -0
- package/dist/src/generators/named.d.ts.map +1 -0
- package/dist/src/generators/named.js +191 -0
- package/dist/src/generators/named.js.map +1 -0
- package/dist/src/generators/preferential-attachment.d.ts +66 -0
- package/dist/src/generators/preferential-attachment.d.ts.map +1 -0
- package/dist/src/generators/preferential-attachment.js +157 -0
- package/dist/src/generators/preferential-attachment.js.map +1 -0
- package/dist/src/generators/trees-and-dags.d.ts +53 -0
- package/dist/src/generators/trees-and-dags.d.ts.map +1 -0
- package/dist/src/generators/trees-and-dags.js +108 -0
- package/dist/src/generators/trees-and-dags.js.map +1 -0
- package/dist/src/generators/util.d.ts +84 -0
- package/dist/src/generators/util.d.ts.map +1 -0
- package/dist/src/generators/util.js +138 -0
- package/dist/src/generators/util.js.map +1 -0
- package/dist/src/generators/variants.d.ts +85 -0
- package/dist/src/generators/variants.d.ts.map +1 -0
- package/dist/src/generators/variants.js +215 -0
- package/dist/src/generators/variants.js.map +1 -0
- package/dist/src/generators/weights.d.ts +65 -0
- package/dist/src/generators/weights.d.ts.map +1 -0
- package/dist/src/generators/weights.js +135 -0
- package/dist/src/generators/weights.js.map +1 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/random/exp.d.ts +23 -0
- package/dist/src/random/exp.d.ts.map +1 -0
- package/dist/src/random/exp.js +101 -0
- package/dist/src/random/exp.js.map +1 -0
- package/dist/src/random/log.d.ts +19 -0
- package/dist/src/random/log.d.ts.map +1 -0
- package/dist/src/random/log.js +83 -0
- package/dist/src/random/log.js.map +1 -0
- package/dist/src/random/stream.d.ts +93 -0
- package/dist/src/random/stream.d.ts.map +1 -0
- package/dist/src/random/stream.js +154 -0
- package/dist/src/random/stream.js.map +1 -0
- package/dist/src/random/threefry.d.ts +19 -0
- package/dist/src/random/threefry.d.ts.map +1 -0
- package/dist/src/random/threefry.js +43 -0
- package/dist/src/random/threefry.js.map +1 -0
- package/dist/src/random/trig.d.ts +16 -0
- package/dist/src/random/trig.d.ts.map +1 -0
- package/dist/src/random/trig.js +78 -0
- package/dist/src/random/trig.js.map +1 -0
- package/dist/src/types.d.ts +28 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +102 -5
- package/src/datasets/build.ts +140 -0
- package/src/datasets/catalog.ts +35 -0
- package/src/datasets/celegans-neural/data.ts +575 -0
- package/src/datasets/celegans-neural/index.ts +17 -0
- package/src/datasets/celegans-neural/meta.ts +25 -0
- package/src/datasets/contiguous-usa/data.ts +164 -0
- package/src/datasets/contiguous-usa/index.ts +17 -0
- package/src/datasets/contiguous-usa/meta.ts +31 -0
- package/src/datasets/davis-southern-women/data.ts +59 -0
- package/src/datasets/davis-southern-women/index.ts +17 -0
- package/src/datasets/davis-southern-women/meta.ts +20 -0
- package/src/datasets/dolphins/data.ts +95 -0
- package/src/datasets/dolphins/index.ts +17 -0
- package/src/datasets/dolphins/meta.ts +21 -0
- package/src/datasets/florentine-families/data.ts +33 -0
- package/src/datasets/florentine-families/index.ts +17 -0
- package/src/datasets/florentine-families/meta.ts +20 -0
- package/src/datasets/football/data.ts +212 -0
- package/src/datasets/football/index.ts +17 -0
- package/src/datasets/football/meta.ts +20 -0
- package/src/datasets/hosted.ts +90 -0
- package/src/datasets/karate/data.ts +32 -0
- package/src/datasets/karate/index.ts +17 -0
- package/src/datasets/karate/meta.ts +25 -0
- package/src/datasets/knuth-miles/data.ts +1205 -0
- package/src/datasets/knuth-miles/index.ts +17 -0
- package/src/datasets/knuth-miles/meta.ts +29 -0
- package/src/datasets/les-miserables/data.ts +119 -0
- package/src/datasets/les-miserables/index.ts +17 -0
- package/src/datasets/les-miserables/meta.ts +24 -0
- package/src/datasets/openflights/data.ts +15459 -0
- package/src/datasets/openflights/index.ts +17 -0
- package/src/datasets/openflights/meta.ts +31 -0
- package/src/datasets/political-blogs/data.ts +4859 -0
- package/src/datasets/political-blogs/index.ts +17 -0
- package/src/datasets/political-blogs/meta.ts +30 -0
- package/src/datasets/political-books/data.ts +167 -0
- package/src/datasets/political-books/index.ts +17 -0
- package/src/datasets/political-books/meta.ts +23 -0
- package/src/datasets/remote.ts +42 -0
- package/src/element.ts +41 -0
- package/src/generators/bipartite.ts +127 -0
- package/src/generators/block-model.ts +160 -0
- package/src/generators/classic.ts +461 -0
- package/src/generators/degree-sequence.ts +807 -0
- package/src/generators/directed.ts +418 -0
- package/src/generators/erdos-renyi.ts +188 -0
- package/src/generators/flow.ts +443 -0
- package/src/generators/geometric.ts +591 -0
- package/src/generators/growth.ts +532 -0
- package/src/generators/hyperbolic.ts +193 -0
- package/src/generators/index.ts +133 -0
- package/src/generators/lattices.ts +290 -0
- package/src/generators/lfr.ts +227 -0
- package/src/generators/named.ts +198 -0
- package/src/generators/preferential-attachment.ts +188 -0
- package/src/generators/trees-and-dags.ts +139 -0
- package/src/generators/util.ts +167 -0
- package/src/generators/variants.ts +253 -0
- package/src/generators/weights.ts +177 -0
- package/src/index.ts +12 -0
- package/src/random/exp.ts +104 -0
- package/src/random/log.ts +86 -0
- package/src/random/stream.ts +169 -0
- package/src/random/threefry.ts +45 -0
- package/src/random/trig.ts +83 -0
- package/src/types.ts +28 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Degree-sequence models: power-law degree sequences, the configuration model (undirected,
|
|
3
|
+
* directed and bipartite), Chung-Lu expected-degree graphs, the degree-corrected stochastic block
|
|
4
|
+
* model and random regular graphs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MAX_COUNT, type U32 } from "@graphty/graph-format";
|
|
8
|
+
|
|
9
|
+
import { detPow } from "../random/exp.js";
|
|
10
|
+
import { detLog } from "../random/log.js";
|
|
11
|
+
import { RandomStream, resolveSeed } from "../random/stream.js";
|
|
12
|
+
import { type SampleGraph } from "../types.js";
|
|
13
|
+
import { checkEdgeCount, checkInt, checkProbability, EdgeBuffer, toGraph } from "./util.js";
|
|
14
|
+
import { applyWeights, type WeightOptions } from "./weights.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What a generator does with self-loops or repeated pairs: keep them, or erase all but the first
|
|
18
|
+
* occurrence in edge order.
|
|
19
|
+
*/
|
|
20
|
+
export type EdgePolicy = "keep" | "erase";
|
|
21
|
+
|
|
22
|
+
/** The largest power-law support (maxDegree - minDegree + 1): the size of the cumulative table. */
|
|
23
|
+
const MAX_POWER_LAW_SUPPORT = 2 ** 24;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Throw unless `exponent` is a finite number >= 0.
|
|
27
|
+
* @param name - the option name
|
|
28
|
+
* @param exponent - the value
|
|
29
|
+
*/
|
|
30
|
+
function checkExponent(name: string, exponent: number): void {
|
|
31
|
+
if (!(Number.isFinite(exponent) && exponent >= 0)) {
|
|
32
|
+
throw new RangeError(`${name} must be a finite number >= 0, got ${String(exponent)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A sampler of the discrete power law P(d) ~ d^-exponent on [min, max]: a cumulative table of
|
|
38
|
+
* detPow(d, -exponent), and per draw one nextFloat u and a binary search for the first entry
|
|
39
|
+
* above u times the total. Checks its arguments.
|
|
40
|
+
* @param names - the option names of [exponent, min, max], for the messages
|
|
41
|
+
* @param exponent - the exponent, >= 0
|
|
42
|
+
* @param min - the smallest value, >= 1
|
|
43
|
+
* @param max - the largest value, >= min
|
|
44
|
+
* @returns the sampler
|
|
45
|
+
*/
|
|
46
|
+
export function powerLawSampler(
|
|
47
|
+
names: readonly [string, string, string],
|
|
48
|
+
exponent: number,
|
|
49
|
+
min: number,
|
|
50
|
+
max: number,
|
|
51
|
+
): (stream: RandomStream) => number {
|
|
52
|
+
checkExponent(names[0], exponent);
|
|
53
|
+
checkInt(names[1], min, 1);
|
|
54
|
+
checkInt(names[2], max, min, Math.min(MAX_COUNT, min + MAX_POWER_LAW_SUPPORT - 1));
|
|
55
|
+
const count = max - min + 1;
|
|
56
|
+
const cdf = new Float64Array(count);
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (let k = 0; k < count; k++) {
|
|
59
|
+
total += detPow(min + k, -exponent);
|
|
60
|
+
cdf[k] = total;
|
|
61
|
+
}
|
|
62
|
+
return (stream) => {
|
|
63
|
+
const u = stream.nextFloat() * total;
|
|
64
|
+
let lo = 0;
|
|
65
|
+
let hi = count - 1;
|
|
66
|
+
while (lo < hi) {
|
|
67
|
+
const mid = (lo + hi) >>> 1;
|
|
68
|
+
if (cdf[mid] > u) {
|
|
69
|
+
hi = mid;
|
|
70
|
+
} else {
|
|
71
|
+
lo = mid + 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return min + lo;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Make the sum of `seq` even by changing its LAST entry: +1 when it is below `max`, else -1 when
|
|
80
|
+
* it is above `min`; a RangeError when neither is possible.
|
|
81
|
+
* @param seq - the sequence, changed in place
|
|
82
|
+
* @param min - the smallest allowed value
|
|
83
|
+
* @param max - the largest allowed value
|
|
84
|
+
*/
|
|
85
|
+
export function makeSumEven(seq: U32, min: number, max: number): void {
|
|
86
|
+
let total = 0;
|
|
87
|
+
for (const d of seq) {
|
|
88
|
+
total += d;
|
|
89
|
+
}
|
|
90
|
+
if (total % 2 === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const last = seq.length - 1;
|
|
94
|
+
if (seq[last] < max) {
|
|
95
|
+
seq[last]++;
|
|
96
|
+
} else if (seq[last] > min) {
|
|
97
|
+
seq[last]--;
|
|
98
|
+
} else {
|
|
99
|
+
throw new RangeError(`cannot make the degree sum even: every degree is ${min} and n is odd`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Options of {@link powerLawDegreeSequence}. */
|
|
104
|
+
export interface PowerLawDegreeSequenceOptions {
|
|
105
|
+
/** The sequence length, >= 0. */
|
|
106
|
+
n: number;
|
|
107
|
+
/** The exponent of P(d) ~ d^-exponent, a finite number >= 0 (typically 2 to 3). */
|
|
108
|
+
exponent: number;
|
|
109
|
+
/** The smallest degree, >= 1. */
|
|
110
|
+
minDegree: number;
|
|
111
|
+
/** The largest degree, >= minDegree, and at most minDegree + 2^24 - 1. */
|
|
112
|
+
maxDegree: number;
|
|
113
|
+
/** Make the sum even (so a configuration model can pair it); default true. */
|
|
114
|
+
evenSum?: boolean | undefined;
|
|
115
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
116
|
+
seed?: number | undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A sequence of n integers drawn independently from the discrete power law
|
|
121
|
+
* P(d) ~ d^-exponent on [minDegree, maxDegree], by inversion of a cumulative table built with
|
|
122
|
+
* detPow: entry i is one nextFloat of stream (seed, "power-law", 0), in index order. With
|
|
123
|
+
* `evenSum` (the default) an odd sum is fixed by changing the last entry: +1 when it is below
|
|
124
|
+
* maxDegree, else -1. O(n log(maxDegree - minDegree) + maxDegree - minDegree).
|
|
125
|
+
* @param options - n, exponent, minDegree, maxDegree, evenSum and seed
|
|
126
|
+
* @returns the sequence
|
|
127
|
+
*/
|
|
128
|
+
export function powerLawDegreeSequence(options: PowerLawDegreeSequenceOptions): Uint32Array<ArrayBuffer> {
|
|
129
|
+
const { n, exponent, minDegree, maxDegree } = options;
|
|
130
|
+
const seed = resolveSeed(options.seed);
|
|
131
|
+
checkInt("n", n, 0);
|
|
132
|
+
const sample = powerLawSampler(["exponent", "minDegree", "maxDegree"], exponent, minDegree, maxDegree);
|
|
133
|
+
const stream = new RandomStream(seed, "power-law", 0);
|
|
134
|
+
const seq = new Uint32Array(n);
|
|
135
|
+
for (let i = 0; i < n; i++) {
|
|
136
|
+
seq[i] = sample(stream);
|
|
137
|
+
}
|
|
138
|
+
if ((options.evenSum ?? true) && n > 0) {
|
|
139
|
+
makeSumEven(seq, minDegree, maxDegree);
|
|
140
|
+
}
|
|
141
|
+
return seq;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check a degree sequence and return its sum.
|
|
146
|
+
* @param name - the option name
|
|
147
|
+
* @param degrees - the degrees
|
|
148
|
+
* @returns the sum
|
|
149
|
+
*/
|
|
150
|
+
function degreeSum(name: string, degrees: ArrayLike<number>): number {
|
|
151
|
+
checkInt(`${name}.length`, degrees.length, 0);
|
|
152
|
+
let total = 0;
|
|
153
|
+
for (let i = 0; i < degrees.length; i++) {
|
|
154
|
+
const d = degrees[i];
|
|
155
|
+
if (!(Number.isInteger(d) && d >= 0 && d <= MAX_COUNT)) {
|
|
156
|
+
checkInt(`${name}[${i}]`, d, 0);
|
|
157
|
+
}
|
|
158
|
+
total += d;
|
|
159
|
+
}
|
|
160
|
+
checkEdgeCount(total / 2);
|
|
161
|
+
return total;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The option value of a keep / erase policy, checked.
|
|
166
|
+
* @param name - the option name
|
|
167
|
+
* @param value - the value (default "erase")
|
|
168
|
+
* @returns true for keep
|
|
169
|
+
*/
|
|
170
|
+
function keeps(name: string, value: EdgePolicy | undefined): boolean {
|
|
171
|
+
if (value !== undefined && value !== "keep" && value !== "erase") {
|
|
172
|
+
throw new RangeError(`${name} must be "keep" or "erase", got ${String(value)}`);
|
|
173
|
+
}
|
|
174
|
+
return value === "keep";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The stub list: node i + offset repeated degrees[i] times, ascending.
|
|
179
|
+
* @param degrees - the degrees
|
|
180
|
+
* @param total - their sum
|
|
181
|
+
* @param offset - added to every node index
|
|
182
|
+
* @returns the stubs
|
|
183
|
+
*/
|
|
184
|
+
export function stubList(degrees: ArrayLike<number>, total: number, offset = 0): Uint32Array<ArrayBuffer> {
|
|
185
|
+
const stubs = new Uint32Array(total);
|
|
186
|
+
let k = 0;
|
|
187
|
+
for (let i = 0; i < degrees.length; i++) {
|
|
188
|
+
stubs.fill(i + offset, k, k + degrees[i]);
|
|
189
|
+
k += degrees[i];
|
|
190
|
+
}
|
|
191
|
+
return stubs;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Fisher-Yates shuffle in place: for i = length - 1 down to 1, swap entries i and nextBelow(i + 1).
|
|
196
|
+
* @param a - the array
|
|
197
|
+
* @param stream - the stream
|
|
198
|
+
*/
|
|
199
|
+
export function shuffle(a: U32, stream: RandomStream): void {
|
|
200
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
201
|
+
const j = stream.nextBelow(i + 1);
|
|
202
|
+
const t = a[i];
|
|
203
|
+
a[i] = a[j];
|
|
204
|
+
a[j] = t;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Filter an edge list, keeping edge order: drop self-loops unless `keepLoops`, drop edges whose
|
|
210
|
+
* endpoints share a `group` value when `group` is given, and drop every repeat of a pair (ordered
|
|
211
|
+
* when directed, unordered otherwise) after its first kept occurrence unless `keepMulti`. O(n + m)
|
|
212
|
+
* by a counting sort on the smaller endpoint and a last-seen mark per node, no hashing.
|
|
213
|
+
* @param n - the node count
|
|
214
|
+
* @param src - the sources
|
|
215
|
+
* @param dst - the targets
|
|
216
|
+
* @param directed - whether pairs are ordered
|
|
217
|
+
* @param keepLoops - keep self-loops
|
|
218
|
+
* @param keepMulti - keep repeated pairs
|
|
219
|
+
* @param out - receives the kept edges
|
|
220
|
+
* @param group - a node column; edges inside one group are dropped
|
|
221
|
+
*/
|
|
222
|
+
function eraseEdges(
|
|
223
|
+
n: number,
|
|
224
|
+
src: U32,
|
|
225
|
+
dst: U32,
|
|
226
|
+
directed: boolean,
|
|
227
|
+
keepLoops: boolean,
|
|
228
|
+
keepMulti: boolean,
|
|
229
|
+
out: EdgeBuffer,
|
|
230
|
+
group?: U32,
|
|
231
|
+
): void {
|
|
232
|
+
const m = src.length;
|
|
233
|
+
const keep = new Uint8Array(m).fill(1);
|
|
234
|
+
for (let e = 0; e < m; e++) {
|
|
235
|
+
const u = src[e];
|
|
236
|
+
const v = dst[e];
|
|
237
|
+
if ((!keepLoops && u === v) || (group !== undefined && group[u] === group[v])) {
|
|
238
|
+
keep[e] = 0;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (!keepMulti) {
|
|
242
|
+
const lo = (e: number): number => (directed ? src[e] : Math.min(src[e], dst[e]));
|
|
243
|
+
const start = new Uint32Array(n + 1);
|
|
244
|
+
for (let e = 0; e < m; e++) {
|
|
245
|
+
start[lo(e) + 1]++;
|
|
246
|
+
}
|
|
247
|
+
for (let i = 0; i < n; i++) {
|
|
248
|
+
start[i + 1] += start[i];
|
|
249
|
+
}
|
|
250
|
+
const fill = start.slice(0, n);
|
|
251
|
+
const order = new Uint32Array(m);
|
|
252
|
+
for (let e = 0; e < m; e++) {
|
|
253
|
+
order[fill[lo(e)]++] = e;
|
|
254
|
+
}
|
|
255
|
+
// mark[v] = u + 1 when the pair (u, v) has been seen in u's bucket
|
|
256
|
+
const mark = new Uint32Array(n);
|
|
257
|
+
for (let k = 0; k < m; k++) {
|
|
258
|
+
const e = order[k];
|
|
259
|
+
if (keep[e] === 0) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const u = lo(e);
|
|
263
|
+
const v = directed ? dst[e] : Math.max(src[e], dst[e]);
|
|
264
|
+
if (mark[v] === u + 1) {
|
|
265
|
+
keep[e] = 0;
|
|
266
|
+
} else {
|
|
267
|
+
mark[v] = u + 1;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
for (let e = 0; e < m; e++) {
|
|
272
|
+
if (keep[e] === 1) {
|
|
273
|
+
out.push(src[e], dst[e]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Pair consecutive stubs of a shuffled undirected stub list into edges (stubs[2k], stubs[2k + 1])
|
|
280
|
+
* and erase as asked.
|
|
281
|
+
* @param n - the node count
|
|
282
|
+
* @param stubs - the shuffled stubs (even length)
|
|
283
|
+
* @param keepLoops - keep self-loops
|
|
284
|
+
* @param keepMulti - keep repeated pairs
|
|
285
|
+
* @param out - receives the edges
|
|
286
|
+
* @param group - a node column; edges inside one group are dropped
|
|
287
|
+
*/
|
|
288
|
+
export function pairStubs(
|
|
289
|
+
n: number,
|
|
290
|
+
stubs: U32,
|
|
291
|
+
keepLoops: boolean,
|
|
292
|
+
keepMulti: boolean,
|
|
293
|
+
out: EdgeBuffer,
|
|
294
|
+
group?: U32,
|
|
295
|
+
): void {
|
|
296
|
+
const m = stubs.length / 2;
|
|
297
|
+
const src = new Uint32Array(m);
|
|
298
|
+
const dst = new Uint32Array(m);
|
|
299
|
+
for (let k = 0; k < m; k++) {
|
|
300
|
+
src[k] = stubs[2 * k];
|
|
301
|
+
dst[k] = stubs[2 * k + 1];
|
|
302
|
+
}
|
|
303
|
+
eraseEdges(n, src, dst, false, keepLoops, keepMulti, out, group);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Options of {@link configurationModelGraph}. */
|
|
307
|
+
export interface ConfigurationModelOptions extends WeightOptions {
|
|
308
|
+
/** The degree of every node; the sum must be even. */
|
|
309
|
+
degrees: ArrayLike<number>;
|
|
310
|
+
/** Keep or erase self-loops; default "erase". */
|
|
311
|
+
selfLoops?: EdgePolicy | undefined;
|
|
312
|
+
/** Keep or erase repeated pairs (the first occurrence in edge order stays); default "erase". */
|
|
313
|
+
multiEdges?: EdgePolicy | undefined;
|
|
314
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
315
|
+
seed?: number | undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The configuration model (E. A. Bender and E. R. Canfield, "The asymptotic number of labeled
|
|
320
|
+
* graphs with given degree sequences", J. Combin. Theory A 24, 296-307, 1978,
|
|
321
|
+
* doi:10.1016/0097-3165(78)90059-6; M. Molloy and B. Reed, "A critical point for random graphs with
|
|
322
|
+
* a given degree sequence", Random Struct. Algorithms 6, 161-179, 1995, doi:10.1002/rsa.3240060204;
|
|
323
|
+
* M. E. J. Newman, S. H. Strogatz and D. J. Watts, "Random graphs with arbitrary degree
|
|
324
|
+
* distributions and their applications", Phys. Rev. E 64, 026118, 2001,
|
|
325
|
+
* doi:10.1103/PhysRevE.64.026118). The stub list (node i repeated degrees[i] times, ascending) is
|
|
326
|
+
* Fisher-Yates shuffled with stream (seed, "configuration", 0) and consecutive stubs are paired:
|
|
327
|
+
* edge k is (stubs[2k], stubs[2k + 1]), in that order. With keep / keep the degrees are exact (a
|
|
328
|
+
* multigraph); erasing drops self-loops and every repeat of a pair after its first occurrence, so
|
|
329
|
+
* degrees can only go down. An odd degree sum is a RangeError. O(n + m).
|
|
330
|
+
* @param options - degrees, selfLoops, multiEdges and seed
|
|
331
|
+
* @returns the undirected graph
|
|
332
|
+
*/
|
|
333
|
+
export function configurationModelGraph(options: ConfigurationModelOptions): SampleGraph {
|
|
334
|
+
const { degrees } = options;
|
|
335
|
+
const seed = resolveSeed(options.seed);
|
|
336
|
+
const keepLoops = keeps("selfLoops", options.selfLoops);
|
|
337
|
+
const keepMulti = keeps("multiEdges", options.multiEdges);
|
|
338
|
+
const total = degreeSum("degrees", degrees);
|
|
339
|
+
if (total % 2 !== 0) {
|
|
340
|
+
throw new RangeError(`the degree sum must be even, got ${total}`);
|
|
341
|
+
}
|
|
342
|
+
const n = degrees.length;
|
|
343
|
+
const stubs = stubList(degrees, total);
|
|
344
|
+
shuffle(stubs, new RandomStream(seed, "configuration", 0));
|
|
345
|
+
const out = new EdgeBuffer(total / 2);
|
|
346
|
+
pairStubs(n, stubs, keepLoops, keepMulti, out);
|
|
347
|
+
return applyWeights(toGraph(n, out, false), options);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Options of {@link directedConfigurationModelGraph}. */
|
|
351
|
+
export interface DirectedConfigurationModelOptions extends WeightOptions {
|
|
352
|
+
/** The out-degree of every node. */
|
|
353
|
+
outDegrees: ArrayLike<number>;
|
|
354
|
+
/** The in-degree of every node; same length and same sum as outDegrees. */
|
|
355
|
+
inDegrees: ArrayLike<number>;
|
|
356
|
+
/** Keep or erase self-loops; default "erase". */
|
|
357
|
+
selfLoops?: EdgePolicy | undefined;
|
|
358
|
+
/** Keep or erase repeated ordered pairs (the first occurrence stays); default "erase". */
|
|
359
|
+
multiEdges?: EdgePolicy | undefined;
|
|
360
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
361
|
+
seed?: number | undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The directed configuration model (Newman, Strogatz and Watts 2001, section on directed graphs):
|
|
366
|
+
* the in-stub list (node i repeated inDegrees[i] times, ascending) is Fisher-Yates shuffled with
|
|
367
|
+
* stream (seed, "configuration-directed", 0) and paired in order with the ascending out-stub list,
|
|
368
|
+
* so edge k runs from the k-th out-stub to the k-th shuffled in-stub (edges come grouped by
|
|
369
|
+
* source, ascending). Erasing works as in {@link configurationModelGraph} on ordered pairs.
|
|
370
|
+
* O(n + m).
|
|
371
|
+
* @param options - outDegrees, inDegrees, selfLoops, multiEdges and seed
|
|
372
|
+
* @returns the directed graph
|
|
373
|
+
*/
|
|
374
|
+
export function directedConfigurationModelGraph(options: DirectedConfigurationModelOptions): SampleGraph {
|
|
375
|
+
const { outDegrees, inDegrees } = options;
|
|
376
|
+
const seed = resolveSeed(options.seed);
|
|
377
|
+
const keepLoops = keeps("selfLoops", options.selfLoops);
|
|
378
|
+
const keepMulti = keeps("multiEdges", options.multiEdges);
|
|
379
|
+
const outTotal = degreeSum("outDegrees", outDegrees);
|
|
380
|
+
const inTotal = degreeSum("inDegrees", inDegrees);
|
|
381
|
+
if (outDegrees.length !== inDegrees.length) {
|
|
382
|
+
throw new RangeError(`outDegrees and inDegrees must have the same length`);
|
|
383
|
+
}
|
|
384
|
+
if (outTotal !== inTotal) {
|
|
385
|
+
throw new RangeError(`the out-degree sum (${outTotal}) must equal the in-degree sum (${inTotal})`);
|
|
386
|
+
}
|
|
387
|
+
const n = outDegrees.length;
|
|
388
|
+
const dst = stubList(inDegrees, inTotal);
|
|
389
|
+
shuffle(dst, new RandomStream(seed, "configuration-directed", 0));
|
|
390
|
+
const out = new EdgeBuffer(outTotal);
|
|
391
|
+
eraseEdges(n, stubList(outDegrees, outTotal), dst, true, keepLoops, keepMulti, out);
|
|
392
|
+
return applyWeights(toGraph(n, out, true), options);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Options of {@link bipartiteConfigurationModelGraph}. */
|
|
396
|
+
export interface BipartiteConfigurationModelOptions extends WeightOptions {
|
|
397
|
+
/** The degree of every left node (nodes 0 .. L - 1). */
|
|
398
|
+
leftDegrees: ArrayLike<number>;
|
|
399
|
+
/** The degree of every right node (nodes L .. L + R - 1); same sum as leftDegrees. */
|
|
400
|
+
rightDegrees: ArrayLike<number>;
|
|
401
|
+
/** Keep or erase repeated pairs (the first occurrence stays); default "erase". */
|
|
402
|
+
multiEdges?: EdgePolicy | undefined;
|
|
403
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
404
|
+
seed?: number | undefined;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The bipartite configuration model (Newman, Strogatz and Watts 2001, section on bipartite
|
|
409
|
+
* graphs): left nodes are 0 .. L - 1, right nodes L .. L + R - 1. The right stub list (ascending)
|
|
410
|
+
* is Fisher-Yates shuffled with stream (seed, "bipartite-configuration", 0) and paired in order with
|
|
411
|
+
* the ascending left stub list; edge k is (k-th left stub, k-th shuffled right stub). Node column
|
|
412
|
+
* `side` (u8): 0 left, 1 right. O(n + m).
|
|
413
|
+
* @param options - leftDegrees, rightDegrees, multiEdges and seed
|
|
414
|
+
* @returns the undirected bipartite graph
|
|
415
|
+
*/
|
|
416
|
+
export function bipartiteConfigurationModelGraph(options: BipartiteConfigurationModelOptions): SampleGraph {
|
|
417
|
+
const { leftDegrees, rightDegrees } = options;
|
|
418
|
+
const seed = resolveSeed(options.seed);
|
|
419
|
+
const keepMulti = keeps("multiEdges", options.multiEdges);
|
|
420
|
+
const leftTotal = degreeSum("leftDegrees", leftDegrees);
|
|
421
|
+
const rightTotal = degreeSum("rightDegrees", rightDegrees);
|
|
422
|
+
if (leftTotal !== rightTotal) {
|
|
423
|
+
throw new RangeError(`the left degree sum (${leftTotal}) must equal the right degree sum (${rightTotal})`);
|
|
424
|
+
}
|
|
425
|
+
const left = leftDegrees.length;
|
|
426
|
+
const n = left + rightDegrees.length;
|
|
427
|
+
checkInt("the node count", n, 0);
|
|
428
|
+
const dst = stubList(rightDegrees, rightTotal, left);
|
|
429
|
+
shuffle(dst, new RandomStream(seed, "bipartite-configuration", 0));
|
|
430
|
+
const out = new EdgeBuffer(leftTotal);
|
|
431
|
+
eraseEdges(n, stubList(leftDegrees, leftTotal), dst, false, true, keepMulti, out);
|
|
432
|
+
const side = new Uint8Array(n).fill(1, left);
|
|
433
|
+
return applyWeights(toGraph(n, out, false, { side }), options);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The checked and precomputed form of an expected-degree model (Chung-Lu or the DC-SBM). */
|
|
437
|
+
interface ExpectedDegreePlan {
|
|
438
|
+
readonly n: number;
|
|
439
|
+
readonly seed: number;
|
|
440
|
+
readonly domain: string;
|
|
441
|
+
/** order[row] = the node of that row. */
|
|
442
|
+
readonly order: U32;
|
|
443
|
+
/** The weight of every row. */
|
|
444
|
+
readonly weight: Float64Array;
|
|
445
|
+
/** The block of every row. */
|
|
446
|
+
readonly rowBlock: U32;
|
|
447
|
+
/** starts[b] = the first row of block b; starts[B] = n. */
|
|
448
|
+
readonly starts: readonly number[];
|
|
449
|
+
/** P(u, v) = min(1, factor[r][s] w_u w_v) for u in block r, v in block s. */
|
|
450
|
+
readonly factor: readonly (readonly number[])[];
|
|
451
|
+
/** The sum of the weights. */
|
|
452
|
+
readonly total: number;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Check weights (finite, >= 0) and return their sum.
|
|
457
|
+
* @param weights - the weights
|
|
458
|
+
* @returns the sum
|
|
459
|
+
*/
|
|
460
|
+
function weightSum(weights: ArrayLike<number>): number {
|
|
461
|
+
checkInt("expectedDegrees.length", weights.length, 0);
|
|
462
|
+
let total = 0;
|
|
463
|
+
for (let i = 0; i < weights.length; i++) {
|
|
464
|
+
if (!(Number.isFinite(weights[i]) && weights[i] >= 0)) {
|
|
465
|
+
throw new RangeError(`expectedDegrees[${i}] must be a finite number >= 0, got ${String(weights[i])}`);
|
|
466
|
+
}
|
|
467
|
+
total += weights[i];
|
|
468
|
+
}
|
|
469
|
+
return total;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Build the rows of an expected-degree model: nodes of each block (consecutive index ranges) sorted
|
|
474
|
+
* by weight descending, ties by index ascending.
|
|
475
|
+
* @param weights - the node weights
|
|
476
|
+
* @param nodeStarts - block boundaries in node indices
|
|
477
|
+
* @param factor - the block-pair factors
|
|
478
|
+
* @param seed - the seed
|
|
479
|
+
* @param domain - the stream domain
|
|
480
|
+
* @param total - the weight sum
|
|
481
|
+
* @returns the plan
|
|
482
|
+
*/
|
|
483
|
+
function planRows(
|
|
484
|
+
weights: ArrayLike<number>,
|
|
485
|
+
nodeStarts: readonly number[],
|
|
486
|
+
factor: readonly (readonly number[])[],
|
|
487
|
+
seed: number,
|
|
488
|
+
domain: string,
|
|
489
|
+
total: number,
|
|
490
|
+
): ExpectedDegreePlan {
|
|
491
|
+
const n = weights.length;
|
|
492
|
+
const order = new Uint32Array(n);
|
|
493
|
+
const rowBlock = new Uint32Array(n);
|
|
494
|
+
for (let i = 0; i < n; i++) {
|
|
495
|
+
order[i] = i;
|
|
496
|
+
}
|
|
497
|
+
for (let b = 0; b + 1 < nodeStarts.length; b++) {
|
|
498
|
+
order.subarray(nodeStarts[b], nodeStarts[b + 1]).sort((x, y) => weights[y] - weights[x] || x - y);
|
|
499
|
+
rowBlock.fill(b, nodeStarts[b], nodeStarts[b + 1]);
|
|
500
|
+
}
|
|
501
|
+
const weight = Float64Array.from(order, (i) => weights[i]);
|
|
502
|
+
return { n, seed, domain, order, weight, rowBlock, starts: nodeStarts, factor, total };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Options of {@link chungLuGraph}. */
|
|
506
|
+
export interface ChungLuOptions extends WeightOptions {
|
|
507
|
+
/** The expected degree (weight) of every node, finite and >= 0. */
|
|
508
|
+
expectedDegrees: ArrayLike<number>;
|
|
509
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
510
|
+
seed?: number | undefined;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Check Chung-Lu options and build the plan: one block, factor 1 / S.
|
|
515
|
+
* @param options - the options
|
|
516
|
+
* @returns the plan
|
|
517
|
+
*/
|
|
518
|
+
export function planChungLu(options: ChungLuOptions): ExpectedDegreePlan {
|
|
519
|
+
const seed = resolveSeed(options.seed);
|
|
520
|
+
const total = weightSum(options.expectedDegrees);
|
|
521
|
+
const n = options.expectedDegrees.length;
|
|
522
|
+
return planRows(options.expectedDegrees, [0, n], [[total > 0 ? 1 / total : 0]], seed, "chung-lu", total);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Rows [start, end) of an expected-degree model (Chung-Lu, or the DC-SBM with its own plan and
|
|
527
|
+
* domain) by the skipping of Miller and Hagberg: row `row` (in block r) draws from stream
|
|
528
|
+
* (seed, domain, row) and walks blocks s = r .. B - 1 in order; inside block s it visits the later
|
|
529
|
+
* rows v (all rows of s when s > r) in row order, whose weights never increase, skipping with the
|
|
530
|
+
* previous probability p (nextSkip(detLog(1 - p)) unless p = 1) and accepting the landing v with
|
|
531
|
+
* probability q / p (one nextFloat), q = min(1, factor[r][s] w_row w_v). Edges are
|
|
532
|
+
* (order[row], order[v]) in original node indices. Concatenating consecutive row ranges gives
|
|
533
|
+
* the whole graph.
|
|
534
|
+
* @param plan - the checked options
|
|
535
|
+
* @param start - the first row
|
|
536
|
+
* @param end - one past the last row
|
|
537
|
+
* @param out - receives the edges
|
|
538
|
+
*/
|
|
539
|
+
export function chungLuRows(plan: ExpectedDegreePlan, start: number, end: number, out: EdgeBuffer): void {
|
|
540
|
+
const { order, weight, rowBlock, starts, factor } = plan;
|
|
541
|
+
const stream = new RandomStream(plan.seed, plan.domain, 0);
|
|
542
|
+
const count = starts.length - 1;
|
|
543
|
+
for (let row = start; row < end; row++) {
|
|
544
|
+
stream.reset(row);
|
|
545
|
+
const r = rowBlock[row];
|
|
546
|
+
// ponytail: O(B) per row like the block model; iterate non-zero blocks if B grows large
|
|
547
|
+
for (let s = r; s < count; s++) {
|
|
548
|
+
const cu = factor[r][s] * weight[row];
|
|
549
|
+
let v = s === r ? row + 1 : starts[s];
|
|
550
|
+
const hi = starts[s + 1];
|
|
551
|
+
if (cu === 0 || v >= hi) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
let p = Math.min(1, cu * weight[v]);
|
|
555
|
+
while (v < hi && p > 0) {
|
|
556
|
+
if (p !== 1) {
|
|
557
|
+
const logQ = detLog(1 - p);
|
|
558
|
+
// p below 2^-53 rounds 1 - p to 1: no further edge in this segment
|
|
559
|
+
if (logQ === 0) {
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
v += stream.nextSkip(logQ);
|
|
563
|
+
if (v >= hi) {
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const q = Math.min(1, cu * weight[v]);
|
|
568
|
+
if (stream.nextFloat() < q / p) {
|
|
569
|
+
out.push(order[row], order[v]);
|
|
570
|
+
}
|
|
571
|
+
p = q;
|
|
572
|
+
v++;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Run a plan over all its rows.
|
|
580
|
+
* @param plan - the plan
|
|
581
|
+
* @param nodeColumns - ground-truth columns
|
|
582
|
+
* @param options - for the weights option
|
|
583
|
+
* @returns the graph
|
|
584
|
+
*/
|
|
585
|
+
function runPlan(plan: ExpectedDegreePlan, nodeColumns: SampleGraph["nodeColumns"], options: WeightOptions): SampleGraph {
|
|
586
|
+
const mean = plan.total / 2;
|
|
587
|
+
checkEdgeCount(mean);
|
|
588
|
+
const out = new EdgeBuffer(Math.ceil(mean + 6 * Math.sqrt(mean) + 16));
|
|
589
|
+
chungLuRows(plan, 0, plan.n, out);
|
|
590
|
+
return applyWeights(toGraph(plan.n, out, false, nodeColumns), options);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The Chung-Lu expected-degree graph (F. Chung and L. Lu, "The average distances in random graphs
|
|
595
|
+
* with given expected degrees", PNAS 99, 15879-15882, 2002, doi:10.1073/pnas.252631999): every
|
|
596
|
+
* pair u != v is joined independently with probability min(1, w_u w_v / S), S the weight sum, so
|
|
597
|
+
* node u's expected degree is about w_u when every w_u w_v is below S. Linear time by the skipping
|
|
598
|
+
* of J. C. Miller and A. Hagberg, "Efficient generation of networks with given expected degrees",
|
|
599
|
+
* WAW 2011, LNCS 6732, 115-126, doi:10.1007/978-3-642-21286-4_10. Rows are the nodes sorted by
|
|
600
|
+
* weight descending (ties by index ascending); row i draws from stream (seed, "chung-lu", i) (see
|
|
601
|
+
* {@link chungLuRows}). Edges are listed by row, each (row node, later node) in original indices.
|
|
602
|
+
* Simple, undirected. O(n log n + m).
|
|
603
|
+
* @param options - expectedDegrees and seed
|
|
604
|
+
* @returns the undirected graph
|
|
605
|
+
*/
|
|
606
|
+
export function chungLuGraph(options: ChungLuOptions): SampleGraph {
|
|
607
|
+
return runPlan(planChungLu(options), undefined, options);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** Options of {@link degreeCorrectedSbmGraph}. */
|
|
611
|
+
export interface DegreeCorrectedSbmOptions extends WeightOptions {
|
|
612
|
+
/** The size of every block; block b holds the next `sizes[b]` node indices. */
|
|
613
|
+
sizes: readonly number[];
|
|
614
|
+
/** The target degree of every node, finite and >= 0; length = the sum of sizes. */
|
|
615
|
+
expectedDegrees: ArrayLike<number>;
|
|
616
|
+
/** The mixing parameter mu in [0, 1]: the share of each node's edges that leave its block. */
|
|
617
|
+
mixing: number;
|
|
618
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
619
|
+
seed?: number | undefined;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Check DC-SBM options and build the plan.
|
|
624
|
+
* @param options - the options
|
|
625
|
+
* @returns the plan
|
|
626
|
+
*/
|
|
627
|
+
export function planDegreeCorrectedSbm(options: DegreeCorrectedSbmOptions): ExpectedDegreePlan {
|
|
628
|
+
const { sizes, expectedDegrees, mixing } = options;
|
|
629
|
+
const seed = resolveSeed(options.seed);
|
|
630
|
+
checkInt("sizes.length", sizes.length, 1);
|
|
631
|
+
checkProbability("mixing", mixing);
|
|
632
|
+
const total = weightSum(expectedDegrees);
|
|
633
|
+
const starts = [0];
|
|
634
|
+
for (let b = 0; b < sizes.length; b++) {
|
|
635
|
+
checkInt(`sizes[${b}]`, sizes[b], 0);
|
|
636
|
+
starts.push(starts[b] + sizes[b]);
|
|
637
|
+
}
|
|
638
|
+
if (starts[sizes.length] !== expectedDegrees.length) {
|
|
639
|
+
throw new RangeError(
|
|
640
|
+
`the sizes sum to ${starts[sizes.length]}, but expectedDegrees has ${expectedDegrees.length} entries`,
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
const kappa = sizes.map((_, b) => {
|
|
644
|
+
let k = 0;
|
|
645
|
+
for (let i = starts[b]; i < starts[b + 1]; i++) {
|
|
646
|
+
k += expectedDegrees[i];
|
|
647
|
+
}
|
|
648
|
+
return k;
|
|
649
|
+
});
|
|
650
|
+
const between = total > 0 ? mixing / total : 0;
|
|
651
|
+
const inside = kappa.map((kr) => (kr > 0 ? (1 - mixing) / kr : 0));
|
|
652
|
+
const factor = kappa.map((_, r) => kappa.map((_2, s) => (r === s ? inside[r] : between)));
|
|
653
|
+
return planRows(expectedDegrees, starts, factor, seed, "dcsbm", total);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* The degree-corrected stochastic block model (B. Karrer and M. E. J. Newman, "Stochastic
|
|
658
|
+
* blockmodels and community structure in networks", Phys. Rev. E 83, 016107, 2011,
|
|
659
|
+
* doi:10.1103/PhysRevE.83.016107), sampled as a simple graph with Bernoulli edges:
|
|
660
|
+
* P(uv) = min(1, theta_u theta_v omega_rs) with theta_u = d_u / kappa_r (kappa_r the degree total of
|
|
661
|
+
* u's block r, K the grand total), omega_rr = (1 - mu) kappa_r and omega_rs = mu kappa_r kappa_s / K
|
|
662
|
+
* for r != s. So node u's expected degree is about d_u (1 - mu kappa_r / K): the inside share is
|
|
663
|
+
* 1 - mu, but the outside share misses the mu kappa_r / K that would land in u's own block.
|
|
664
|
+
* Rows: block by block, inside a block the nodes sorted by d descending (ties by index); row i
|
|
665
|
+
* draws from stream (seed, "dcsbm", i) and walks its own block's later rows, then every later
|
|
666
|
+
* block (see {@link chungLuRows}). Edges are listed by row in original node indices. Node column
|
|
667
|
+
* `community` (u32). With one block and mu = 0 it is Chung-Lu. O(n log n + n B + m).
|
|
668
|
+
* @param options - sizes, expectedDegrees, mixing and seed
|
|
669
|
+
* @returns the undirected graph
|
|
670
|
+
*/
|
|
671
|
+
export function degreeCorrectedSbmGraph(options: DegreeCorrectedSbmOptions): SampleGraph {
|
|
672
|
+
const plan = planDegreeCorrectedSbm(options);
|
|
673
|
+
const community = new Uint32Array(plan.n);
|
|
674
|
+
for (let b = 0; b + 1 < plan.starts.length; b++) {
|
|
675
|
+
community.fill(b, plan.starts[b], plan.starts[b + 1]);
|
|
676
|
+
}
|
|
677
|
+
return runPlan(plan, { community }, options);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** Options of {@link randomRegularGraph}. */
|
|
681
|
+
export interface RandomRegularOptions extends WeightOptions {
|
|
682
|
+
/** The node count, >= 0. */
|
|
683
|
+
n: number;
|
|
684
|
+
/** The degree of every node, in [0, n - 1]; n d must be even. */
|
|
685
|
+
d: number;
|
|
686
|
+
/** The seed, an integer in [0, 2^53); default 0. */
|
|
687
|
+
seed?: number | undefined;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** Attempts before randomRegularGraph gives up. */
|
|
691
|
+
const REGULAR_ATTEMPTS = 100;
|
|
692
|
+
/** Re-pairing rounds inside one attempt before it counts as failed. */
|
|
693
|
+
const REGULAR_ROUNDS = 1000;
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* One attempt of the Steger-Wormald pairing; fills `adj` (d slots per node) and `deg`.
|
|
697
|
+
* @param n - the node count
|
|
698
|
+
* @param d - the degree
|
|
699
|
+
* @param stream - the attempt's stream
|
|
700
|
+
* @param adj - the adjacency slots
|
|
701
|
+
* @param deg - the filled slot count per node
|
|
702
|
+
* @returns true on success
|
|
703
|
+
*/
|
|
704
|
+
function regularAttempt(n: number, d: number, stream: RandomStream, adj: U32, deg: U32): boolean {
|
|
705
|
+
deg.fill(0);
|
|
706
|
+
const has = (u: number, v: number): boolean => {
|
|
707
|
+
const base = u * d;
|
|
708
|
+
for (let k = base; k < base + deg[u]; k++) {
|
|
709
|
+
if (adj[k] === v) {
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
};
|
|
715
|
+
const potential = new Uint32Array(n);
|
|
716
|
+
let stubs: U32 = stubList(new Uint32Array(n).fill(d), n * d);
|
|
717
|
+
for (let round = 0; round < REGULAR_ROUNDS && stubs.length > 0; round++) {
|
|
718
|
+
shuffle(stubs, stream);
|
|
719
|
+
const touched: number[] = [];
|
|
720
|
+
for (let k = 0; k < stubs.length; k += 2) {
|
|
721
|
+
const u = Math.min(stubs[k], stubs[k + 1]);
|
|
722
|
+
const v = Math.max(stubs[k], stubs[k + 1]);
|
|
723
|
+
if (u !== v && !has(u, v)) {
|
|
724
|
+
adj[u * d + deg[u]++] = v;
|
|
725
|
+
adj[v * d + deg[v]++] = u;
|
|
726
|
+
} else {
|
|
727
|
+
for (const x of [u, v]) {
|
|
728
|
+
if (potential[x]++ === 0) {
|
|
729
|
+
touched.push(x);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
touched.sort((a, b) => a - b);
|
|
735
|
+
// suitable: some pair of distinct leftover nodes is not yet an edge
|
|
736
|
+
let suitable = touched.length === 0;
|
|
737
|
+
for (let i = 0; i < touched.length && !suitable; i++) {
|
|
738
|
+
for (let j = i + 1; j < touched.length && !suitable; j++) {
|
|
739
|
+
suitable = !has(touched[i], touched[j]);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (!suitable) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
let total = 0;
|
|
746
|
+
for (const x of touched) {
|
|
747
|
+
total += potential[x];
|
|
748
|
+
}
|
|
749
|
+
const next = new Uint32Array(total);
|
|
750
|
+
let k = 0;
|
|
751
|
+
for (const x of touched) {
|
|
752
|
+
next.fill(x, k, k + potential[x]);
|
|
753
|
+
k += potential[x];
|
|
754
|
+
potential[x] = 0;
|
|
755
|
+
}
|
|
756
|
+
stubs = next;
|
|
757
|
+
}
|
|
758
|
+
return stubs.length === 0;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* A uniformly-ish random d-regular simple graph by the pairing algorithm of A. Steger and
|
|
763
|
+
* N. C. Wormald, "Generating random regular graphs quickly", Combin. Probab. Comput. 8, 377-396,
|
|
764
|
+
* 1999, doi:10.1017/S0963548399003867, structured like networkx's `random_regular_graph`: attempt
|
|
765
|
+
* a (a = 0, 1, ...) draws from stream (seed, "random-regular", a); it Fisher-Yates shuffles the
|
|
766
|
+
* stub list (node i repeated d times, ascending), keeps each consecutive pair that is not a loop
|
|
767
|
+
* and not yet an edge, and re-pairs the leftover stubs (ascending node order) while some leftover
|
|
768
|
+
* pair could still be joined; otherwise it restarts. At most 100 attempts of at most 1000 rounds,
|
|
769
|
+
* then a RangeError. Edges (u, v), u < v, in lexicographic order. Expected O(n d^2) for small d.
|
|
770
|
+
* @param options - n, d and seed
|
|
771
|
+
* @returns the undirected graph
|
|
772
|
+
*/
|
|
773
|
+
export function randomRegularGraph(options: RandomRegularOptions): SampleGraph {
|
|
774
|
+
const { n, d } = options;
|
|
775
|
+
const seed = resolveSeed(options.seed);
|
|
776
|
+
checkInt("n", n, 0);
|
|
777
|
+
checkInt("d", d, 0, Math.max(0, n - 1));
|
|
778
|
+
if ((n * d) % 2 !== 0) {
|
|
779
|
+
throw new RangeError(`n * d must be even, got ${n} * ${d}`);
|
|
780
|
+
}
|
|
781
|
+
checkEdgeCount((n * d) / 2);
|
|
782
|
+
const out = new EdgeBuffer((n * d) / 2);
|
|
783
|
+
if (d > 0) {
|
|
784
|
+
const adj = new Uint32Array(n * d);
|
|
785
|
+
const deg = new Uint32Array(n);
|
|
786
|
+
const stream = new RandomStream(seed, "random-regular", 0);
|
|
787
|
+
let attempt = 0;
|
|
788
|
+
for (; attempt < REGULAR_ATTEMPTS; attempt++) {
|
|
789
|
+
stream.reset(attempt);
|
|
790
|
+
if (regularAttempt(n, d, stream, adj, deg)) {
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (attempt === REGULAR_ATTEMPTS) {
|
|
795
|
+
throw new RangeError(`randomRegularGraph found no ${d}-regular graph on ${n} nodes in ${REGULAR_ATTEMPTS} attempts`);
|
|
796
|
+
}
|
|
797
|
+
for (let u = 0; u < n; u++) {
|
|
798
|
+
const row = adj.subarray(u * d, u * d + d).sort();
|
|
799
|
+
for (const v of row) {
|
|
800
|
+
if (v > u) {
|
|
801
|
+
out.push(u, v);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return applyWeights(toGraph(n, out, false), options);
|
|
807
|
+
}
|