datahike-browser-tests 1.0.0
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/.circleci/config.yml +405 -0
- package/.circleci/scripts/gen_ci.clj +194 -0
- package/.cirrus.yml +60 -0
- package/.clj-kondo/babashka/sci/config.edn +1 -0
- package/.clj-kondo/babashka/sci/sci/core.clj +9 -0
- package/.clj-kondo/config.edn +95 -0
- package/.dir-locals.el +2 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +68 -0
- package/.github/ISSUE_TEMPLATE/2-feature-request.yml +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +6 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/native-image.yml +84 -0
- package/LICENSE +203 -0
- package/README.md +273 -0
- package/bb/deps.edn +9 -0
- package/bb/resources/github-fingerprints +3 -0
- package/bb/resources/native-image-tests/run-bb-pod-tests.clj +162 -0
- package/bb/resources/native-image-tests/run-libdatahike-tests +12 -0
- package/bb/resources/native-image-tests/run-native-image-tests +74 -0
- package/bb/resources/native-image-tests/run-python-tests +22 -0
- package/bb/resources/native-image-tests/testconfig.attr-refs.edn +6 -0
- package/bb/resources/native-image-tests/testconfig.edn +5 -0
- package/bb/resources/template/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
- package/bb/resources/template/.settings/org.eclipse.jdt.core.prefs +9 -0
- package/bb/resources/template/.settings/org.eclipse.m2e.core.prefs +4 -0
- package/bb/resources/template/pom.xml +22 -0
- package/bb/src/tools/build.clj +132 -0
- package/bb/src/tools/clj_kondo.clj +32 -0
- package/bb/src/tools/deploy.clj +26 -0
- package/bb/src/tools/examples.clj +19 -0
- package/bb/src/tools/npm.clj +100 -0
- package/bb/src/tools/python.clj +14 -0
- package/bb/src/tools/release.clj +94 -0
- package/bb/src/tools/test.clj +148 -0
- package/bb/src/tools/version.clj +47 -0
- package/bb.edn +269 -0
- package/benchmark/src/benchmark/cli.clj +195 -0
- package/benchmark/src/benchmark/compare.clj +157 -0
- package/benchmark/src/benchmark/config.clj +316 -0
- package/benchmark/src/benchmark/measure.clj +187 -0
- package/benchmark/src/benchmark/store.clj +190 -0
- package/benchmark/test/benchmark/measure_test.clj +156 -0
- package/build.clj +30 -0
- package/config.edn +49 -0
- package/deps.edn +138 -0
- package/dev/sandbox.clj +82 -0
- package/dev/sandbox.cljs +127 -0
- package/dev/sandbox_benchmarks.clj +27 -0
- package/dev/sandbox_client.clj +87 -0
- package/dev/sandbox_transact_bench.clj +109 -0
- package/dev/user.clj +79 -0
- package/doc/README.md +96 -0
- package/doc/adl/README.md +6 -0
- package/doc/adl/adr-000-adr.org +28 -0
- package/doc/adl/adr-001-attribute-references.org +15 -0
- package/doc/adl/adr-002-build-tooling.org +54 -0
- package/doc/adl/adr-003-db-meta-data.md +52 -0
- package/doc/adl/adr-004-github-flow.md +40 -0
- package/doc/adl/adr-XYZ-template.md +30 -0
- package/doc/adl/index.org +3 -0
- package/doc/assets/datahike-logo.svg +3 -0
- package/doc/assets/datahiking-invoice.org +85 -0
- package/doc/assets/hhtree2.png +0 -0
- package/doc/assets/network_topology.svg +624 -0
- package/doc/assets/perf.png +0 -0
- package/doc/assets/schema_mindmap.mm +132 -0
- package/doc/assets/schema_mindmap.svg +970 -0
- package/doc/assets/temporal_index.mm +74 -0
- package/doc/backend-development.md +78 -0
- package/doc/bb-pod.md +89 -0
- package/doc/benchmarking.md +360 -0
- package/doc/bindings/edn-conversion.md +383 -0
- package/doc/cli.md +162 -0
- package/doc/cljdoc.edn +27 -0
- package/doc/cljs-support.md +133 -0
- package/doc/config.md +406 -0
- package/doc/contributing.md +114 -0
- package/doc/datalog-vs-sql.md +210 -0
- package/doc/datomic_differences.md +109 -0
- package/doc/development/pull-api-ns.md +186 -0
- package/doc/development/pull-frame-state-diagram.jpg +0 -0
- package/doc/distributed.md +566 -0
- package/doc/entity_spec.md +92 -0
- package/doc/gc.md +273 -0
- package/doc/java-api.md +808 -0
- package/doc/javascript-api.md +421 -0
- package/doc/libdatahike.md +86 -0
- package/doc/logging_and_error_handling.md +43 -0
- package/doc/norms.md +66 -0
- package/doc/schema-migration.md +85 -0
- package/doc/schema.md +287 -0
- package/doc/storage-backends.md +363 -0
- package/doc/store-id-refactoring.md +596 -0
- package/doc/time_variance.md +325 -0
- package/doc/unstructured.md +167 -0
- package/doc/versioning.md +261 -0
- package/examples/basic/README.md +19 -0
- package/examples/basic/deps.edn +6 -0
- package/examples/basic/docker-compose.yml +13 -0
- package/examples/basic/src/examples/core.clj +60 -0
- package/examples/basic/src/examples/schema.clj +155 -0
- package/examples/basic/src/examples/store.clj +60 -0
- package/examples/basic/src/examples/time_travel.clj +185 -0
- package/examples/java/.settings/org.eclipse.core.resources.prefs +3 -0
- package/examples/java/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
- package/examples/java/.settings/org.eclipse.jdt.core.prefs +9 -0
- package/examples/java/.settings/org.eclipse.m2e.core.prefs +4 -0
- package/examples/java/README.md +162 -0
- package/examples/java/pom.xml +62 -0
- package/examples/java/src/main/java/examples/QuickStart.java +115 -0
- package/examples/java/src/main/java/examples/SchemaExample.java +148 -0
- package/examples/java/src/main/java/examples/TimeTravelExample.java +121 -0
- package/flake.lock +27 -0
- package/flake.nix +27 -0
- package/http-server/datahike/http/middleware.clj +75 -0
- package/http-server/datahike/http/server.clj +269 -0
- package/java/src/datahike/java/Database.java +274 -0
- package/java/src/datahike/java/Datahike.java +281 -0
- package/java/src/datahike/java/DatahikeGeneratedTest.java +349 -0
- package/java/src/datahike/java/DatahikeTest.java +370 -0
- package/java/src/datahike/java/EDN.java +170 -0
- package/java/src/datahike/java/IEntity.java +11 -0
- package/java/src/datahike/java/Keywords.java +161 -0
- package/java/src/datahike/java/SchemaFlexibility.java +52 -0
- package/java/src/datahike/java/Util.java +219 -0
- package/karma.conf.js +19 -0
- package/libdatahike/compile-cpp +7 -0
- package/libdatahike/src/datahike/impl/LibDatahikeBase.java +203 -0
- package/libdatahike/src/datahike/impl/libdatahike.clj +59 -0
- package/libdatahike/src/test_cpp.cpp +61 -0
- package/npm-package/PUBLISHING.md +140 -0
- package/npm-package/README.md +226 -0
- package/npm-package/package.template.json +34 -0
- package/npm-package/test-isomorphic.ts +281 -0
- package/npm-package/test.js +557 -0
- package/npm-package/typescript-test.ts +70 -0
- package/package.json +16 -0
- package/pydatahike/README.md +569 -0
- package/pydatahike/pyproject.toml +91 -0
- package/pydatahike/setup.py +42 -0
- package/pydatahike/src/datahike/__init__.py +134 -0
- package/pydatahike/src/datahike/_native.py +250 -0
- package/pydatahike/src/datahike/_version.py +2 -0
- package/pydatahike/src/datahike/database.py +722 -0
- package/pydatahike/src/datahike/edn.py +311 -0
- package/pydatahike/src/datahike/py.typed +0 -0
- package/pydatahike/tests/conftest.py +17 -0
- package/pydatahike/tests/test_basic.py +170 -0
- package/pydatahike/tests/test_database.py +51 -0
- package/pydatahike/tests/test_edn_conversion.py +299 -0
- package/pydatahike/tests/test_query.py +99 -0
- package/pydatahike/tests/test_schema.py +55 -0
- package/resources/clj-kondo.exports/io.replikativ/datahike/config.edn +5 -0
- package/resources/example_server.edn +4 -0
- package/shadow-cljs.edn +56 -0
- package/src/data_readers.clj +7 -0
- package/src/datahike/api/impl.cljc +176 -0
- package/src/datahike/api/specification.cljc +633 -0
- package/src/datahike/api/types.cljc +261 -0
- package/src/datahike/api.cljc +41 -0
- package/src/datahike/array.cljc +99 -0
- package/src/datahike/cli.clj +166 -0
- package/src/datahike/cljs.cljs +6 -0
- package/src/datahike/codegen/cli.clj +406 -0
- package/src/datahike/codegen/clj_kondo.clj +291 -0
- package/src/datahike/codegen/java.clj +403 -0
- package/src/datahike/codegen/naming.cljc +33 -0
- package/src/datahike/codegen/native.clj +559 -0
- package/src/datahike/codegen/pod.clj +488 -0
- package/src/datahike/codegen/python.clj +838 -0
- package/src/datahike/codegen/report.clj +55 -0
- package/src/datahike/codegen/typescript.clj +262 -0
- package/src/datahike/codegen/validation.clj +145 -0
- package/src/datahike/config.cljc +294 -0
- package/src/datahike/connections.cljc +16 -0
- package/src/datahike/connector.cljc +265 -0
- package/src/datahike/constants.cljc +142 -0
- package/src/datahike/core.cljc +297 -0
- package/src/datahike/datom.cljc +459 -0
- package/src/datahike/db/interface.cljc +119 -0
- package/src/datahike/db/search.cljc +305 -0
- package/src/datahike/db/transaction.cljc +937 -0
- package/src/datahike/db/utils.cljc +338 -0
- package/src/datahike/db.cljc +956 -0
- package/src/datahike/experimental/unstructured.cljc +126 -0
- package/src/datahike/experimental/versioning.cljc +172 -0
- package/src/datahike/externs.js +31 -0
- package/src/datahike/gc.cljc +69 -0
- package/src/datahike/http/client.clj +188 -0
- package/src/datahike/http/writer.clj +79 -0
- package/src/datahike/impl/entity.cljc +218 -0
- package/src/datahike/index/interface.cljc +93 -0
- package/src/datahike/index/persistent_set.cljc +469 -0
- package/src/datahike/index/utils.cljc +44 -0
- package/src/datahike/index.cljc +32 -0
- package/src/datahike/js/api.cljs +172 -0
- package/src/datahike/js/api_macros.clj +22 -0
- package/src/datahike/js.cljs +163 -0
- package/src/datahike/json.cljc +209 -0
- package/src/datahike/lru.cljc +146 -0
- package/src/datahike/migrate.clj +39 -0
- package/src/datahike/norm/norm.clj +245 -0
- package/src/datahike/online_gc.cljc +252 -0
- package/src/datahike/pod.clj +155 -0
- package/src/datahike/pull_api.cljc +325 -0
- package/src/datahike/query.cljc +1945 -0
- package/src/datahike/query_stats.cljc +88 -0
- package/src/datahike/readers.cljc +62 -0
- package/src/datahike/remote.cljc +218 -0
- package/src/datahike/schema.cljc +228 -0
- package/src/datahike/schema_cache.cljc +42 -0
- package/src/datahike/spec.cljc +101 -0
- package/src/datahike/store.cljc +80 -0
- package/src/datahike/tools.cljc +308 -0
- package/src/datahike/transit.cljc +80 -0
- package/src/datahike/writer.cljc +239 -0
- package/src/datahike/writing.cljc +362 -0
- package/src/deps.cljs +1 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/insert.cljc +76 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/upsert.cljc +128 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree.cljc +213 -0
- package/test/datahike/backward_compatibility_test/src/backward_test.clj +37 -0
- package/test/datahike/integration_test/config_record_file_test.clj +14 -0
- package/test/datahike/integration_test/config_record_test.clj +14 -0
- package/test/datahike/integration_test/depr_config_uri_test.clj +15 -0
- package/test/datahike/integration_test/return_map_test.clj +62 -0
- package/test/datahike/integration_test.cljc +67 -0
- package/test/datahike/norm/norm_test.clj +124 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/004-tx-data-and-tx-fn-test.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/01-transact-basic-characters.edn +2 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/02 add occupation.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/checksums.edn +12 -0
- package/test/datahike/norm/resources/simple-test/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/simple-test/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/simple-test/checksums.edn +4 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/checksums.edn +6 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/004-tx-data-and-tx-fn-test.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/checksums.edn +2 -0
- package/test/datahike/norm/resources/tx-fn-test/first/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/tx-fn-test/first/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/tx-fn-test/first/checksums.edn +4 -0
- package/test/datahike/norm/resources/tx-fn-test/second/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/tx-fn-test/second/checksums.edn +2 -0
- package/test/datahike/test/api_test.cljc +895 -0
- package/test/datahike/test/array_test.cljc +40 -0
- package/test/datahike/test/attribute_refs/datoms_test.cljc +140 -0
- package/test/datahike/test/attribute_refs/db_test.cljc +42 -0
- package/test/datahike/test/attribute_refs/differences_test.cljc +515 -0
- package/test/datahike/test/attribute_refs/entity_test.cljc +89 -0
- package/test/datahike/test/attribute_refs/pull_api_test.cljc +320 -0
- package/test/datahike/test/attribute_refs/query_find_specs_test.cljc +59 -0
- package/test/datahike/test/attribute_refs/query_fns_test.cljc +130 -0
- package/test/datahike/test/attribute_refs/query_interop_test.cljc +47 -0
- package/test/datahike/test/attribute_refs/query_not_test.cljc +193 -0
- package/test/datahike/test/attribute_refs/query_or_test.cljc +137 -0
- package/test/datahike/test/attribute_refs/query_pull_test.cljc +156 -0
- package/test/datahike/test/attribute_refs/query_rules_test.cljc +176 -0
- package/test/datahike/test/attribute_refs/query_test.cljc +241 -0
- package/test/datahike/test/attribute_refs/temporal_search.cljc +22 -0
- package/test/datahike/test/attribute_refs/transact_test.cljc +220 -0
- package/test/datahike/test/attribute_refs/utils.cljc +128 -0
- package/test/datahike/test/cache_test.cljc +38 -0
- package/test/datahike/test/components_test.cljc +92 -0
- package/test/datahike/test/config_test.cljc +158 -0
- package/test/datahike/test/core_test.cljc +105 -0
- package/test/datahike/test/datom_test.cljc +44 -0
- package/test/datahike/test/db_test.cljc +54 -0
- package/test/datahike/test/entity_spec_test.cljc +159 -0
- package/test/datahike/test/entity_test.cljc +103 -0
- package/test/datahike/test/explode_test.cljc +143 -0
- package/test/datahike/test/filter_test.cljc +75 -0
- package/test/datahike/test/gc_test.cljc +159 -0
- package/test/datahike/test/http/server_test.clj +192 -0
- package/test/datahike/test/http/writer_test.clj +86 -0
- package/test/datahike/test/ident_test.cljc +32 -0
- package/test/datahike/test/index_test.cljc +345 -0
- package/test/datahike/test/insert.cljc +125 -0
- package/test/datahike/test/java_bindings_test.clj +6 -0
- package/test/datahike/test/listen_test.cljc +41 -0
- package/test/datahike/test/lookup_refs_test.cljc +266 -0
- package/test/datahike/test/lru_test.cljc +27 -0
- package/test/datahike/test/migrate_test.clj +297 -0
- package/test/datahike/test/model/core.cljc +376 -0
- package/test/datahike/test/model/invariant.cljc +142 -0
- package/test/datahike/test/model/rng.cljc +82 -0
- package/test/datahike/test/model_test.clj +217 -0
- package/test/datahike/test/nodejs_test.cljs +262 -0
- package/test/datahike/test/online_gc_test.cljc +475 -0
- package/test/datahike/test/pod_test.clj +369 -0
- package/test/datahike/test/pull_api_test.cljc +474 -0
- package/test/datahike/test/purge_test.cljc +144 -0
- package/test/datahike/test/query_aggregates_test.cljc +101 -0
- package/test/datahike/test/query_find_specs_test.cljc +52 -0
- package/test/datahike/test/query_fns_test.cljc +523 -0
- package/test/datahike/test/query_interop_test.cljc +47 -0
- package/test/datahike/test/query_not_test.cljc +189 -0
- package/test/datahike/test/query_or_test.cljc +158 -0
- package/test/datahike/test/query_pull_test.cljc +147 -0
- package/test/datahike/test/query_rules_test.cljc +248 -0
- package/test/datahike/test/query_stats_test.cljc +218 -0
- package/test/datahike/test/query_test.cljc +984 -0
- package/test/datahike/test/schema_test.cljc +424 -0
- package/test/datahike/test/specification_test.cljc +30 -0
- package/test/datahike/test/store_test.cljc +78 -0
- package/test/datahike/test/stress_test.cljc +57 -0
- package/test/datahike/test/time_variance_test.cljc +518 -0
- package/test/datahike/test/tools_test.clj +134 -0
- package/test/datahike/test/transact_test.cljc +518 -0
- package/test/datahike/test/tuples_test.cljc +564 -0
- package/test/datahike/test/unstructured_test.cljc +291 -0
- package/test/datahike/test/upsert_impl_test.cljc +205 -0
- package/test/datahike/test/upsert_test.cljc +363 -0
- package/test/datahike/test/utils.cljc +110 -0
- package/test/datahike/test/validation_test.cljc +48 -0
- package/test/datahike/test/versioning_test.cljc +56 -0
- package/test/datahike/test.cljc +66 -0
- package/tests.edn +24 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
(ns datahike.test.model.core
|
|
2
|
+
"Pure model of Datahike semantics for model-based testing.
|
|
3
|
+
|
|
4
|
+
This namespace implements a pure Clojure model of how Datahike
|
|
5
|
+
should behave, without any side effects. It can be used to:
|
|
6
|
+
|
|
7
|
+
1. Generate valid sequences of transactions
|
|
8
|
+
2. Track expected state after each transaction
|
|
9
|
+
3. Compare expected state against actual Datahike state
|
|
10
|
+
|
|
11
|
+
The model handles:
|
|
12
|
+
- Cardinality semantics (one vs many)
|
|
13
|
+
- Type constraints (string, long, ref)
|
|
14
|
+
- Historical state tracking (for as-of queries)
|
|
15
|
+
- Index content tracking (EAVT, AVET, AEVT)"
|
|
16
|
+
(:require [datahike.test.model.rng :as rng]))
|
|
17
|
+
|
|
18
|
+
;; =============================================================================
|
|
19
|
+
;; Pattern Matching (for remove-by-pattern)
|
|
20
|
+
;; =============================================================================
|
|
21
|
+
|
|
22
|
+
(defn- matches
|
|
23
|
+
"Helper that treats nil as wildcard."
|
|
24
|
+
[x0 x]
|
|
25
|
+
(or (nil? x0) (= x0 x)))
|
|
26
|
+
|
|
27
|
+
(defn predicate-from-pattern
|
|
28
|
+
"Returns a predicate function for a pattern.
|
|
29
|
+
Nil in any position matches anything."
|
|
30
|
+
[[e0 a0 v0]]
|
|
31
|
+
(fn [[e a v]]
|
|
32
|
+
(and (matches e0 e)
|
|
33
|
+
(matches a0 a)
|
|
34
|
+
(matches v0 v))))
|
|
35
|
+
|
|
36
|
+
(defn remove-pattern
|
|
37
|
+
"Filter a set of datoms by removing those matching pattern."
|
|
38
|
+
[datom-set pattern]
|
|
39
|
+
(into #{} (remove (predicate-from-pattern pattern)) datom-set))
|
|
40
|
+
|
|
41
|
+
;; =============================================================================
|
|
42
|
+
;; Model State
|
|
43
|
+
;; =============================================================================
|
|
44
|
+
|
|
45
|
+
(defrecord Model
|
|
46
|
+
[;; Schema: {attr-ident {:db/valueType, :db/cardinality, :db/index, ...}}
|
|
47
|
+
schema
|
|
48
|
+
;; Current datoms: #{[e a v]}
|
|
49
|
+
current-datoms
|
|
50
|
+
;; Historical states: {tx #{[e a v]}}
|
|
51
|
+
;; Maps transaction ID to state at that transaction
|
|
52
|
+
history
|
|
53
|
+
;; Transaction counter
|
|
54
|
+
next-tx])
|
|
55
|
+
|
|
56
|
+
(defn create-model
|
|
57
|
+
"Create initial model state with given schema."
|
|
58
|
+
[schema]
|
|
59
|
+
(->Model schema #{} {} 536870912))
|
|
60
|
+
|
|
61
|
+
;; =============================================================================
|
|
62
|
+
;; Core Semantics: Accumulate Datom
|
|
63
|
+
;; =============================================================================
|
|
64
|
+
|
|
65
|
+
(defn lookup-attribute
|
|
66
|
+
"Look up attribute info from schema."
|
|
67
|
+
[schema a]
|
|
68
|
+
(or (get schema a)
|
|
69
|
+
(throw (ex-info (str "Attribute not found: " a) {:attribute a}))))
|
|
70
|
+
|
|
71
|
+
(defn accumulate-datom
|
|
72
|
+
"Apply a single datom to the model state.
|
|
73
|
+
|
|
74
|
+
This is the core of the model - it implements Datahike's
|
|
75
|
+
cardinality semantics in pure Clojure:
|
|
76
|
+
|
|
77
|
+
- :db.cardinality/one: Replaces existing value for [e a]
|
|
78
|
+
- :db.cardinality/many: Accumulates values, no duplicates
|
|
79
|
+
|
|
80
|
+
Returns updated datom set."
|
|
81
|
+
[schema datom-set [e a v added]]
|
|
82
|
+
(if added
|
|
83
|
+
(let [attr (lookup-attribute schema a)
|
|
84
|
+
cardinality (or (:db/cardinality attr) :db.cardinality/one)]
|
|
85
|
+
(case cardinality
|
|
86
|
+
:db.cardinality/many
|
|
87
|
+
(if (some (predicate-from-pattern [e a v]) datom-set)
|
|
88
|
+
datom-set
|
|
89
|
+
(conj datom-set [e a v]))
|
|
90
|
+
:db.cardinality/one
|
|
91
|
+
(-> datom-set
|
|
92
|
+
(remove-pattern [e a nil])
|
|
93
|
+
(conj [e a v]))))
|
|
94
|
+
(remove-pattern datom-set [e a v])))
|
|
95
|
+
|
|
96
|
+
(defn apply-tx
|
|
97
|
+
"Apply a transaction to the model, returning updated model.
|
|
98
|
+
|
|
99
|
+
Transaction format: {:tx-data [[op e a v] ...]}
|
|
100
|
+
where op is :db/add or :db/retract.
|
|
101
|
+
|
|
102
|
+
Also records historical state for as-of queries."
|
|
103
|
+
[model {:keys [tx-data] :as _tx}]
|
|
104
|
+
(let [tx-id (:next-tx model)
|
|
105
|
+
;; For accumulate-datom, we need [e a v added] (4 elements)
|
|
106
|
+
datoms-for-accumulate (mapv (fn [[op e a v]]
|
|
107
|
+
[e a v (= op :db/add)])
|
|
108
|
+
tx-data)
|
|
109
|
+
new-current (reduce
|
|
110
|
+
(fn [acc datom]
|
|
111
|
+
(accumulate-datom (:schema model) acc datom))
|
|
112
|
+
(:current-datoms model)
|
|
113
|
+
datoms-for-accumulate)
|
|
114
|
+
new-history (assoc (:history model) tx-id new-current)]
|
|
115
|
+
(assoc model
|
|
116
|
+
:current-datoms new-current
|
|
117
|
+
:history new-history
|
|
118
|
+
:next-tx (inc tx-id))))
|
|
119
|
+
|
|
120
|
+
(defn apply-txs
|
|
121
|
+
"Apply multiple transactions sequentially."
|
|
122
|
+
[model txs]
|
|
123
|
+
(reduce apply-tx model txs))
|
|
124
|
+
|
|
125
|
+
;; =============================================================================
|
|
126
|
+
;; Querying the Model
|
|
127
|
+
;; =============================================================================
|
|
128
|
+
|
|
129
|
+
(defn get-current-datoms
|
|
130
|
+
"Get current datoms as a set of [e a v] tuples."
|
|
131
|
+
[model]
|
|
132
|
+
(:current-datoms model))
|
|
133
|
+
|
|
134
|
+
(defn get-datoms-at-tx
|
|
135
|
+
"Get datoms as of a specific transaction."
|
|
136
|
+
[model tx-id]
|
|
137
|
+
(get (:history model) tx-id))
|
|
138
|
+
|
|
139
|
+
(defn get-transaction-ids
|
|
140
|
+
"Get all transaction IDs in order."
|
|
141
|
+
[model]
|
|
142
|
+
(sort (keys (:history model))))
|
|
143
|
+
|
|
144
|
+
;; =============================================================================
|
|
145
|
+
;; Index Computation
|
|
146
|
+
;; =============================================================================
|
|
147
|
+
|
|
148
|
+
(defn compute-eavt
|
|
149
|
+
"Compute EAVT index from model (sorted by [e a v])."
|
|
150
|
+
[model]
|
|
151
|
+
(sort-by identity (get-current-datoms model)))
|
|
152
|
+
|
|
153
|
+
(defn compute-aevt
|
|
154
|
+
"Compute AEVT index from model (sorted by [a e v])."
|
|
155
|
+
[model]
|
|
156
|
+
(sort-by (fn [[e a v]] [a e v]) (get-current-datoms model)))
|
|
157
|
+
|
|
158
|
+
(defn indexed-attrs
|
|
159
|
+
"Get set of indexed attribute identifiers from schema."
|
|
160
|
+
[schema]
|
|
161
|
+
(set (keep (fn [[k v]] (when (:db/index v) k)) schema)))
|
|
162
|
+
|
|
163
|
+
(defn ref-attrs
|
|
164
|
+
"Get set of ref-type attribute identifiers from schema."
|
|
165
|
+
[schema]
|
|
166
|
+
(set (keep (fn [[k v]] (when (= :db.type/ref (:db/valueType v)) k)) schema)))
|
|
167
|
+
|
|
168
|
+
(defn compute-avet
|
|
169
|
+
"Compute AVET index from model.
|
|
170
|
+
|
|
171
|
+
Only includes datoms where:
|
|
172
|
+
- Attribute has :db/index true, OR
|
|
173
|
+
- Attribute is :db.type/ref"
|
|
174
|
+
[model]
|
|
175
|
+
(let [schema (:schema model)
|
|
176
|
+
indexed (indexed-attrs schema)
|
|
177
|
+
refs (ref-attrs schema)
|
|
178
|
+
indexed-or-ref? (fn [a]
|
|
179
|
+
(or (contains? indexed a)
|
|
180
|
+
(contains? refs a)))]
|
|
181
|
+
(sort-by (fn [[e a v]] [a v e])
|
|
182
|
+
(filter (fn [[_e a _v]] (indexed-or-ref? a))
|
|
183
|
+
(get-current-datoms model)))))
|
|
184
|
+
|
|
185
|
+
;; =============================================================================
|
|
186
|
+
;; Transaction Generation - Weighted Action Sampling
|
|
187
|
+
;; =============================================================================
|
|
188
|
+
|
|
189
|
+
(def action-weights
|
|
190
|
+
"Weights for each action type in transaction generation.
|
|
191
|
+
:add - Add entirely new random datom
|
|
192
|
+
:remove - Retract an existing datom
|
|
193
|
+
:tweak - Take existing datom, generate new value for same entity/attribute
|
|
194
|
+
:combine - Take two existing datoms, mix their entity/value parts
|
|
195
|
+
:transact - End current transaction (emit boundary)"
|
|
196
|
+
{:add 4
|
|
197
|
+
:remove 2
|
|
198
|
+
:tweak 1
|
|
199
|
+
:combine 1
|
|
200
|
+
:transact 1})
|
|
201
|
+
|
|
202
|
+
(defn- generate-value
|
|
203
|
+
"Generate a random value for the given attribute type."
|
|
204
|
+
[rng value-type entity-range]
|
|
205
|
+
(case value-type
|
|
206
|
+
:db.type/string (str "v" (rng/next-int rng 100))
|
|
207
|
+
:db.type/long (long (rng/next-int rng 100))
|
|
208
|
+
:db.type/ref (rng/rand-nth-rng rng entity-range)
|
|
209
|
+
:db.type/boolean (rng/next-boolean rng)
|
|
210
|
+
:db.type/keyword (keyword (str "k" (rng/next-int rng 20)))
|
|
211
|
+
(rng/next-long rng)))
|
|
212
|
+
|
|
213
|
+
(defn- generate-add
|
|
214
|
+
"Generate a random :db/add operation."
|
|
215
|
+
[rng schema entity-range]
|
|
216
|
+
(let [attr-idents (vec (keys schema))
|
|
217
|
+
attr-id (rng/rand-nth-rng rng attr-idents)
|
|
218
|
+
attr (get schema attr-id)
|
|
219
|
+
value-type (:db/valueType attr)
|
|
220
|
+
e (rng/rand-nth-rng rng entity-range)
|
|
221
|
+
v (generate-value rng value-type entity-range)]
|
|
222
|
+
[:db/add e attr-id v]))
|
|
223
|
+
|
|
224
|
+
(defn- generate-remove
|
|
225
|
+
"Generate a :db/retract operation from existing datoms."
|
|
226
|
+
[rng current-datoms]
|
|
227
|
+
(let [[e a v] (rng/rand-nth-rng rng (vec current-datoms))]
|
|
228
|
+
[:db/retract e a v]))
|
|
229
|
+
|
|
230
|
+
(defn- generate-tweak
|
|
231
|
+
"Generate a :db/add that modifies an existing datom with a new value.
|
|
232
|
+
Exercises cardinality-one replacement and upsert semantics."
|
|
233
|
+
[rng schema entity-range current-datoms]
|
|
234
|
+
(let [[e a _old-v] (rng/rand-nth-rng rng (vec current-datoms))
|
|
235
|
+
attr (get schema a)
|
|
236
|
+
value-type (:db/valueType attr)
|
|
237
|
+
new-v (generate-value rng value-type entity-range)]
|
|
238
|
+
[:db/add e a new-v]))
|
|
239
|
+
|
|
240
|
+
(defn- generate-combine
|
|
241
|
+
"Generate a :db/add by mixing entity or value from two datoms with same attribute.
|
|
242
|
+
Creates interesting patterns while preserving type safety.
|
|
243
|
+
Returns nil if no valid combination exists."
|
|
244
|
+
[rng current-datoms]
|
|
245
|
+
(when (seq current-datoms)
|
|
246
|
+
(let [by-attr (group-by second current-datoms)
|
|
247
|
+
valid-groups (filter #(<= 2 (count (second %))) by-attr)]
|
|
248
|
+
(when (seq valid-groups)
|
|
249
|
+
(let [[_ datoms-with-same-attr] (rng/rand-nth-rng rng (vec valid-groups))
|
|
250
|
+
[e1 a _v1] (rng/rand-nth-rng rng (vec datoms-with-same-attr))
|
|
251
|
+
[e2 _a v2] (rng/rand-nth-rng rng (vec datoms-with-same-attr))
|
|
252
|
+
idx (rng/next-int rng 2)
|
|
253
|
+
e (case idx 0 e1 1 e2)
|
|
254
|
+
v (case idx 0 v2 1 v2)]
|
|
255
|
+
[:db/add e a v])))))
|
|
256
|
+
|
|
257
|
+
(defn- sample-action
|
|
258
|
+
"Sample an action type based on weights and current state.
|
|
259
|
+
When no datoms exist, only :add or :transact are valid."
|
|
260
|
+
[rng current-datoms]
|
|
261
|
+
(if (seq current-datoms)
|
|
262
|
+
(rng/weighted-sample-rng rng action-weights)
|
|
263
|
+
(rng/weighted-sample-rng rng {:add 4 :transact 1})))
|
|
264
|
+
|
|
265
|
+
(defn- datom-equivalence
|
|
266
|
+
"Returns the equivalence key for a datom.
|
|
267
|
+
Two datoms are equivalent if they would conflict in the same transaction.
|
|
268
|
+
For cardinality-one: same [e a]
|
|
269
|
+
For cardinality-many: same [e a v]"
|
|
270
|
+
[schema [e a v]]
|
|
271
|
+
(let [attr (get schema a)
|
|
272
|
+
cardinality (or (:db/cardinality attr) :db.cardinality/one)]
|
|
273
|
+
(case cardinality
|
|
274
|
+
:db.cardinality/many [e a v]
|
|
275
|
+
:db.cardinality/one [e a])))
|
|
276
|
+
|
|
277
|
+
(defn- op-equivalence
|
|
278
|
+
"Returns the equivalence key for an operation [op e a v].
|
|
279
|
+
Ignores the op type - add and retract of same datom are equivalent."
|
|
280
|
+
[schema [_op e a v]]
|
|
281
|
+
(datom-equivalence schema [e a v]))
|
|
282
|
+
|
|
283
|
+
(defn generate-transaction
|
|
284
|
+
"Generate a single transaction using weighted action sampling.
|
|
285
|
+
|
|
286
|
+
Returns tx-ops: a vector of [op e a v] operations.
|
|
287
|
+
|
|
288
|
+
The transaction ends when:
|
|
289
|
+
- :transact action is sampled (probabilistic boundary)
|
|
290
|
+
- max-ops limit is reached
|
|
291
|
+
|
|
292
|
+
Uses an internal datom set to track state during generation, so operations
|
|
293
|
+
can reference datoms from previous operations within the same transaction.
|
|
294
|
+
Does NOT update model state - caller should apply the transaction.
|
|
295
|
+
|
|
296
|
+
Avoids generating conflicting operations within the same transaction
|
|
297
|
+
(e.g., add and retract of same datom, or two adds of same cardinality-one
|
|
298
|
+
attribute with same entity but different values)."
|
|
299
|
+
[rng schema entity-range model-state max-ops]
|
|
300
|
+
(let [initial-datoms (get-current-datoms model-state)]
|
|
301
|
+
(letfn [(accumulate [datoms [e a v added?]]
|
|
302
|
+
(if added?
|
|
303
|
+
(let [attr (lookup-attribute schema a)
|
|
304
|
+
cardinality (or (:db/cardinality attr) :db.cardinality/one)]
|
|
305
|
+
(case cardinality
|
|
306
|
+
:db.cardinality/many
|
|
307
|
+
(if (some (predicate-from-pattern [e a v]) datoms)
|
|
308
|
+
datoms
|
|
309
|
+
(conj datoms [e a v]))
|
|
310
|
+
:db.cardinality/one
|
|
311
|
+
(-> datoms
|
|
312
|
+
(remove-pattern [e a nil])
|
|
313
|
+
(conj [e a v]))))
|
|
314
|
+
(remove-pattern datoms [e a v])))
|
|
315
|
+
|
|
316
|
+
(apply-op-to-datoms [datoms [op e a v]]
|
|
317
|
+
(accumulate datoms [e a v (= op :db/add)]))
|
|
318
|
+
|
|
319
|
+
(equiv-key [op]
|
|
320
|
+
(op-equivalence schema op))
|
|
321
|
+
|
|
322
|
+
(step [ops current-datoms current-equiv n]
|
|
323
|
+
(if (>= n max-ops)
|
|
324
|
+
(vec ops)
|
|
325
|
+
(let [action (sample-action rng current-datoms)]
|
|
326
|
+
(case action
|
|
327
|
+
:transact
|
|
328
|
+
(vec ops)
|
|
329
|
+
|
|
330
|
+
:add
|
|
331
|
+
(let [op (generate-add rng schema entity-range)
|
|
332
|
+
equiv (equiv-key op)]
|
|
333
|
+
(if (contains? current-equiv equiv)
|
|
334
|
+
;; Skip conflicting op, try again with same n
|
|
335
|
+
(recur ops current-datoms current-equiv n)
|
|
336
|
+
;; Accept op
|
|
337
|
+
(let [current-datoms' (apply-op-to-datoms current-datoms op)]
|
|
338
|
+
(recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
|
|
339
|
+
|
|
340
|
+
:remove
|
|
341
|
+
(let [op (generate-remove rng current-datoms)
|
|
342
|
+
equiv (equiv-key op)]
|
|
343
|
+
(if (contains? current-equiv equiv)
|
|
344
|
+
(recur ops current-datoms current-equiv n)
|
|
345
|
+
(let [current-datoms' (apply-op-to-datoms current-datoms op)]
|
|
346
|
+
(recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
|
|
347
|
+
|
|
348
|
+
:tweak
|
|
349
|
+
(let [op (generate-tweak rng schema entity-range current-datoms)
|
|
350
|
+
equiv (equiv-key op)]
|
|
351
|
+
(if (contains? current-equiv equiv)
|
|
352
|
+
(recur ops current-datoms current-equiv n)
|
|
353
|
+
(let [current-datoms' (apply-op-to-datoms current-datoms op)]
|
|
354
|
+
(recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
|
|
355
|
+
|
|
356
|
+
:combine
|
|
357
|
+
(if-let [op (generate-combine rng current-datoms)]
|
|
358
|
+
(let [equiv (equiv-key op)]
|
|
359
|
+
(if (contains? current-equiv equiv)
|
|
360
|
+
(recur ops current-datoms current-equiv n)
|
|
361
|
+
(let [current-datoms' (apply-op-to-datoms current-datoms op)]
|
|
362
|
+
(recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
|
|
363
|
+
;; combine failed, skip this iteration
|
|
364
|
+
(recur ops current-datoms current-equiv (inc n)))))))]
|
|
365
|
+
(step [] initial-datoms #{} 0))))
|
|
366
|
+
|
|
367
|
+
(defn generate-tx-batch
|
|
368
|
+
"Generate a batch of transaction operations.
|
|
369
|
+
|
|
370
|
+
Deprecated: Use generate-transaction for weighted action sampling.
|
|
371
|
+
Kept for backward compatibility."
|
|
372
|
+
([rng schema entity-range max-ops]
|
|
373
|
+
(generate-tx-batch rng schema entity-range max-ops nil))
|
|
374
|
+
([rng schema entity-range max-ops model-state]
|
|
375
|
+
(let [n (inc (rng/next-int rng max-ops))]
|
|
376
|
+
(vec (repeatedly n #(generate-add rng schema entity-range))))))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
(ns datahike.test.model.invariant
|
|
2
|
+
"Invariant checking framework for datahike model-based testing."
|
|
3
|
+
(:require [clojure.set :as set]
|
|
4
|
+
[datahike.api :as d]
|
|
5
|
+
[datahike.datom :as dd]
|
|
6
|
+
[datahike.db.interface :as dbi]
|
|
7
|
+
[datahike.test.model.core :as model]))
|
|
8
|
+
|
|
9
|
+
(defprotocol PInvariant
|
|
10
|
+
(check [this model-state actual-db])
|
|
11
|
+
(invariant-name [_this]))
|
|
12
|
+
|
|
13
|
+
(defn check-all
|
|
14
|
+
"Check all invariants against model and actual DB."
|
|
15
|
+
[invariants model-state actual-db]
|
|
16
|
+
(let [violations (keep #(check % model-state actual-db) invariants)]
|
|
17
|
+
(if (empty? violations)
|
|
18
|
+
{:valid? true}
|
|
19
|
+
{:valid? false :violations (vec violations)})))
|
|
20
|
+
|
|
21
|
+
;; =============================================================================
|
|
22
|
+
;; Helper Functions
|
|
23
|
+
;; =============================================================================
|
|
24
|
+
|
|
25
|
+
(defn- eav-triplet-set
|
|
26
|
+
"Convert datoms to a set of [e a v] tuples, filtering to user-attrs.
|
|
27
|
+
|
|
28
|
+
Handles both :attribute-refs? true and false:
|
|
29
|
+
- When true, :a is entity ID, uses dbi/-ident-for to convert
|
|
30
|
+
- When false, :a is already a keyword ident"
|
|
31
|
+
[db user-attrs datoms]
|
|
32
|
+
(let [attr-refs? (:attribute-refs? (.-config db))]
|
|
33
|
+
(into #{}
|
|
34
|
+
(comp (filter (if attr-refs?
|
|
35
|
+
(comp user-attrs #(dbi/-ident-for db %) :a)
|
|
36
|
+
(comp user-attrs :a)))
|
|
37
|
+
(map (if attr-refs?
|
|
38
|
+
(juxt :e #(dbi/-ident-for db (:a %)) :v)
|
|
39
|
+
(juxt :e :a :v))))
|
|
40
|
+
datoms)))
|
|
41
|
+
|
|
42
|
+
;; =============================================================================
|
|
43
|
+
;; Index Sortedness Invariant
|
|
44
|
+
;; =============================================================================
|
|
45
|
+
|
|
46
|
+
(defrecord IndexSortedInvariant [index-type]
|
|
47
|
+
PInvariant
|
|
48
|
+
(check [_this _model-state actual-db]
|
|
49
|
+
(let [actual-datoms (into [] (d/datoms actual-db index-type))
|
|
50
|
+
cmp-fn (dd/index-type->cmp-quick index-type true)
|
|
51
|
+
pairs (partition 2 1 actual-datoms)]
|
|
52
|
+
(when-not (every?
|
|
53
|
+
(fn [[a b]] (not (pos? (cmp-fn a b))))
|
|
54
|
+
pairs)
|
|
55
|
+
{:violation :index-unsorted
|
|
56
|
+
:evidence {:index-type index-type
|
|
57
|
+
:sample (take 10 actual-datoms)}})))
|
|
58
|
+
(invariant-name [_this]
|
|
59
|
+
(str (name index-type) "-sorted")))
|
|
60
|
+
|
|
61
|
+
;; =============================================================================
|
|
62
|
+
;; Index Content Invariant
|
|
63
|
+
;; =============================================================================
|
|
64
|
+
|
|
65
|
+
(defrecord IndexContentInvariant [index-type user-attrs]
|
|
66
|
+
PInvariant
|
|
67
|
+
(check [_this model-state actual-db]
|
|
68
|
+
(let [actual-datoms (eav-triplet-set actual-db user-attrs (d/datoms actual-db index-type))
|
|
69
|
+
expected-datoms (case index-type
|
|
70
|
+
:eavt (set (model/compute-eavt model-state))
|
|
71
|
+
:aevt (set (model/compute-aevt model-state))
|
|
72
|
+
:avet (set (model/compute-avet model-state))
|
|
73
|
+
(throw (ex-info "Unknown index" {:index-type index-type})))
|
|
74
|
+
missing (set/difference expected-datoms actual-datoms)
|
|
75
|
+
extra (set/difference actual-datoms expected-datoms)]
|
|
76
|
+
(when (or (seq missing) (seq extra))
|
|
77
|
+
{:violation :index-content-mismatch
|
|
78
|
+
:evidence {:index-type index-type
|
|
79
|
+
:missing (vec (take 10 missing))
|
|
80
|
+
:extra (vec (take 10 extra))}})))
|
|
81
|
+
(invariant-name [_this]
|
|
82
|
+
(str (name index-type) "-content")))
|
|
83
|
+
|
|
84
|
+
;; =============================================================================
|
|
85
|
+
;; Historical Consistency Invariant
|
|
86
|
+
;; =============================================================================
|
|
87
|
+
|
|
88
|
+
(defrecord HistoricalConsistencyInvariant [actual-tx-ids tx-offset user-attrs]
|
|
89
|
+
PInvariant
|
|
90
|
+
(check [_this model-state actual-db]
|
|
91
|
+
(let [violations
|
|
92
|
+
(for [actual-tx actual-tx-ids
|
|
93
|
+
:let [model-tx (- actual-tx tx-offset)
|
|
94
|
+
expected (model/get-datoms-at-tx model-state model-tx)
|
|
95
|
+
actual (try
|
|
96
|
+
(eav-triplet-set (d/as-of actual-db actual-tx) user-attrs (d/datoms (d/as-of actual-db actual-tx) :eavt))
|
|
97
|
+
(catch Exception _ nil))
|
|
98
|
+
missing (when actual (set/difference expected actual))
|
|
99
|
+
extra (when actual (set/difference actual expected))]
|
|
100
|
+
:when (and actual (or (seq missing) (seq extra)))]
|
|
101
|
+
{:tx-id actual-tx
|
|
102
|
+
:model-tx model-tx
|
|
103
|
+
:missing (vec (take 5 missing))
|
|
104
|
+
:extra (vec (take 5 extra))})]
|
|
105
|
+
(when (seq violations)
|
|
106
|
+
{:violation :historical-mismatch
|
|
107
|
+
:evidence {:violations (vec violations)}})))
|
|
108
|
+
(invariant-name [_this]
|
|
109
|
+
"historical-consistency"))
|
|
110
|
+
|
|
111
|
+
;; =============================================================================
|
|
112
|
+
;; Constructor Functions
|
|
113
|
+
;; =============================================================================
|
|
114
|
+
|
|
115
|
+
(defn index-sorted
|
|
116
|
+
"Create invariant that checks an index is sorted."
|
|
117
|
+
[index-type]
|
|
118
|
+
(->IndexSortedInvariant index-type))
|
|
119
|
+
|
|
120
|
+
(defn index-content
|
|
121
|
+
"Create invariant that checks index content matches model."
|
|
122
|
+
[index-type user-attrs]
|
|
123
|
+
(->IndexContentInvariant index-type user-attrs))
|
|
124
|
+
|
|
125
|
+
(defn historical-consistency
|
|
126
|
+
"Create invariant that checks historical states match as-of queries.
|
|
127
|
+
|
|
128
|
+
actual-tx-ids: the actual transaction IDs in Datahike
|
|
129
|
+
tx-offset: the offset to convert actual tx-ids to model tx-ids
|
|
130
|
+
user-attrs: the set of user attributes to check"
|
|
131
|
+
[actual-tx-ids tx-offset user-attrs]
|
|
132
|
+
(->HistoricalConsistencyInvariant actual-tx-ids tx-offset user-attrs))
|
|
133
|
+
|
|
134
|
+
(defn all-index-invariants
|
|
135
|
+
"Create all standard index invariants (sortedness + content for EAVT, AVET, AEVT)."
|
|
136
|
+
[user-attrs]
|
|
137
|
+
[(index-sorted :eavt)
|
|
138
|
+
(index-sorted :avet)
|
|
139
|
+
(index-sorted :aevt)
|
|
140
|
+
(index-content :eavt user-attrs)
|
|
141
|
+
(index-content :avet user-attrs)
|
|
142
|
+
(index-content :aevt user-attrs)])
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
(ns datahike.test.model.rng
|
|
2
|
+
"Fork-safe random number generation using SplittableRandom."
|
|
3
|
+
#?(:clj (:import [java.util SplittableRandom])))
|
|
4
|
+
|
|
5
|
+
(defprotocol PRNG
|
|
6
|
+
(next-double [this])
|
|
7
|
+
(next-long [this])
|
|
8
|
+
(next-int [this bound])
|
|
9
|
+
(next-boolean [this])
|
|
10
|
+
(fork [this]))
|
|
11
|
+
|
|
12
|
+
#?(:clj
|
|
13
|
+
(defn- wrap-splittable-random
|
|
14
|
+
[rng]
|
|
15
|
+
(reify PRNG
|
|
16
|
+
(next-double [_] (.nextDouble rng))
|
|
17
|
+
(next-long [_] (.nextLong rng))
|
|
18
|
+
(next-int [_ bound] (.nextInt rng (int bound)))
|
|
19
|
+
(next-boolean [_] (.nextBoolean rng))
|
|
20
|
+
(fork [_] (wrap-splittable-random (.split rng))))))
|
|
21
|
+
|
|
22
|
+
#?(:clj
|
|
23
|
+
(defn create
|
|
24
|
+
[seed]
|
|
25
|
+
(wrap-splittable-random (SplittableRandom. (long seed)))))
|
|
26
|
+
|
|
27
|
+
#?(:cljs
|
|
28
|
+
(defn- step-state
|
|
29
|
+
"Linear congruential generator step function."
|
|
30
|
+
[s]
|
|
31
|
+
(mod (+ (* s 1103515245) 12345) (js/Math.pow 2 31))))
|
|
32
|
+
|
|
33
|
+
#?(:cljs
|
|
34
|
+
(defn create
|
|
35
|
+
[seed]
|
|
36
|
+
(let [state (atom (long seed))]
|
|
37
|
+
(reify PRNG
|
|
38
|
+
(next-double [_]
|
|
39
|
+
(/ (double (swap! state step-state)) (js/Math.pow 2 31)))
|
|
40
|
+
(next-long [_]
|
|
41
|
+
(swap! state step-state))
|
|
42
|
+
(next-int [_ bound]
|
|
43
|
+
(mod (swap! state step-state) bound))
|
|
44
|
+
(next-boolean [_]
|
|
45
|
+
(even? (swap! state step-state)))
|
|
46
|
+
(fork [_]
|
|
47
|
+
(create (swap! state step-state)))))))
|
|
48
|
+
|
|
49
|
+
(defn should-trigger?
|
|
50
|
+
[rng rate]
|
|
51
|
+
(< (next-double rng) rate))
|
|
52
|
+
|
|
53
|
+
(defn random-in-range
|
|
54
|
+
[rng min-val max-val]
|
|
55
|
+
(+ min-val (next-int rng (inc (- max-val min-val)))))
|
|
56
|
+
|
|
57
|
+
(defn rand-nth-rng
|
|
58
|
+
[rng coll]
|
|
59
|
+
(nth coll (next-int rng (count coll))))
|
|
60
|
+
|
|
61
|
+
(defn weighted-sample-rng
|
|
62
|
+
[rng pairs]
|
|
63
|
+
(let [sum (transduce (map second) + pairs)
|
|
64
|
+
at (* sum (next-double rng))]
|
|
65
|
+
(loop [at at
|
|
66
|
+
[[k w] & rest] pairs]
|
|
67
|
+
(if (<= at w)
|
|
68
|
+
k
|
|
69
|
+
(recur (- at w) rest)))))
|
|
70
|
+
|
|
71
|
+
(defn shuffle-rng
|
|
72
|
+
[rng coll]
|
|
73
|
+
(let [v (vec coll)
|
|
74
|
+
n (count v)]
|
|
75
|
+
(loop [i (dec n)
|
|
76
|
+
v v]
|
|
77
|
+
(if (pos? i)
|
|
78
|
+
(let [j (next-int rng (inc i))
|
|
79
|
+
vi (v i)
|
|
80
|
+
vj (v j)]
|
|
81
|
+
(recur (dec i) (assoc v i vj j vi)))
|
|
82
|
+
v))))
|