diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f3453de --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Install pinned Rust toolchain + run: rustup toolchain install 1.98.0 --profile minimal --component rustfmt --component clippy --target wasm32-unknown-unknown + - run: cargo fmt --all --check + - run: cargo clippy --workspace --all-targets -- -D warnings + - run: cargo test --workspace + - run: cargo check -p moddotplot-wasm --target wasm32-unknown-unknown + + web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Install pinned Rust toolchain + run: rustup toolchain install 1.98.0 --profile minimal --target wasm32-unknown-unknown + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .node-version + cache: npm + cache-dependency-path: web/package-lock.json + - name: Install pinned wasm-pack + run: cargo install wasm-pack --version 0.15.0 --locked + - run: npm ci + working-directory: web + - run: npm run build:release + working-directory: web + - run: git diff --exit-code -- SBOM.cdx.json THIRD_PARTY_LICENSES.txt + - run: npx playwright install --with-deps chromium firefox webkit + working-directory: web + - name: Run browser end-to-end tests + run: xvfb-run --auto-servernum npm run test:e2e + working-directory: web diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..be434c5 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,61 @@ +name: Deploy GitHub Pages + +on: + push: + branches: + - develop + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: github-pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Install pinned Rust toolchain + run: rustup toolchain install 1.98.0 --profile minimal --target wasm32-unknown-unknown + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .node-version + cache: npm + cache-dependency-path: web/package-lock.json + - name: Install pinned wasm-pack + run: cargo install wasm-pack --version 0.15.0 --locked + - id: pages + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + with: + enablement: true + - run: npm ci + working-directory: web + - run: npm test + working-directory: web + - run: npm run build:wasm + working-directory: web + - run: npm run build:ui + working-directory: web + env: + MODDOTPLOT_BASE_PATH: /${{ github.event.repository.name }}/ + - run: npm run docs:check + working-directory: web + - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + with: + path: web/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..edca00c --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/target/ +/node_modules/ +/web/dist/ +/web/node_modules/ +/web/public/examples/Col-CEN_v1.2.fasta +/web/src/wasm/ +/web/playwright-report/ +/web/test-results/ +/release-artifacts/ +*.log +.DS_Store +.idea/ +.vscode/ diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..e362e4c --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +25.9.0 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..279258f --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,225 @@ +cff-version: 1.2.0 +message: >- + If you use ModDotPlot Browser, please cite the original ModDotPlot paper listed as the + preferred citation and credit the relevant method papers listed in references. +title: "ModDotPlot Browser" +type: software +version: 1.0.0 +date-released: 2026-10-04 +repository-code: "https://github.com/marbl/ModDotPlot-Browser" +license: MIT-0 +abstract: >- + Browser-native genomic dotplot software written by ChatGPT 5.6 Sol under the + direction of Adam Phillippy. +authors: + - name: "ChatGPT 5.6 Sol" + - family-names: "Phillippy" + given-names: "Adam M." + +preferred-citation: + type: article + title: "ModDotPlot-rapid and interactive visualization of tandem repeats" + authors: + - family-names: "Sweeten" + given-names: "Alexander P." + - family-names: "Schatz" + given-names: "Michael C." + - family-names: "Phillippy" + given-names: "Adam M." + journal: "Bioinformatics" + year: 2024 + month: 8 + volume: 40 + issue: 8 + pages: "btae493" + doi: "10.1093/bioinformatics/btae493" + url: "https://pubmed.ncbi.nlm.nih.gov/39110522/" + notes: "PMID: 39110522; PMCID: PMC11321072." + +references: + - type: article + title: "Mash: fast genome and metagenome distance estimation using MinHash" + authors: + - family-names: "Ondov" + given-names: "Brian D." + - family-names: "Treangen" + given-names: "Todd J." + - family-names: "Melsted" + given-names: "Páll" + - family-names: "Mallonee" + given-names: "Adam B." + - family-names: "Bergman" + given-names: "Nicholas H." + - family-names: "Koren" + given-names: "Sergey" + - family-names: "Phillippy" + given-names: "Adam M." + journal: "Genome Biology" + year: 2016 + volume: 17 + issue: 1 + pages: "132" + doi: "10.1186/s13059-016-0997-x" + url: "https://pubmed.ncbi.nlm.nih.gov/27323842/" + + - type: article + title: "A fast approximate algorithm for mapping long reads to large reference databases" + authors: + - family-names: "Jain" + given-names: "Chirag" + - family-names: "Dilthey" + given-names: "Alexander" + - family-names: "Koren" + given-names: "Sergey" + - family-names: "Aluru" + given-names: "Srinivas" + - family-names: "Phillippy" + given-names: "Adam M." + journal: "Journal of Computational Biology" + year: 2018 + volume: 25 + issue: 7 + pages: "766-779" + doi: "10.1089/cmb.2018.0036" + url: "https://pubmed.ncbi.nlm.nih.gov/29708767/" + + - type: article + title: "Minmers are a generalization of minimizers that enable unbiased local Jaccard estimation" + authors: + - family-names: "Kille" + given-names: "Bryce" + - family-names: "Garrison" + given-names: "Erik" + - family-names: "Treangen" + given-names: "Todd J." + - family-names: "Phillippy" + given-names: "Adam M." + journal: "Bioinformatics" + year: 2023 + volume: 39 + issue: 9 + pages: "btad512" + doi: "10.1093/bioinformatics/btad512" + url: "https://academic.oup.com/bioinformatics/article/39/9/btad512/7246743" + + - type: article + title: "ntHash2: recursive spaced seed hashing for nucleotide sequences" + authors: + - family-names: "Kazemi" + given-names: "Parham" + - family-names: "Wong" + given-names: "Johnathan" + - family-names: "Nikolić" + given-names: "Vladimir" + - family-names: "Mohamadi" + given-names: "Hamid" + - family-names: "Warren" + given-names: "René L." + - family-names: "Birol" + given-names: "Inanç" + journal: "Bioinformatics" + year: 2022 + volume: 38 + issue: 20 + pages: "4812-4813" + doi: "10.1093/bioinformatics/btac564" + url: "https://academic.oup.com/bioinformatics/article/38/20/4812/6674501" + + - type: conference-paper + title: "b-Bit minwise hashing" + authors: + - family-names: "Li" + given-names: "Ping" + - family-names: "König" + given-names: "Arnd Christian" + collection-title: "Proceedings of the 19th International Conference on World Wide Web" + year: 2010 + pages: "671-680" + doi: "10.1145/1772690.1772759" + url: "https://doi.org/10.1145/1772690.1772759" + + - type: conference-paper + title: "One permutation hashing" + authors: + - family-names: "Li" + given-names: "Ping" + - family-names: "Owen" + given-names: "Art B." + - family-names: "Zhang" + given-names: "Cun-Hui" + collection-title: "Advances in Neural Information Processing Systems 25" + year: 2012 + pages: "3122-3130" + url: "https://papers.nips.cc/paper_files/paper/2012/hash/eaa32c96f620053cf442ad32258076b9-Abstract.html" + + - type: conference-paper + title: "HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm" + authors: + - family-names: "Flajolet" + given-names: "Philippe" + - family-names: "Fusy" + given-names: "Éric" + - family-names: "Gandouet" + given-names: "Olivier" + - family-names: "Meunier" + given-names: "Frédéric" + collection-title: "Discrete Mathematics and Theoretical Computer Science Proceedings, Analysis of Algorithms 2007" + year: 2007 + volume: "AH" + pages: "127-146" + url: "https://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf" + + - type: conference-paper + title: "On the resemblance and containment of documents" + authors: + - family-names: "Broder" + given-names: "Andrei Z." + collection-title: "Compression and Complexity of Sequences 1997" + year: 1997 + pages: "21-29" + doi: "10.1109/SEQUEN.1997.666900" + url: "https://doi.org/10.1109/SEQUEN.1997.666900" + + - type: software + title: "ModDotPlot" + authors: + - family-names: "Sweeten" + given-names: "Alexander P." + - family-names: "Schatz" + given-names: "Michael C." + - family-names: "Phillippy" + given-names: "Adam M." + repository-code: "https://github.com/marbl/moddotplot" + + - type: software + title: "MashMap" + authors: + - name: "MashMap contributors" + repository-code: "https://github.com/marbl/MashMap" + commit: "1a07d0e" + + - type: software + title: "ntHash" + authors: + - name: "BCGSC ntHash contributors" + repository-code: "https://github.com/bcgsc/ntHash" + commit: "c26bd4572a19de81e30d55042dbd33c1fd21d4b6" + + - type: software + title: "SMHasher and the MurmurHash3 reference implementation" + authors: + - family-names: "Appleby" + given-names: "Austin" + url: "https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp" + + - type: software + title: "ColorBrewer 2" + version: "2.0" + authors: + - family-names: "Brewer" + given-names: "Cynthia" + - family-names: "Harrower" + given-names: "Mark" + - name: "The Pennsylvania State University" + repository-code: "https://github.com/axismaps/colorbrewer" + url: "https://colorbrewer2.org/" diff --git a/COLORBREWER_LICENSE.txt b/COLORBREWER_LICENSE.txt new file mode 100644 index 0000000..e3a0611 --- /dev/null +++ b/COLORBREWER_LICENSE.txt @@ -0,0 +1,182 @@ +ColorBrewer 2 color schemes + +Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. + +Licensed under the Apache License, Version 2.0. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e0a76ab --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,231 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "moddotplot-core" +version = "1.0.0" + +[[package]] +name = "moddotplot-wasm" +version = "1.0.0" +dependencies = [ + "console_error_panic_hook", + "moddotplot-core", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5a3961e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[workspace] +members = ["crates/core", "crates/wasm"] +resolver = "2" + +[workspace.package] +version = "1.0.0" +edition = "2024" +license = "MIT-0" +repository = "https://github.com/marbl/ModDotPlot-Browser" + +[workspace.lints.rust] +missing_docs = "warn" +unsafe_code = "forbid" + +[workspace.lints.clippy] +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +module_name_repetitions = "allow" +must_use_candidate = "allow" diff --git a/D3_SCALE_CHROMATIC_LICENSE.txt b/D3_SCALE_CHROMATIC_LICENSE.txt new file mode 100644 index 0000000..088e458 --- /dev/null +++ b/D3_SCALE_CHROMATIC_LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2010-2024 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/LICENSE b/LICENSE index cf91d2b..0690d5e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,16 @@ -MIT License +MIT No Attribution -Copyright (c) 2026 MarBL +Copyright 2026 moddotplot-interactive contributors -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index fbe91de..b659d5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ -# ModDotPlot-Browser -Interactive ModDotPlot on the web browser +![Animated demonstration of ModDotPlot Browser](web/public/moddotplot-browser.gif) +# ModDotPlot Browser + +`ModDotPlot Browser` is a web-based, interactive genomic dotplot viewer designed to visualize entire chromosomes in *seconds*. Input data remains local to your web browser and is not stored in any database or server. + +**Live application:** [marbl.github.io/ModDotPlot-Browser](https://marbl.github.io/ModDotPlot-Browser/) + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. It is intended to be used for exploratory purposes only. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +## Documentation + +* [Tutorial/User Guide](docs/USER_GUIDE.md) +* [Local/HPC Cluster Deployment](docs/DEPLOYMENT.md) +* [Methods](docs/METHODS.md) +* [Privacy & Data Handling](docs/PRIVACY.md) +* [Citation and method credits](docs/CITATIONS.md) +* [Release checklist](docs/RELEASE_CHECKLIST.md) + +## Citation, authorship, and license + +Users of `ModDotPlot Browser` are requested to cite the original ModDotPlot paper: Sweeten AP, Schatz MC, +Phillippy AM. *ModDotPlot-rapid and interactive visualization of tandem repeats*. +Bioinformatics. 2024;40(8):btae493. +[doi:10.1093/bioinformatics/btae493](https://doi.org/10.1093/bioinformatics/btae493). + +Structured software and method credits are in [CITATION.cff](CITATION.cff). + +`ModDotPlot Browser` is released under [MIT-0](LICENSE). diff --git a/SBOM.cdx.json b/SBOM.cdx.json new file mode 100644 index 0000000..e844b46 --- /dev/null +++ b/SBOM.cdx.json @@ -0,0 +1,2923 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "version": 1, + "metadata": { + "component": { + "type": "application", + "name": "moddotplot-interactive", + "version": "1.0.0", + "licenses": [ + { + "license": { + "id": "MIT-0" + } + } + ], + "properties": [ + { + "name": "moddotplot-interactive:dependency-fingerprint", + "value": "da90bf877f791aef3e9295ef82e1f735443aaf84c9a337d92f9e44d8fc44b893" + }, + { + "name": "moddotplot-interactive:generator", + "value": "scripts/generate-sbom.mjs" + } + ] + } + }, + "components": [ + { + "type": "library", + "name": "bumpalo", + "version": "3.20.3", + "purl": "pkg:cargo/bumpalo@3.20.3", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "cfg-if", + "version": "1.0.4", + "purl": "pkg:cargo/cfg-if@1.0.4", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "console_error_panic_hook", + "version": "0.1.7", + "purl": "pkg:cargo/console_error_panic_hook@0.1.7", + "licenses": [ + { + "expression": "Apache-2.0/MIT" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "futures-core", + "version": "0.3.34", + "purl": "pkg:cargo/futures-core@0.3.34", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "futures-task", + "version": "0.3.34", + "purl": "pkg:cargo/futures-task@0.3.34", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "futures-util", + "version": "0.3.34", + "purl": "pkg:cargo/futures-util@0.3.34", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "js-sys", + "version": "0.3.104", + "purl": "pkg:cargo/js-sys@0.3.104", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "once_cell", + "version": "1.21.4", + "purl": "pkg:cargo/once_cell@1.21.4", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "pin-project-lite", + "version": "0.2.17", + "purl": "pkg:cargo/pin-project-lite@0.2.17", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "proc-macro2", + "version": "1.0.107", + "purl": "pkg:cargo/proc-macro2@1.0.107", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "quote", + "version": "1.0.47", + "purl": "pkg:cargo/quote@1.0.47", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "rustversion", + "version": "1.0.23", + "purl": "pkg:cargo/rustversion@1.0.23", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "serde_core", + "version": "1.0.229", + "purl": "pkg:cargo/serde_core@1.0.229", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "serde_derive", + "version": "1.0.229", + "purl": "pkg:cargo/serde_derive@1.0.229", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "serde-wasm-bindgen", + "version": "0.6.5", + "purl": "pkg:cargo/serde-wasm-bindgen@0.6.5", + "licenses": [ + { + "expression": "MIT" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "serde", + "version": "1.0.229", + "purl": "pkg:cargo/serde@1.0.229", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "slab", + "version": "0.4.12", + "purl": "pkg:cargo/slab@0.4.12", + "licenses": [ + { + "expression": "MIT" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "syn", + "version": "2.0.119", + "purl": "pkg:cargo/syn@2.0.119", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "syn", + "version": "3.0.4", + "purl": "pkg:cargo/syn@3.0.4", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "unicode-ident", + "version": "1.0.24", + "purl": "pkg:cargo/unicode-ident@1.0.24", + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-3.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "wasm-bindgen-macro-support", + "version": "0.2.127", + "purl": "pkg:cargo/wasm-bindgen-macro-support@0.2.127", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "wasm-bindgen-macro", + "version": "0.2.127", + "purl": "pkg:cargo/wasm-bindgen-macro@0.2.127", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "wasm-bindgen-shared", + "version": "0.2.127", + "purl": "pkg:cargo/wasm-bindgen-shared@0.2.127", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "wasm-bindgen", + "version": "0.2.127", + "purl": "pkg:cargo/wasm-bindgen@0.2.127", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "registry+https://github.com/rust-lang/crates.io-index" + } + ] + }, + { + "type": "library", + "name": "ColorBrewer color schemes", + "version": "2.0", + "purl": "pkg:generic/colorbrewer@2.0", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://colorbrewer2.org/" + }, + { + "type": "vcs", + "url": "https://github.com/axismaps/colorbrewer" + } + ] + }, + { + "type": "library", + "name": "@esbuild/aix-ppc64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/aix-ppc64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/android-arm", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/android-arm@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/android-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/android-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/android-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/android-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/darwin-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/darwin-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/darwin-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/darwin-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/freebsd-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/freebsd-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/freebsd-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/freebsd-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-arm", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-arm@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-ia32", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-ia32@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-loong64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-loong64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-mips64el", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-mips64el@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-ppc64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-ppc64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-riscv64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-riscv64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-s390x", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-s390x@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/linux-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/linux-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/netbsd-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/netbsd-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/netbsd-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/netbsd-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/openbsd-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/openbsd-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/openbsd-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/openbsd-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/openharmony-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/openharmony-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/sunos-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/sunos-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/win32-arm64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/win32-arm64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/win32-ia32", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/win32-ia32@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@esbuild/win32-x64", + "version": "0.28.2", + "purl": "pkg:npm/%40esbuild/win32-x64@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@jridgewell/sourcemap-codec", + "version": "1.6.0", + "purl": "pkg:npm/%40jridgewell/sourcemap-codec@1.6.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz" + } + ] + }, + { + "type": "library", + "name": "@napi-rs/lzma-linux-x64-gnu", + "version": "1.5.1", + "purl": "pkg:npm/%40napi-rs/lzma-linux-x64-gnu@1.5.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@playwright/test", + "version": "1.62.1", + "purl": "pkg:npm/%40playwright/test@1.62.1", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-android-arm-eabi", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-android-arm-eabi@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-android-arm64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-android-arm64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-darwin-arm64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-darwin-arm64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-darwin-x64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-darwin-x64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-freebsd-arm64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-freebsd-arm64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-freebsd-x64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-freebsd-x64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-arm-gnueabihf", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-arm-gnueabihf@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-arm-musleabihf", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-arm-musleabihf@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-arm64-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-arm64-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-arm64-musl", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-arm64-musl@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-loong64-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-loong64-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-loong64-musl", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-loong64-musl@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-ppc64-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-ppc64-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-ppc64-musl", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-ppc64-musl@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-riscv64-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-riscv64-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-riscv64-musl", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-riscv64-musl@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-s390x-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-s390x-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-x64-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-x64-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-linux-x64-musl", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-linux-x64-musl@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-openbsd-x64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-openbsd-x64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-openharmony-arm64", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-openharmony-arm64@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-win32-arm64-msvc", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-win32-arm64-msvc@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-win32-ia32-msvc", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-win32-ia32-msvc@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-win32-x64-gnu", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-win32-x64-gnu@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@rollup/rollup-win32-x64-msvc", + "version": "4.63.1", + "purl": "pkg:npm/%40rollup/rollup-win32-x64-msvc@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "@types/chai", + "version": "5.2.3", + "purl": "pkg:npm/%40types/chai@5.2.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz" + } + ] + }, + { + "type": "library", + "name": "@types/deep-eql", + "version": "4.0.2", + "purl": "pkg:npm/%40types/deep-eql@4.0.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz" + } + ] + }, + { + "type": "library", + "name": "@types/estree", + "version": "1.0.9", + "purl": "pkg:npm/%40types/estree@1.0.9", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/expect", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/expect@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/mocker", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/mocker@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/pretty-format", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/pretty-format@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/runner", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/runner@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/snapshot", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/snapshot@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/spy", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/spy@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "@vitest/utils", + "version": "3.2.7", + "purl": "pkg:npm/%40vitest/utils@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "assertion-error", + "version": "2.0.1", + "purl": "pkg:npm/assertion-error@2.0.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz" + } + ] + }, + { + "type": "library", + "name": "cac", + "version": "6.7.14", + "purl": "pkg:npm/cac@6.7.14", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" + } + ] + }, + { + "type": "library", + "name": "chai", + "version": "5.3.3", + "purl": "pkg:npm/chai@5.3.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz" + } + ] + }, + { + "type": "library", + "name": "check-error", + "version": "2.1.3", + "purl": "pkg:npm/check-error@2.1.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz" + } + ] + }, + { + "type": "library", + "name": "d3-scale-chromatic", + "version": "3.1.0", + "purl": "pkg:npm/d3-scale-chromatic@3.1.0", + "licenses": [ + { + "license": { + "id": "ISC" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://d3js.org/d3-scale-chromatic/" + }, + { + "type": "vcs", + "url": "https://github.com/d3/d3-scale-chromatic" + } + ] + }, + { + "type": "library", + "name": "debug", + "version": "4.4.3", + "purl": "pkg:npm/debug@4.4.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + } + ] + }, + { + "type": "library", + "name": "deep-eql", + "version": "5.0.2", + "purl": "pkg:npm/deep-eql@5.0.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz" + } + ] + }, + { + "type": "library", + "name": "es-module-lexer", + "version": "1.7.0", + "purl": "pkg:npm/es-module-lexer@1.7.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" + } + ] + }, + { + "type": "library", + "name": "esbuild", + "version": "0.28.2", + "purl": "pkg:npm/esbuild@0.28.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz" + } + ] + }, + { + "type": "library", + "name": "estree-walker", + "version": "3.0.3", + "purl": "pkg:npm/estree-walker@3.0.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz" + } + ] + }, + { + "type": "library", + "name": "expect-type", + "version": "1.4.0", + "purl": "pkg:npm/expect-type@1.4.0", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz" + } + ] + }, + { + "type": "library", + "name": "fdir", + "version": "6.5.0", + "purl": "pkg:npm/fdir@6.5.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + } + ] + }, + { + "type": "library", + "name": "fsevents", + "version": "2.3.3", + "purl": "pkg:npm/fsevents@2.3.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + } + ] + }, + { + "type": "library", + "name": "js-tokens", + "version": "9.0.1", + "purl": "pkg:npm/js-tokens@9.0.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz" + } + ] + }, + { + "type": "library", + "name": "loupe", + "version": "3.2.1", + "purl": "pkg:npm/loupe@3.2.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz" + } + ] + }, + { + "type": "library", + "name": "magic-string", + "version": "0.30.21", + "purl": "pkg:npm/magic-string@0.30.21", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" + } + ] + }, + { + "type": "library", + "name": "ms", + "version": "2.1.3", + "purl": "pkg:npm/ms@2.1.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + } + ] + }, + { + "type": "library", + "name": "nanoid", + "version": "3.3.18", + "purl": "pkg:npm/nanoid@3.3.18", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz" + } + ] + }, + { + "type": "library", + "name": "pathe", + "version": "2.0.3", + "purl": "pkg:npm/pathe@2.0.3", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz" + } + ] + }, + { + "type": "library", + "name": "pathval", + "version": "2.0.1", + "purl": "pkg:npm/pathval@2.0.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz" + } + ] + }, + { + "type": "library", + "name": "picocolors", + "version": "1.1.1", + "purl": "pkg:npm/picocolors@1.1.1", + "licenses": [ + { + "expression": "ISC" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + } + ] + }, + { + "type": "library", + "name": "picomatch", + "version": "4.0.7", + "purl": "pkg:npm/picomatch@4.0.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz" + } + ] + }, + { + "type": "library", + "name": "playwright-core", + "version": "1.62.1", + "purl": "pkg:npm/playwright-core@1.62.1", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz" + } + ] + }, + { + "type": "library", + "name": "playwright", + "version": "1.62.1", + "purl": "pkg:npm/playwright@1.62.1", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz" + } + ] + }, + { + "type": "library", + "name": "playwright/node_modules/fsevents", + "version": "2.3.2", + "purl": "pkg:npm/playwright%2Fnode_modules%2Ffsevents@2.3.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + } + ] + }, + { + "type": "library", + "name": "postcss", + "version": "8.5.26", + "purl": "pkg:npm/postcss@8.5.26", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz" + } + ] + }, + { + "type": "library", + "name": "rollup", + "version": "4.63.1", + "purl": "pkg:npm/rollup@4.63.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz" + } + ] + }, + { + "type": "library", + "name": "siginfo", + "version": "2.0.0", + "purl": "pkg:npm/siginfo@2.0.0", + "licenses": [ + { + "expression": "ISC" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz" + } + ] + }, + { + "type": "library", + "name": "source-map-js", + "version": "1.2.1", + "purl": "pkg:npm/source-map-js@1.2.1", + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + } + ] + }, + { + "type": "library", + "name": "stackback", + "version": "0.0.2", + "purl": "pkg:npm/stackback@0.0.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz" + } + ] + }, + { + "type": "library", + "name": "std-env", + "version": "3.10.0", + "purl": "pkg:npm/std-env@3.10.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz" + } + ] + }, + { + "type": "library", + "name": "strip-literal", + "version": "3.1.0", + "purl": "pkg:npm/strip-literal@3.1.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz" + } + ] + }, + { + "type": "library", + "name": "tinybench", + "version": "2.9.0", + "purl": "pkg:npm/tinybench@2.9.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz" + } + ] + }, + { + "type": "library", + "name": "tinyexec", + "version": "0.3.2", + "purl": "pkg:npm/tinyexec@0.3.2", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz" + } + ] + }, + { + "type": "library", + "name": "tinyglobby", + "version": "0.2.17", + "purl": "pkg:npm/tinyglobby@0.2.17", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz" + } + ] + }, + { + "type": "library", + "name": "tinypool", + "version": "1.1.1", + "purl": "pkg:npm/tinypool@1.1.1", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz" + } + ] + }, + { + "type": "library", + "name": "tinyrainbow", + "version": "2.0.0", + "purl": "pkg:npm/tinyrainbow@2.0.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz" + } + ] + }, + { + "type": "library", + "name": "tinyspy", + "version": "4.0.4", + "purl": "pkg:npm/tinyspy@4.0.4", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz" + } + ] + }, + { + "type": "library", + "name": "typescript", + "version": "5.9.3", + "purl": "pkg:npm/typescript@5.9.3", + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + } + ] + }, + { + "type": "library", + "name": "vite-node", + "version": "3.2.4", + "purl": "pkg:npm/vite-node@3.2.4", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz" + } + ] + }, + { + "type": "library", + "name": "vite", + "version": "7.3.6", + "purl": "pkg:npm/vite@7.3.6", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz" + } + ] + }, + { + "type": "library", + "name": "vitest", + "version": "3.2.7", + "purl": "pkg:npm/vitest@3.2.7", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz" + } + ] + }, + { + "type": "library", + "name": "why-is-node-running", + "version": "2.3.0", + "purl": "pkg:npm/why-is-node-running@2.3.0", + "licenses": [ + { + "expression": "MIT" + } + ], + "hashes": [ + { + "alg": "SHA-512", + "content": "hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz" + } + ] + } + ] +} diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt new file mode 100644 index 0000000..4ee3746 --- /dev/null +++ b/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,5596 @@ +===== @jridgewell/sourcemap-codec 1.6.0 :: LICENSE ===== +Copyright 2024 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @playwright/test 1.62.1 :: LICENSE ===== +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +===== @playwright/test 1.62.1 :: NOTICE ===== +Playwright +Copyright (c) Microsoft Corporation + +This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer), +available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE). + +===== @types/chai 5.2.3 :: LICENSE ===== +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +===== @types/deep-eql 4.0.2 :: LICENSE ===== +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +===== @types/estree 1.0.9 :: LICENSE ===== +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +===== @vitest/expect 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @vitest/mocker 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @vitest/pretty-format 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @vitest/runner 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @vitest/snapshot 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @vitest/spy 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== @vitest/utils 3.2.7 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== ColorBrewer 2 :: Apache-2.0 ===== +ColorBrewer 2 color schemes + +Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. + +Licensed under the Apache License, Version 2.0. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +===== assertion-error 2.0.1 :: LICENSE ===== +MIT License + +Copyright (c) 2013 Jake Luer jake@qualiancy.com (http://qualiancy.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== cac 6.7.14 :: LICENSE ===== +The MIT License (MIT) + +Copyright (c) EGOIST <0x142857@gmail.com> (https://github.com/egoist) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +===== chai 5.3.3 :: LICENSE ===== +MIT License + +Copyright (c) 2017 Chai.js Assertion Library + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== check-error 2.1.3 :: LICENSE ===== +Copyright (c) 2013 Jake Luer (http://alogicalparadox.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +===== d3-scale-chromatic 3.1.0 :: LICENSE ===== +Copyright 2010-2024 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +===== debug 4.4.3 :: LICENSE ===== +(The MIT License) + +Copyright (c) 2014-2017 TJ Holowaychuk +Copyright (c) 2018-2021 Josh Junon + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the 'Software'), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== deep-eql 5.0.2 :: LICENSE ===== +Copyright (c) 2013 Jake Luer (http://alogicalparadox.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +===== es-module-lexer 1.7.0 :: LICENSE ===== +MIT License +----------- + +Copyright (C) 2018-2022 Guy Bedford + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== esbuild 0.28.2 :: LICENSE.md ===== +MIT License + +Copyright (c) 2020 Evan Wallace + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== estree-walker 3.0.3 :: LICENSE ===== +Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== expect-type 1.4.0 :: LICENSE ===== +Copyright 2024 Misha Kaletsky + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +===== fdir 6.5.0 :: LICENSE ===== +Copyright 2023 Abdullah Atta + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== js-tokens 9.0.1 :: LICENSE ===== +The MIT License (MIT) + +Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024 Simon Lydell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +===== loupe 3.2.1 :: LICENSE ===== +(The MIT License) + +Copyright (c) 2011-2013 Jake Luer jake@alogicalparadox.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== magic-string 0.30.21 :: LICENSE ===== +Copyright 2018 Rich Harris + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== ms 2.1.3 :: license.md ===== +The MIT License (MIT) + +Copyright (c) 2020 Vercel, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== nanoid 3.3.18 :: LICENSE ===== +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== pathe 2.0.3 :: LICENSE ===== +MIT License + +Copyright (c) Pooya Parsa - Daniel Roe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +Copyright Joyent, Inc. and other Node contributors. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + +Bundled zeptomatch (https://github.com/fabiospampinato/zeptomatch) + +The MIT License (MIT) + +Copyright (c) 2023-present Fabio Spampinato + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +===== pathval 2.0.1 :: LICENSE ===== +MIT License + +Copyright (c) 2011-2013 Jake Luer jake@alogicalparadox.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== picocolors 1.1.1 :: LICENSE ===== +ISC License + +Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +===== picomatch 4.0.7 :: LICENSE ===== +The MIT License (MIT) + +Copyright (c) 2017-present, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +===== playwright 1.62.1 :: LICENSE ===== +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +===== playwright 1.62.1 :: NOTICE ===== +Playwright +Copyright (c) Microsoft Corporation + +This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer), +available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE). + +===== playwright-core 1.62.1 :: LICENSE ===== +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +===== playwright-core 1.62.1 :: NOTICE ===== +Playwright +Copyright (c) Microsoft Corporation + +This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer), +available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE). + +===== postcss 8.5.26 :: LICENSE ===== +The MIT License (MIT) + +Copyright 2013 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== rollup 4.63.1 :: LICENSE.md ===== +# Rollup core license +Rollup is released under the MIT license: + +The MIT License (MIT) + +Copyright (c) 2017 [these people](https://github.com/rollup/rollup/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Licenses of bundled dependencies +The published Rollup artifact additionally contains code with the following licenses: +MIT, ISC, 0BSD + +# Bundled dependencies: +## @jridgewell/sourcemap-codec +License: MIT +By: Justin Ridgewell +Repository: git+https://github.com/jridgewell/sourcemaps.git + +> Copyright 2024 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @rollup/pluginutils +License: MIT +By: Rich Harris +Repository: rollup/plugins + +> The MIT License (MIT) +> +> Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## anymatch +License: ISC +By: Elan Shanker +Repository: https://github.com/micromatch/anymatch + +> The ISC License +> +> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com) +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## binary-extensions +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/binary-extensions + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## braces +License: MIT +By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm +Repository: micromatch/braces + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## builtin-modules +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/builtin-modules + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## chokidar +License: MIT +By: Paul Miller, Elan Shanker +Repository: git+https://github.com/paulmillr/chokidar.git + +> The MIT License (MIT) +> +> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the “Software”), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## date-time +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/date-time + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## fill-range +License: MIT +By: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling +Repository: jonschlinkert/fill-range + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## flru +License: MIT +By: Luke Edwards +Repository: lukeed/flru + +> MIT License +> +> Copyright (c) Luke Edwards (lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## glob-parent +License: ISC +By: Gulp Team, Elan Shanker, Blaine Bublitz +Repository: gulpjs/glob-parent + +> The ISC License +> +> Copyright (c) 2015, 2019 Elan Shanker +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## is-binary-path +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/is-binary-path + +> MIT License +> +> Copyright (c) 2019 Sindre Sorhus (https://sindresorhus.com), Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## is-extglob +License: MIT +By: Jon Schlinkert +Repository: jonschlinkert/is-extglob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-glob +License: MIT +By: Jon Schlinkert, Brian Woodward, Daniel Perez +Repository: micromatch/is-glob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2017, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-number +License: MIT +By: Jon Schlinkert, Olsten Larck, Rouven Weßling +Repository: jonschlinkert/is-number + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-reference +License: MIT +By: Rich Harris +Repository: git+https://github.com/Rich-Harris/is-reference.git + +--------------------------------------- + +## locate-character +License: MIT +By: Rich Harris +Repository: git+https://gitlab.com/Rich-Harris/locate-character.git + +--------------------------------------- + +## magic-string +License: MIT +By: Rich Harris +Repository: git+https://github.com/Rich-Harris/magic-string.git + +> Copyright 2018 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## normalize-path +License: MIT +By: Jon Schlinkert, Blaine Bublitz +Repository: jonschlinkert/normalize-path + +> The MIT License (MIT) +> +> Copyright (c) 2014-2018, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## parse-ms +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/parse-ms + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## picocolors +License: ISC +By: Alexey Raspopov +Repository: alexeyraspopov/picocolors + +> ISC License +> +> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## picomatch +License: MIT +By: Jon Schlinkert +Repository: micromatch/picomatch + +> The MIT License (MIT) +> +> Copyright (c) 2017-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## pretty-bytes +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/pretty-bytes + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## pretty-ms +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/pretty-ms + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## readdirp +License: MIT +By: Thorsten Lorenz, Paul Miller +Repository: git://github.com/paulmillr/readdirp.git + +> MIT License +> +> Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## signal-exit +License: ISC +By: Ben Coe +Repository: https://github.com/tapjs/signal-exit.git + +> The ISC License +> +> Copyright (c) 2015-2023 Benjamin Coe, Isaac Z. Schlueter, and Contributors +> +> Permission to use, copy, modify, and/or distribute this software +> for any purpose with or without fee is hereby granted, provided +> that the above copyright notice and this permission notice +> appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +> OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +> LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +> OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## time-zone +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/time-zone + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## to-regex-range +License: MIT +By: Jon Schlinkert, Rouven Weßling +Repository: micromatch/to-regex-range + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## tslib +License: 0BSD +By: Microsoft Corp. +Repository: https://github.com/Microsoft/tslib.git + +> Copyright (c) Microsoft Corporation. +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +> OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +> PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## yargs-parser +License: ISC +By: Ben Coe +Repository: https://github.com/yargs/yargs-parser.git + +> Copyright (c) 2016, Contributors +> +> Permission to use, copy, modify, and/or distribute this software +> for any purpose with or without fee is hereby granted, provided +> that the above copyright notice and this permission notice +> appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +> OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +> LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +> OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +===== serde-wasm-bindgen 0.6.5 :: LICENSE ===== +MIT License + +Copyright (c) 2019 Cloudflare, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== siginfo 2.0.0 :: LICENSE ===== +Copyright (c) 2017, Emil Bay + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +===== slab 0.4.12 :: LICENSE ===== +Copyright (c) 2019 Carl Lerche + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +===== source-map-js 1.2.1 :: LICENSE ===== +Copyright (c) 2009-2011, Mozilla Foundation and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +===== std-env 3.10.0 :: LICENCE ===== +MIT License + +Copyright (c) Pooya Parsa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== strip-literal 3.1.0 :: LICENSE ===== +MIT License + +Copyright (c) 2022 Anthony Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== tinybench 2.9.0 :: LICENSE ===== +MIT License + +Copyright (c) 2022 Tinylibs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== tinyexec 0.3.2 :: LICENSE ===== +MIT License + +Copyright (c) 2024 Tinylibs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== tinyglobby 0.2.17 :: LICENSE ===== +MIT License + +Copyright (c) 2024 Madeline Gurriarán + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== tinypool 1.1.1 :: LICENSE ===== +The MIT License (MIT) + +Copyright (c) 2020 James M Snell and the Piscina contributors + +Piscina contributors listed at https://github.com/jasnell/piscina#the-team and +in the README file. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== tinyrainbow 2.0.0 :: LICENCE ===== +MIT License + +Copyright (c) 2022 Tinylibs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== tinyspy 4.0.4 :: LICENCE ===== +MIT License + +Copyright (c) 2022 Tinylibs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== typescript 5.9.3 :: LICENSE.txt ===== +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +===== vite 7.3.6 :: LICENSE.md ===== +# Vite core license +Vite is released under the MIT license: + +MIT License + +Copyright (c) 2019-present, VoidZero Inc. and Vite contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +# Licenses of bundled dependencies +The published Vite artifact additionally contains code with the following licenses: +BSD-2-Clause, CC0-1.0, ISC, MIT + +# Bundled dependencies: +## @jridgewell/gen-mapping, @jridgewell/remapping, @jridgewell/sourcemap-codec, @jridgewell/trace-mapping +License: MIT +By: Justin Ridgewell +Repositories: https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps + +> Copyright 2024 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @jridgewell/resolve-uri +License: MIT +By: Justin Ridgewell +Repository: https://github.com/jridgewell/resolve-uri + +> Copyright 2019 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @polka/compression +License: MIT +Repository: https://github.com/lukeed/polka + +--------------------------------------- + +## @polka/url +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/polka + +--------------------------------------- + +## @rolldown/pluginutils +License: MIT +Repository: https://github.com/rolldown/rolldown + +> MIT License +> +> Copyright (c) 2024-present VoidZero Inc. & Contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> +> end of terms and conditions +> +> The licenses of externally maintained libraries from which parts of the Software is derived are listed [here](https://github.com/rolldown/rolldown/blob/main/THIRD-PARTY-LICENSE). + +--------------------------------------- + +## @rollup/plugin-alias, @rollup/plugin-commonjs, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils +License: MIT +By: Johannes Stein +Repository: https://github.com/rollup/plugins + +License: MIT +By: Rich Harris +Repository: https://github.com/rollup/plugins + +License: MIT +By: LarsDenBakker +Repository: https://github.com/rollup/plugins + +License: MIT +By: Rich Harris +Repository: https://github.com/rollup/plugins + +> The MIT License (MIT) +> +> Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## anymatch +License: ISC +By: Elan Shanker +Repository: https://github.com/micromatch/anymatch + +> The ISC License +> +> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com) +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## artichokie +License: MIT +By: sapphi-red, Evan You +Repository: https://github.com/sapphi-red/artichokie + +> MIT License +> +> Copyright (c) 2020-present, Yuxi (Evan) You +> Copyright (c) 2023-present, sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## binary-extensions +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/binary-extensions + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## braces, fill-range, is-number +License: MIT +By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm +Repository: https://github.com/micromatch/braces + +License: MIT +By: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling +Repository: https://github.com/jonschlinkert/fill-range + +License: MIT +By: Jon Schlinkert, Olsten Larck, Rouven Weßling +Repository: https://github.com/jonschlinkert/is-number + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## bundle-name, default-browser, default-browser-id, define-lazy-prop, is-docker, is-inside-container, is-wsl, open, run-applescript, wsl-utils +License: MIT +By: Sindre Sorhus +Repositories: https://github.com/sindresorhus/bundle-name, https://github.com/sindresorhus/default-browser, https://github.com/sindresorhus/default-browser-id, https://github.com/sindresorhus/define-lazy-prop, https://github.com/sindresorhus/is-docker, https://github.com/sindresorhus/is-inside-container, https://github.com/sindresorhus/is-wsl, https://github.com/sindresorhus/open, https://github.com/sindresorhus/run-applescript, https://github.com/sindresorhus/wsl-utils + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cac +License: MIT +By: egoist +Repository: https://github.com/egoist/cac + +> The MIT License (MIT) +> +> Copyright (c) EGOIST <0x142857@gmail.com> (https://github.com/egoist) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## chokidar +License: MIT +By: Paul Miller, Elan Shanker +Repository: https://github.com/paulmillr/chokidar + +> The MIT License (MIT) +> +> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the “Software”), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## commondir, shell-quote +License: MIT +By: James Halliday +Repositories: http://github.com/substack/node-commondir, http://github.com/ljharb/shell-quote + +> The MIT License +> +> Copyright (c) 2013 James Halliday (mail@substack.net) +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## connect +License: MIT +By: TJ Holowaychuk, Douglas Christopher Wilson, Jonathan Ong, Tim Caswell +Repository: https://github.com/senchalabs/connect + +> (The MIT License) +> +> Copyright (c) 2010 Sencha Inc. +> Copyright (c) 2011 LearnBoost +> Copyright (c) 2011-2014 TJ Holowaychuk +> Copyright (c) 2015 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## convert-source-map +License: MIT +By: Thorsten Lorenz +Repository: https://github.com/thlorenz/convert-source-map + +> Copyright 2013 Thorsten Lorenz. +> All rights reserved. +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cors +License: MIT +By: Troy Goode +Repository: https://github.com/expressjs/cors + +> (The MIT License) +> +> Copyright (c) 2013 Troy Goode +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cross-spawn +License: MIT +By: André Cruz +Repository: https://github.com/moxystudio/node-cross-spawn + +> The MIT License (MIT) +> +> Copyright (c) 2018 Made With MOXY Lda +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## cssesc +License: MIT +By: Mathias Bynens +Repository: https://github.com/mathiasbynens/cssesc + +> Copyright Mathias Bynens +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## dotenv +License: BSD-2-Clause +Repository: https://github.com/motdotla/dotenv + +> Copyright (c) 2015, Scott Motte +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## dotenv-expand +License: BSD-2-Clause +By: motdotla +Repository: https://github.com/motdotla/dotenv-expand + +> Copyright (c) 2016, Scott Motte +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## ee-first +License: MIT +By: Jonathan Ong, Douglas Christopher Wilson +Repository: https://github.com/jonathanong/ee-first + +> The MIT License (MIT) +> +> Copyright (c) 2014 Jonathan Ong me@jongleberry.com +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## encodeurl +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/pillarjs/encodeurl + +> (The MIT License) +> +> Copyright (c) 2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## entities +License: BSD-2-Clause +By: Felix Boehm +Repository: https://github.com/fb55/entities + +> Copyright (c) Felix Böhm +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +> +> Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +> +> Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +> +> THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +> EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## es-module-lexer +License: MIT +By: Guy Bedford +Repository: https://github.com/guybedford/es-module-lexer + +> MIT License +> ----------- +> +> Copyright (C) 2018-2022 Guy Bedford +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## escape-html +License: MIT +Repository: https://github.com/component/escape-html + +> (The MIT License) +> +> Copyright (c) 2012-2013 TJ Holowaychuk +> Copyright (c) 2015 Andreas Lubbe +> Copyright (c) 2015 Tiancheng "Timothy" Gu +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## estree-walker +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/estree-walker + +> Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## etag +License: MIT +By: Douglas Christopher Wilson, David Björklund +Repository: https://github.com/jshttp/etag + +> (The MIT License) +> +> Copyright (c) 2014-2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## finalhandler +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/pillarjs/finalhandler + +> (The MIT License) +> +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## follow-redirects +License: MIT +By: Ruben Verborgh, Olivier Lalonde, James Talmage +Repository: https://github.com/follow-redirects/follow-redirects + +> Copyright 2014–present Olivier Lalonde , James Talmage , Ruben Verborgh +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +> IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## generic-names +License: MIT +By: Alexey Litvinov +Repository: https://github.com/css-modules/generic-names + +> The MIT License (MIT) +> +> Copyright (c) 2015 Alexey Litvinov +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## glob-parent +License: ISC +By: Gulp Team, Elan Shanker, Blaine Bublitz +Repository: https://github.com/gulpjs/glob-parent + +> The ISC License +> +> Copyright (c) 2015, 2019 Elan Shanker +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## host-validation-middleware +License: MIT +By: sapphi-red +Repository: https://github.com/sapphi-red/host-validation-middleware + +> MIT License +> +> Copyright (c) 2025 sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## http-proxy-3 +License: MIT +By: William Stein, Charlie Robbins, Jimb Esser, jcrugzz +Repository: https://github.com/sagemathinc/http-proxy-3 + +> node-http-3 +> +> Copyright (c) 2010-2025 William Stein, Charlie Robbins, Jarrett Cruger & the Contributors. +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## icss-utils +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/icss-utils + +> ISC License (ISC) +> Copyright 2018 Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## is-binary-path +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/is-binary-path + +> MIT License +> +> Copyright (c) 2019 Sindre Sorhus (https://sindresorhus.com), Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## is-extglob +License: MIT +By: Jon Schlinkert +Repository: https://github.com/jonschlinkert/is-extglob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-glob +License: MIT +By: Jon Schlinkert, Brian Woodward, Daniel Perez +Repository: https://github.com/micromatch/is-glob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2017, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-reference +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/is-reference + +--------------------------------------- + +## isexe, which +License: ISC +By: Isaac Z. Schlueter +Repositories: https://github.com/isaacs/isexe, https://github.com/isaacs/node-which + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## js-tokens +License: MIT +By: Simon Lydell +Repository: https://github.com/lydell/js-tokens + +> The MIT License (MIT) +> +> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024 Simon Lydell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## launch-editor, launch-editor-middleware +License: MIT +By: Evan You +Repositories: https://github.com/vitejs/launch-editor, https://github.com/vitejs/launch-editor + +> The MIT License (MIT) +> +> Copyright (c) 2017-present, Yuxi (Evan) You +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## lilconfig +License: MIT +By: antonk52 +Repository: https://github.com/antonk52/lilconfig + +> MIT License +> +> Copyright (c) 2022 Anton Kastritskiy +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## loader-utils +License: MIT +By: Tobias Koppers @sokra +Repository: https://github.com/webpack/loader-utils + +> Copyright JS Foundation and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## lodash.camelcase +License: MIT +By: John-David Dalton, Blaine Bublitz, Mathias Bynens +Repository: https://github.com/lodash/lodash + +> Copyright jQuery Foundation and other contributors +> +> Based on Underscore.js, copyright Jeremy Ashkenas, +> DocumentCloud and Investigative Reporters & Editors +> +> This software consists of voluntary contributions made by many +> individuals. For exact contribution history, see the revision history +> available at https://github.com/lodash/lodash +> +> The following license applies to all parts of this software except as +> documented below: +> +> ==== +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> +> ==== +> +> Copyright and related rights for sample code are waived via CC0. Sample +> code is defined as all source code displayed within the prose of the +> documentation. +> +> CC0: http://creativecommons.org/publicdomain/zero/1.0/ +> +> ==== +> +> Files located in the node_modules and vendor directories are externally +> maintained libraries used by this software which have their own +> licenses; we recommend you read them, as their terms may differ from the +> terms above. + +--------------------------------------- + +## magic-string +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/magic-string + +> Copyright 2018 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## mlly, ufo +License: MIT +Repositories: https://github.com/unjs/mlly, https://github.com/unjs/ufo + +> MIT License +> +> Copyright (c) Pooya Parsa +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## mrmime +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/mrmime + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (https://lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## normalize-path +License: MIT +By: Jon Schlinkert, Blaine Bublitz +Repository: https://github.com/jonschlinkert/normalize-path + +> The MIT License (MIT) +> +> Copyright (c) 2014-2018, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## object-assign +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/object-assign + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## obug +License: MIT +By: Kevin Deng +Repository: https://github.com/sxzz/obug + +> The MIT License (MIT) +> +> Copyright © 2025-PRESENT Kevin Deng (https://github.com/sxzz) +> Copyright (c) 2014-2017 TJ Holowaychuk +> Copyright (c) 2018-2021 Josh Junon +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## on-finished +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/jshttp/on-finished + +> (The MIT License) +> +> Copyright (c) 2013 Jonathan Ong +> Copyright (c) 2014 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## parse5 +License: MIT +By: Ivan Nikulin, https://github.com/inikulin/parse5/graphs/contributors +Repository: https://github.com/inikulin/parse5 + +> Copyright (c) 2013-2019 Ivan Nikulin (ifaaan@gmail.com, https://github.com/inikulin) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## parseurl +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/pillarjs/parseurl + +> (The MIT License) +> +> Copyright (c) 2014 Jonathan Ong +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## path-key, shebang-regex +License: MIT +By: Sindre Sorhus +Repositories: https://github.com/sindresorhus/path-key, https://github.com/sindresorhus/shebang-regex + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## periscopic +License: MIT +Repository: https://github.com/Rich-Harris/periscopic + +> Copyright (c) 2019 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## picocolors +License: ISC +By: Alexey Raspopov +Repository: https://github.com/alexeyraspopov/picocolors + +> ISC License +> +> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-import +License: MIT +By: Maxime Thirouin +Repository: https://github.com/postcss/postcss-import + +> The MIT License (MIT) +> +> Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-load-config +License: MIT +By: Michael Ciniawky, Ryan Dunckel, Mateusz Derks, Dalton Santos, Patrick Gilday, François Wouts +Repository: https://github.com/postcss/postcss-load-config + +> The MIT License (MIT) +> +> Copyright Michael Ciniawsky +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules +License: MIT +By: Alexander Madyankin +Repository: https://github.com/css-modules/postcss-modules + +> The MIT License (MIT) +> +> Copyright 2015-present Alexander Madyankin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules-extract-imports +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-extract-imports + +> Copyright 2015 Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-modules-local-by-default +License: MIT +By: Mark Dalgleish +Repository: https://github.com/css-modules/postcss-modules-local-by-default + +> The MIT License (MIT) +> +> Copyright 2015 Mark Dalgleish +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules-scope +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-scope + +> ISC License (ISC) +> +> Copyright (c) 2015, Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-modules-values +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-values + +> ISC License (ISC) +> +> Copyright (c) 2015, Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-selector-parser +License: MIT +By: Ben Briggs, Chris Eppstein +Repository: https://github.com/postcss/postcss-selector-parser + +> Copyright (c) Ben Briggs (http://beneb.info) +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-value-parser +License: MIT +By: Bogdan Chadkin +Repository: https://github.com/TrySound/postcss-value-parser + +> Copyright (c) Bogdan Chadkin +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## readdirp +License: MIT +By: Thorsten Lorenz, Paul Miller +Repository: https://github.com/paulmillr/readdirp + +> MIT License +> +> Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## resolve.exports, totalist +License: MIT +By: Luke Edwards +Repositories: https://github.com/lukeed/resolve.exports, https://github.com/lukeed/totalist + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## shebang-command +License: MIT +By: Kevin Mårtensson +Repository: https://github.com/kevva/shebang-command + +> MIT License +> +> Copyright (c) Kevin Mårtensson (github.com/kevva) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## sirv +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/sirv + +--------------------------------------- + +## statuses +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/jshttp/statuses + +> The MIT License (MIT) +> +> Copyright (c) 2014 Jonathan Ong +> Copyright (c) 2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## string-hash +License: CC0-1.0 +By: The Dark Sky Company +Repository: https://github.com/darkskyapp/string-hash + +--------------------------------------- + +## strip-literal +License: MIT +By: Anthony Fu +Repository: https://github.com/antfu/strip-literal + +> MIT License +> +> Copyright (c) 2022 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## to-regex-range +License: MIT +By: Jon Schlinkert, Rouven Weßling +Repository: https://github.com/micromatch/to-regex-range + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## tsconfck +License: MIT +By: dominikg +Repository: https://github.com/dominikg/tsconfck + +> MIT License +> +> Copyright (c) 2021-present dominikg and [contributors](https://github.com/dominikg/tsconfck/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> +> -- Licenses for 3rd-party code included in tsconfck -- +> +> # strip-bom and strip-json-comments +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## unpipe +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/stream-utils/unpipe + +> (The MIT License) +> +> Copyright (c) 2015 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## util-deprecate +License: MIT +By: Nathan Rajlich +Repository: https://github.com/TooTallNate/util-deprecate + +> (The MIT License) +> +> Copyright (c) 2014 Nathan Rajlich +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## utils-merge +License: MIT +By: Jared Hanson +Repository: https://github.com/jaredhanson/utils-merge + +> The MIT License (MIT) +> +> Copyright (c) 2013-2017 Jared Hanson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## vary +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/jshttp/vary + +> (The MIT License) +> +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## ws +License: MIT +By: Einar Otto Stangvik +Repository: https://github.com/websockets/ws + +> Copyright (c) 2011 Einar Otto Stangvik +> Copyright (c) 2013 Arnout Kazemier and contributors +> Copyright (c) 2016 Luigi Pinca and contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== vite-node 3.2.4 :: LICENSE ===== +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== vitest 3.2.7 :: LICENSE.md ===== +# Vitest core license +Vitest is released under the MIT license: + +MIT License + +Copyright (c) 2021-Present Vitest Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +# Licenses of bundled dependencies +The published Vitest artifact additionally contains code with the following licenses: +BSD-3-Clause, ISC, MIT + +# Bundled dependencies: +## @antfu/install-pkg +License: MIT +By: Anthony Fu +Repository: git+https://github.com/antfu/install-pkg.git + +> MIT License +> +> Copyright (c) 2021 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @sinonjs/commons +License: BSD-3-Clause +Repository: git+https://github.com/sinonjs/commons.git + +> BSD 3-Clause License +> +> Copyright (c) 2018, Sinon.JS +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> * Neither the name of the copyright holder nor the names of its +> contributors may be used to endorse or promote products derived from +> this software without specific prior written permission. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## @sinonjs/fake-timers +License: BSD-3-Clause +By: Christian Johansen +Repository: git+https://github.com/sinonjs/fake-timers.git + +> Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no. All rights reserved. +> +> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +> +> 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +> +> 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +> +> 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## acorn-walk +License: MIT +By: Marijn Haverbeke, Ingvar Stepanyan, Adrian Heine +Repository: https://github.com/acornjs/acorn.git + +> MIT License +> +> Copyright (C) 2012-2020 by various contributors (see AUTHORS) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## birpc +License: MIT +By: Anthony Fu +Repository: git+https://github.com/antfu/birpc.git + +> MIT License +> +> Copyright (c) 2021 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## cac +License: MIT +By: egoist +Repository: egoist/cac + +> The MIT License (MIT) +> +> Copyright (c) EGOIST <0x142857@gmail.com> (https://github.com/egoist) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## chai-subset +License: MIT +By: Andrii Shumada, Robert Herhold +Repository: https://github.com/debitoor/chai-subset.git + +> The MIT License (MIT) +> +> Copyright (c) 2014 +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## find-up +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/find-up + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## flatted +License: ISC +By: Andrea Giammarchi +Repository: git+https://github.com/WebReflection/flatted.git + +> ISC License +> +> Copyright (c) 2018-2020, Andrea Giammarchi, @WebReflection +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +> OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +> PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## js-tokens +License: MIT +By: Simon Lydell +Repository: lydell/js-tokens + +> The MIT License (MIT) +> +> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024 Simon Lydell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## kleur +License: MIT +By: Luke Edwards +Repository: lukeed/kleur + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## local-pkg +License: MIT +By: Anthony Fu +Repository: git+https://github.com/antfu/local-pkg.git + +> MIT License +> +> Copyright (c) 2021 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## locate-path +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/locate-path + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## mime +License: MIT +By: Robert Kieffer +Repository: https://github.com/broofa/mime + +> MIT License +> +> Copyright (c) 2023 Robert Kieffer +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## mlly +License: MIT +Repository: unjs/mlly + +> MIT License +> +> Copyright (c) Pooya Parsa +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## p-limit +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/p-limit + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## p-locate +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/p-locate + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## package-manager-detector +License: MIT +By: Anthony Fu +Repository: git+https://github.com/antfu-collective/package-manager-detector.git + +> MIT License +> +> Copyright (c) 2020-PRESENT Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## prompts +License: MIT +By: Terkel Gjervig +Repository: terkelg/prompts + +> MIT License +> +> Copyright (c) 2018 Terkel Gjervig Nielsen +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## quansync +License: MIT +By: Anthony Fu, 三咲智子 Kevin Deng +Repository: git+https://github.com/quansync-dev/quansync.git + +> MIT License +> +> Copyright (c) 2025-PRESENT Anthony Fu and Kevin Deng +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## sisteransi +License: MIT +By: Terkel Gjervig +Repository: https://github.com/terkelg/sisteransi + +> MIT License +> +> Copyright (c) 2018 Terkel Gjervig Nielsen +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## strip-literal +License: MIT +By: Anthony Fu +Repository: git+https://github.com/antfu/strip-literal.git + +> MIT License +> +> Copyright (c) 2022 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## type-detect +License: MIT +By: Jake Luer, Keith Cirkel, David Losert, Aleksey Shvayka, Lucas Fernandes da Costa, Grant Snodgrass, Jeremy Tice, Edward Betts, dvlsg, Amila Welihinda, Jake Champion, Miroslav Bajtoš +Repository: git+ssh://git@github.com/chaijs/type-detect.git + +> Copyright (c) 2013 Jake Luer (http://alogicalparadox.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## ufo +License: MIT +Repository: unjs/ufo + +> MIT License +> +> Copyright (c) Pooya Parsa +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## ws +License: MIT +By: Einar Otto Stangvik +Repository: git+https://github.com/websockets/ws.git + +> Copyright (c) 2011 Einar Otto Stangvik +> Copyright (c) 2013 Arnout Kazemier and contributors +> Copyright (c) 2016 Luigi Pinca and contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## yocto-queue +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/yocto-queue + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +===== why-is-node-running 2.3.0 :: LICENSE ===== +The MIT License (MIT) + +Copyright (c) 2016 Mathias Buus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/assets/example-data/Col-CEN_v1.2.fasta.gz b/assets/example-data/Col-CEN_v1.2.fasta.gz new file mode 100644 index 0000000..9b86340 Binary files /dev/null and b/assets/example-data/Col-CEN_v1.2.fasta.gz differ diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..ac05022 --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "moddotplot-core" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Scientific computation core for moddotplot-interactive" + +[dependencies] + +[features] +default = [] +validation = [] + +[[test]] +name = "estimator_validation" +path = "tests/estimator_validation.rs" +required-features = ["validation"] + +[[test]] +name = "statistical_accuracy" +path = "tests/statistical_accuracy.rs" +required-features = ["validation"] + +[lints] +workspace = true diff --git a/crates/core/LICENSE b/crates/core/LICENSE new file mode 100644 index 0000000..0690d5e --- /dev/null +++ b/crates/core/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright 2026 moddotplot-interactive contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs new file mode 100644 index 0000000..f54a10b --- /dev/null +++ b/crates/core/src/config.rs @@ -0,0 +1,541 @@ +//! Versioned scientific configuration shared by native tests and browser production. + +use crate::DEFAULT_HASH_SEED; +use crate::hash::HashAlgorithm; +use crate::sketch::OphJaccardEstimator; +use std::fmt::{Display, Formatter}; + +const DEFAULT_ANI_FLOOR: f64 = 0.80; +const DEFAULT_EXACT_CORE_CAP: usize = 2_048; +const DEFAULT_MATCH_DETECTION_PROBABILITY: f64 = 0.99; + +/// Current serialized scientific-contract version. +pub const SCIENTIFIC_CONFIG_VERSION: u16 = 1; + +/// Default number of low winner bits used by the candidate comparison stage. +pub const DEFAULT_B_BITS: u8 = 14; + +/// Winner fingerprint width used to verify candidates and determine orientation. +pub const DEFAULT_VERIFICATION_BITS: u8 = 32; + +/// Default HLL precision pending the documented precision sweep. +pub const DEFAULT_HLL_PRECISION: u8 = 10; + +/// Fixed-point scale used for ANI values in matrix tiles. +pub const DEFAULT_IDENTITY_SCALE: u16 = 10_000; + +/// Missing-value sentinel in the unsigned identity channel. +pub const DEFAULT_MISSING_IDENTITY: u16 = u16::MAX; + +/// Exact canonical identity of a validated scientific configuration. +/// +/// This value is suitable for in-process equality, ordering, and cache keys. The +/// shorter 64-bit digest remains the external provenance label and is not used as the +/// sole proof that two cached scientific objects are compatible. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ScientificConfigIdentity([u8; 54]); + +impl ScientificConfigIdentity { + /// Canonical little-endian field encoding. + pub const fn as_bytes(&self) -> &[u8; 54] { + &self.0 + } +} + +/// How neighboring context is added to the target interval. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum AxisExpansionPolicy { + /// Expand by one half of the core interval on both sides. + HalfWindow = 1, +} + +/// Transform from directed containment to displayed identity. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum AniTransform { + /// Display `containment^(1/k)`. + ContainmentKthRoot = 1, +} + +/// A validated, immutable description of every parameter that can affect a sketch tile. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ScientificConfig { + version: u16, + k: u8, + hash_algorithm: HashAlgorithm, + hash_seed: u64, + register_count: usize, + b_bits: u8, + verification_bits: u8, + oph_estimator: OphJaccardEstimator, + hll_precision: u8, + expansion: AxisExpansionPolicy, + ani_transform: AniTransform, + sparse_core_cap: usize, + ani_floor: f64, + minimum_detection_probability: f64, + missing_identity: u16, + identity_scale: u16, +} + +impl ScientificConfig { + /// Constructs the production configuration using the release-default HLL precision. + /// + /// # Errors + /// + /// Returns a validation error when `k` or `register_count` is unsupported. + pub fn production_default( + k: u8, + register_count: usize, + sparse_correction: bool, + ) -> Result { + Self::production(k, register_count, DEFAULT_HLL_PRECISION, sparse_correction) + } + + /// Constructs the production ntHash2 configuration for one quality tier. + /// + /// # Errors + /// + /// Returns a validation error for an unsupported k-mer length, register count, + /// or `HyperLogLog` precision. + pub fn production( + k: u8, + register_count: usize, + hll_precision: u8, + sparse_correction: bool, + ) -> Result { + Self::new( + k, + HashAlgorithm::NtHash2, + DEFAULT_HASH_SEED, + register_count, + DEFAULT_B_BITS, + DEFAULT_VERIFICATION_BITS, + OphJaccardEstimator::VerifiedWinners, + hll_precision, + if sparse_correction { + DEFAULT_EXACT_CORE_CAP + } else { + 0 + }, + DEFAULT_ANI_FLOOR, + DEFAULT_MATCH_DETECTION_PROBABILITY, + ) + } + + /// Constructs an explicit configuration for controlled scientific validation. + /// + /// # Errors + /// + /// Returns the field-specific [`ConfigError`] for any invalid combination. + #[allow(clippy::too_many_arguments)] + pub fn new( + k: u8, + hash_algorithm: HashAlgorithm, + hash_seed: u64, + register_count: usize, + b_bits: u8, + verification_bits: u8, + oph_estimator: OphJaccardEstimator, + hll_precision: u8, + sparse_core_cap: usize, + ani_floor: f64, + minimum_detection_probability: f64, + ) -> Result { + if !(1..=31).contains(&k) { + return Err(ConfigError::KmerLength); + } + if !(2..=4_096).contains(®ister_count) || !register_count.is_power_of_two() { + return Err(ConfigError::RegisterCount); + } + if b_bits == 0 || b_bits > verification_bits { + return Err(ConfigError::ComparisonBits); + } + if !(b_bits..=64).contains(&verification_bits) { + return Err(ConfigError::VerificationBits); + } + if !(4..=16).contains(&hll_precision) { + return Err(ConfigError::HllPrecision); + } + if !(0.0..=1.0).contains(&ani_floor) || !ani_floor.is_finite() { + return Err(ConfigError::AniFloor); + } + if !(0.0..1.0).contains(&minimum_detection_probability) + || !minimum_detection_probability.is_finite() + { + return Err(ConfigError::DetectionProbability); + } + if sparse_core_cap > 0 && !cfg!(feature = "validation") { + return Err(ConfigError::ValidationFeatureDisabled); + } + Ok(Self { + version: SCIENTIFIC_CONFIG_VERSION, + k, + hash_algorithm, + hash_seed, + register_count, + b_bits, + verification_bits, + oph_estimator, + hll_precision, + expansion: AxisExpansionPolicy::HalfWindow, + ani_transform: AniTransform::ContainmentKthRoot, + sparse_core_cap, + ani_floor, + minimum_detection_probability, + missing_identity: DEFAULT_MISSING_IDENTITY, + identity_scale: DEFAULT_IDENTITY_SCALE, + }) + } + + /// Stable scientific-contract version. + pub const fn version(self) -> u16 { + self.version + } + /// Canonical k-mer length. + pub const fn k(self) -> u8 { + self.k + } + /// Canonical hash stream. + pub const fn hash_algorithm(self) -> HashAlgorithm { + self.hash_algorithm + } + /// Public deterministic hash seed. + pub const fn hash_seed(self) -> u64 { + self.hash_seed + } + /// OPH register count. + pub const fn register_count(self) -> usize { + self.register_count + } + /// Low-bit candidate width. + pub const fn b_bits(self) -> u8 { + self.b_bits + } + /// Winner verification width. + pub const fn verification_bits(self) -> u8 { + self.verification_bits + } + /// OPH resemblance statistic. + pub const fn oph_estimator(self) -> OphJaccardEstimator { + self.oph_estimator + } + /// `HyperLogLog` precision. + pub const fn hll_precision(self) -> u8 { + self.hll_precision + } + /// Neighbor expansion policy. + pub const fn expansion(self) -> AxisExpansionPolicy { + self.expansion + } + /// Display identity transform. + pub const fn ani_transform(self) -> AniTransform { + self.ani_transform + } + /// Exact sparse-core retention cap, or zero when disabled. + pub const fn sparse_core_cap(self) -> usize { + self.sparse_core_cap + } + /// ANI floor used by sparse-detection policy. + pub const fn ani_floor(self) -> f64 { + self.ani_floor + } + /// Required probability of observing a true winner match. + pub const fn minimum_detection_probability(self) -> f64 { + self.minimum_detection_probability + } + /// Missing identity sentinel. + pub const fn missing_identity(self) -> u16 { + self.missing_identity + } + /// Fixed-point identity scale. + pub const fn identity_scale(self) -> u16 { + self.identity_scale + } + + /// Stable FNV-1a digest covering every scientific field. + pub fn digest(self) -> u64 { + let mut digest = 0xcbf2_9ce4_8422_2325_u64; + for byte in self.identity().0 { + digest ^= u64::from(byte); + digest = digest.wrapping_mul(0x0000_0100_0000_01b3); + } + digest + } + + /// Exact canonical identity covering every scientific field. + /// + /// # Panics + /// + /// Panics if an internal schema edit changes the canonical encoding width without + /// updating [`ScientificConfigIdentity`]. Validated runtime values cannot trigger + /// this invariant. + pub fn identity(self) -> ScientificConfigIdentity { + let mut bytes = Vec::with_capacity(64); + bytes.extend_from_slice(&self.version.to_le_bytes()); + bytes.push(self.k); + bytes.push(self.hash_algorithm as u8); + bytes.extend_from_slice(&self.hash_seed.to_le_bytes()); + bytes.extend_from_slice(&(self.register_count as u64).to_le_bytes()); + bytes.push(self.b_bits); + bytes.push(self.verification_bits); + bytes.push(self.oph_estimator as u8); + bytes.push(self.hll_precision); + bytes.push(self.expansion as u8); + bytes.push(self.ani_transform as u8); + bytes.extend_from_slice(&(self.sparse_core_cap as u64).to_le_bytes()); + bytes.extend_from_slice(&self.ani_floor.to_bits().to_le_bytes()); + bytes.extend_from_slice(&self.minimum_detection_probability.to_bits().to_le_bytes()); + bytes.extend_from_slice(&self.missing_identity.to_le_bytes()); + bytes.extend_from_slice(&self.identity_scale.to_le_bytes()); + ScientificConfigIdentity( + bytes + .try_into() + .expect("scientific configuration identity has a fixed width"), + ) + } +} + +/// Validation failure for a scientific configuration. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ConfigError { + /// `k` is outside `1..=31`. + KmerLength, + /// Register count is unsupported or not a power of two. + RegisterCount, + /// Candidate bits are zero or exceed the verification width. + ComparisonBits, + /// Verification width is narrower than candidate bits or wider than 64. + VerificationBits, + /// HLL precision is outside `4..=16`. + HllPrecision, + /// ANI floor is not finite or lies outside `[0,1]`. + AniFloor, + /// Detection probability is not finite or lies outside `[0,1)`. + DetectionProbability, + /// A validation-only configuration was requested from a default build. + ValidationFeatureDisabled, +} + +impl Display for ConfigError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter.write_str(match self { + Self::KmerLength => "k-mer length must be in 1..=31", + Self::RegisterCount => "register count must be a power of two between 2 and 4096", + Self::ComparisonBits => "comparison bits must be in 1..=verification bits", + Self::VerificationBits => "verification bits must be in comparison bits..=64", + Self::HllPrecision => "HLL precision must be in 4..=16", + Self::AniFloor => "ANI floor must be finite and in 0..=1", + Self::DetectionProbability => "detection probability must be finite and in 0..1", + Self::ValidationFeatureDisabled => { + "sparse correction requires the non-default validation feature" + } + }) + } +} + +impl std::error::Error for ConfigError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn production_digest_is_stable_and_sensitive_to_quality() { + let preview = ScientificConfig::production_default(21, 256, false).unwrap(); + let same = ScientificConfig::production_default(21, 256, false).unwrap(); + let detailed = ScientificConfig::production_default(21, 1_024, false).unwrap(); + assert_eq!(preview.digest(), same.digest()); + assert_ne!(preview.digest(), detailed.digest()); + assert_eq!(preview.digest(), 0x58e0_84b7_190f_4d93); + assert_eq!(detailed.digest(), 0xbc5c_5131_ad1c_9a20); + assert_eq!(preview.version(), SCIENTIFIC_CONFIG_VERSION); + assert_eq!(preview.identity().as_bytes().len(), 54); + assert_ne!(preview.identity(), detailed.identity()); + } + + #[test] + fn invalid_fields_are_rejected_before_allocation() { + assert_eq!( + ScientificConfig::production(21, 300, 12, false), + Err(ConfigError::RegisterCount) + ); + assert_eq!( + ScientificConfig::production(32, 256, 12, false), + Err(ConfigError::KmerLength) + ); + #[cfg(not(feature = "validation"))] + assert_eq!( + ScientificConfig::production_default(21, 256, true), + Err(ConfigError::ValidationFeatureDisabled) + ); + } + + #[test] + #[cfg(feature = "validation")] + #[allow(clippy::too_many_lines)] + fn canonical_identity_distinguishes_every_configurable_scientific_field() { + let configurations = [ + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 19, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 1, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 512, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 12, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 40, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::BbitCollisionCorrected, + 10, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 11, + 0, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 2_048, + 0.80, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.81, + 0.99, + ) + .unwrap(), + ScientificConfig::new( + 21, + HashAlgorithm::NtHash2, + 0, + 256, + 14, + 32, + OphJaccardEstimator::VerifiedWinners, + 10, + 0, + 0.80, + 0.98, + ) + .unwrap(), + ]; + let identities = configurations + .into_iter() + .map(ScientificConfig::identity) + .collect::>(); + assert_eq!(identities.len(), configurations.len()); + } +} diff --git a/crates/core/src/containment.rs b/crates/core/src/containment.rs new file mode 100644 index 0000000..7580d1a --- /dev/null +++ b/crates/core/src/containment.rs @@ -0,0 +1,186 @@ +//! Directed containment and orientation estimation. + +use crate::sketch::{BitSlicedSignature, OphSketch, RegisterComparison, verified_winner_jaccard}; + +/// Directional support derived only from complete-hash register matches. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct DirectionEvidence { + /// Matches supporting the same relative orientation. + pub forward: u32, + /// Matches supporting reverse-complement orientation. + pub reverse: u32, + /// Matches without an unambiguous direction. + pub ambiguous: u32, +} + +impl DirectionEvidence { + /// Total unambiguous support. + pub fn informative(self) -> u32 { + self.forward + self.reverse + } + + /// Signed direction in `[-1, 1]`, or `None` without informative support. + pub fn signed(self) -> Option { + let total = self.informative(); + (total > 0).then(|| (f64::from(self.forward) - f64::from(self.reverse)) / f64::from(total)) + } + + /// Adds independently sampled evidence. + #[must_use] + pub fn combine(self, other: Self) -> Self { + Self { + forward: self.forward.saturating_add(other.forward), + reverse: self.reverse.saturating_add(other.reverse), + ambiguous: self.ambiguous.saturating_add(other.ambiguous), + } + } +} + +/// One directed containment estimate `|X ∩ Y| / |X|`. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ContainmentEstimate { + /// Collision-corrected Jaccard estimate. + pub jaccard: f64, + /// Directed containment of the first sketch in the second. + pub containment: f64, + /// ANI-like transform `containment^(1/k)`. + pub ani: f64, + /// Full-hash directional evidence. + pub direction: DirectionEvidence, + /// Register comparison counters for diagnostics and validation. + pub registers: RegisterComparison, +} + +/// Estimates directed containment of `x` in `y`. +/// +/// Jaccard is converted using estimated distinct cardinalities: +/// `intersection = J * (|X| + |Y|) / (1 + J)`. +pub fn estimate_containment( + x: &OphSketch, + y: &OphSketch, + k: u8, + b: u8, + register_limit: usize, +) -> Option { + let mut registers = x.compare_bbit(y, b, register_limit); + registers.jaccard = verified_winner_jaccard(registers.verified_matches, registers.considered); + estimate_from_comparison( + x.estimated_cardinality(), + y.estimated_cardinality(), + k, + registers, + ) +} + +/// Estimates directed containment from word-parallel bit-sliced signatures. +#[inline] +pub fn estimate_containment_bit_sliced( + x: &BitSlicedSignature, + y: &BitSlicedSignature, + k: u8, + b: u8, + register_limit: usize, +) -> Option { + let mut registers = x.compare(y, b, register_limit); + registers.jaccard = verified_winner_jaccard(registers.verified_matches, registers.considered); + estimate_from_comparison( + x.estimated_cardinality(), + y.estimated_cardinality(), + k, + registers, + ) +} + +/// Test-only scalar interpretation of a frozen bit-sliced signature. +#[cfg(test)] +pub(crate) fn estimate_containment_bit_sliced_scalar( + x: &BitSlicedSignature, + y: &BitSlicedSignature, + k: u8, + b: u8, + register_limit: usize, +) -> Option { + estimate_from_comparison( + x.estimated_cardinality(), + y.estimated_cardinality(), + k, + x.compare_scalar(y, b, register_limit), + ) +} + +/// Estimates containment from externally accumulated aligned-register counters. +/// +/// This is used by sparse all-pairs kernels that invert b-bit winners by register +/// instead of comparing every signature pair independently. +pub fn estimate_containment_from_comparison( + x_cardinality: f64, + y_cardinality: f64, + k: u8, + _b: u8, + registers: RegisterComparison, +) -> Option { + let mut registers = registers; + registers.jaccard = verified_winner_jaccard(registers.verified_matches, registers.considered); + estimate_from_comparison(x_cardinality, y_cardinality, k, registers) +} + +#[inline] +pub(crate) fn estimate_from_comparison( + x_cardinality: f64, + y_cardinality: f64, + k: u8, + registers: RegisterComparison, +) -> Option { + if x_cardinality <= 0.0 { + return None; + } + let jaccard = registers.jaccard?; + let intersection = if jaccard == 0.0 { + 0.0 + } else { + jaccard * (x_cardinality + y_cardinality) / (1.0 + jaccard) + } + .clamp(0.0, x_cardinality.min(y_cardinality)); + let containment = (intersection / x_cardinality).clamp(0.0, 1.0); + let ani = if containment == 0.0 { + 0.0 + } else { + containment.powf(1.0 / f64::from(k)) + }; + Some(ContainmentEstimate { + jaccard, + containment, + ani, + direction: DirectionEvidence { + forward: registers.forward_matches, + reverse: registers.reverse_matches, + ambiguous: registers.ambiguous_matches, + }, + registers, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dna::Orientation; + use crate::hash::fmix64; + + #[test] + fn identical_sketch_has_full_containment_and_ani() { + let mut sketch = OphSketch::new(256, 10); + for value in 0..50_000_u64 { + sketch.insert(fmix64(value), Orientation::Forward); + } + let estimate = estimate_containment(&sketch, &sketch, 21, 14, 256).unwrap(); + assert!((estimate.containment - 1.0).abs() < f64::EPSILON); + assert!((estimate.ani - 1.0).abs() < f64::EPSILON); + assert_eq!(estimate.direction.signed(), Some(1.0)); + } + + #[test] + fn empty_numerator_is_missing() { + let empty = OphSketch::new(64, 8); + assert!(estimate_containment(&empty, &empty, 21, 14, 64).is_none()); + } +} diff --git a/crates/core/src/dna.rs b/crates/core/src/dna.rs new file mode 100644 index 0000000..3b943ed --- /dev/null +++ b/crates/core/src/dna.rs @@ -0,0 +1,768 @@ +//! Packed DNA storage and canonical rolling k-mers. + +use std::fmt; + +/// Orientation of a canonical k-mer relative to its source sequence. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[repr(u8)] +pub enum Orientation { + /// The forward representation is canonical. + Forward = 0, + /// The reverse-complement representation is canonical. + Reverse = 1, + /// Both orientations were observed for a repeated hash or are indistinguishable. + Both = 2, + /// No orientation evidence is available. + #[default] + Unknown = 3, +} + +impl Orientation { + /// Combines observations of the same canonical hash. + #[must_use] + pub fn combine(self, other: Self) -> Self { + match (self, other) { + (Self::Unknown, value) | (value, Self::Unknown) => value, + (left, right) if left == right => left, + _ => Self::Both, + } + } +} + +/// A canonical k-mer produced by [`PackedSequence::canonical_kmers`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CanonicalKmer { + /// Zero-based start coordinate. + pub start: u64, + /// Two-bit canonical nucleotide encoding. + pub bits: u64, + /// Orientation of the canonical encoding. + pub orientation: Orientation, +} + +const SPARSE_BIT_BLOCK_SHIFT: usize = 12; +const SPARSE_BIT_BLOCK_BITS: usize = 1 << SPARSE_BIT_BLOCK_SHIFT; +const SPARSE_BIT_WORDS: usize = SPARSE_BIT_BLOCK_BITS / u64::BITS as usize; +const STORAGE_COMPACTION_MIN_UNUSED_BYTES: usize = 1024 * 1024; +const STORAGE_COMPACTION_UNUSED_RATIO_DENOMINATOR: usize = 4; + +fn should_compact_capacity(len: usize, capacity: usize) -> bool { + let unused = capacity.saturating_sub(len); + unused.saturating_mul(std::mem::size_of::()) >= STORAGE_COMPACTION_MIN_UNUSED_BYTES + && unused.saturating_mul(STORAGE_COMPACTION_UNUSED_RATIO_DENOMINATOR) >= len +} + +fn compact_vec_if_excessive(values: &mut Vec) { + if should_compact_capacity::(values.len(), values.capacity()) { + values.shrink_to_fit(); + } +} + +fn compact_string_if_excessive(value: &mut String) { + if should_compact_capacity::(value.len(), value.capacity()) { + value.shrink_to_fit(); + } +} + +/// Two-level sparse bit vector used for the uncommon invalid-base positions. +/// +/// The first level costs one `u32` only through the last block containing an invalid +/// base. Second-level 4-Kibit blocks are allocated only when at least one bit is set. +/// Lookup remains constant-time in sequence-scanning hot paths. +#[derive(Clone, Default, Eq, PartialEq)] +struct SparseBitVector { + block_ids: Vec, + blocks: Vec<[u64; SPARSE_BIT_WORDS]>, +} + +impl SparseBitVector { + fn insert(&mut self, position: usize) { + let block_index = position >> SPARSE_BIT_BLOCK_SHIFT; + if self.block_ids.len() <= block_index { + self.block_ids.resize(block_index + 1, 0); + } + let stored_id = self.block_ids[block_index]; + let block_id = if stored_id == 0 { + self.blocks.push([0; SPARSE_BIT_WORDS]); + let new_id = u32::try_from(self.blocks.len()) + .expect("supported sequence has fewer than u32::MAX sparse blocks"); + self.block_ids[block_index] = new_id; + new_id + } else { + stored_id + }; + let within_block = position & (SPARSE_BIT_BLOCK_BITS - 1); + self.blocks[block_id as usize - 1][within_block / u64::BITS as usize] |= + 1_u64 << (within_block % u64::BITS as usize); + } + + fn contains(&self, position: usize) -> bool { + let block_index = position >> SPARSE_BIT_BLOCK_SHIFT; + let Some(&block_id) = self.block_ids.get(block_index) else { + return false; + }; + if block_id == 0 { + return false; + } + let within_block = position & (SPARSE_BIT_BLOCK_BITS - 1); + self.blocks[block_id as usize - 1][within_block / u64::BITS as usize] + & (1_u64 << (within_block % u64::BITS as usize)) + != 0 + } + + fn estimated_heap_bytes(&self) -> usize { + self.block_ids + .capacity() + .saturating_mul(std::mem::size_of::()) + .saturating_add( + self.blocks + .capacity() + .saturating_mul(std::mem::size_of::<[u64; SPARSE_BIT_WORDS]>()), + ) + } + + fn compact_storage(&mut self) { + compact_vec_if_excessive(&mut self.block_ids); + compact_vec_if_excessive(&mut self.blocks); + } +} + +/// Stable content identity for one packed biological sequence. +/// +/// The identity covers the complete scientifically relevant base stream. Invalid +/// IUPAC symbols share one token because every invalid symbol has the same k-mer +/// exclusion semantics in the core. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SequenceIdentity([u8; 16]); + +impl SequenceIdentity { + /// Canonical identity bytes used in cache and provenance records. + pub const fn as_bytes(&self) -> &[u8; 16] { + &self.0 + } +} + +/// Compact sequence storage using two bits per base and sparse invalid-base bits. +#[derive(Clone, Eq, PartialEq)] +pub struct PackedSequence { + name: String, + description: String, + len: u64, + bases: Vec, + invalid: SparseBitVector, + identity_left: u64, + identity_right: u64, +} + +impl Default for PackedSequence { + fn default() -> Self { + Self { + name: String::new(), + description: String::new(), + len: 0, + bases: Vec::new(), + invalid: SparseBitVector::default(), + identity_left: 0xcbf2_9ce4_8422_2325, + identity_right: 0x6a09_e667_f3bc_c909, + } + } +} + +impl fmt::Debug for PackedSequence { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("PackedSequence") + .field("name", &self.name) + .field("description", &self.description) + .field("len", &self.len) + .finish_non_exhaustive() + } +} + +impl PackedSequence { + /// Creates an empty record with the supplied sequence identifier. + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + ..Self::default() + } + } + + /// Creates an empty record with separate FASTA identifier and description fields. + pub fn with_description(name: impl Into, description: impl Into) -> Self { + Self { + name: name.into(), + description: description.into(), + ..Self::default() + } + } + + /// Creates a record from an in-memory ASCII sequence. + pub fn from_ascii(name: impl Into, sequence: &[u8]) -> Self { + let mut packed = Self::new(name); + for &base in sequence { + packed.push_ascii(base); + } + packed + } + + /// Returns the FASTA sequence identifier, ending at the first header whitespace. + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the optional FASTA description following the identifier. + pub fn description(&self) -> &str { + &self.description + } + + /// Returns the sequence length in bases. + pub fn len(&self) -> u64 { + self.len + } + + /// Stable identity of the complete scientifically relevant base stream. + pub fn identity(&self) -> SequenceIdentity { + let mut bytes = [0_u8; 16]; + bytes[..8].copy_from_slice(&self.identity_left.to_le_bytes()); + bytes[8..].copy_from_slice(&self.identity_right.to_le_bytes()); + SequenceIdentity(bytes) + } + + /// Returns true when the sequence contains no bases. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Appends an ASCII nucleotide. + /// + /// A/C/G/T are encoded case-insensitively. Other ASCII letters are retained as + /// invalid bases so their coordinates remain stable and overlapping k-mers are + /// excluded. + /// + /// # Panics + /// + /// Panics only if the sequence exceeds the platform's addressable memory. The + /// supported one-billion-base limit is safely addressable on Wasm32. + pub fn push_ascii(&mut self, base: u8) { + let (code, is_valid) = encode_base(base); + let base_index = usize::try_from(self.len).expect("sequence exceeds addressable memory"); + if base_index.is_multiple_of(4) { + self.bases.push(0); + } + + let base_shift = (base_index % 4) * 2; + self.bases[base_index / 4] |= code << base_shift; + if !is_valid { + self.invalid.insert(base_index); + } + self.update_identity(code, is_valid); + self.len += 1; + } + + #[inline] + fn update_identity(&mut self, code: u8, is_valid: bool) { + let identity_token = code | (u8::from(is_valid) << 2); + (self.identity_left, self.identity_right) = advance_identity( + self.identity_left, + self.identity_right, + self.len, + identity_token, + ); + } + + /// Appends one whitespace-free IUPAC sequence run in packed groups of eight. + /// + /// Returns the zero-based position of the first unsupported byte after retaining + /// the valid prefix. FASTA parsing uses this bulk path to avoid per-base parser + /// state transitions for ordinary wrapped sequence lines. + #[inline] + pub(crate) fn push_iupac_chunk(&mut self, chunk: &[u8]) -> Result<(), usize> { + let current_len = usize::try_from(self.len).expect("sequence exceeds addressable memory"); + let final_len = current_len.saturating_add(chunk.len()); + self.bases + .reserve(final_len.div_ceil(4).saturating_sub(self.bases.len())); + + let mut offset = 0; + while offset < chunk.len() && !self.len.is_multiple_of(8) { + if encode_iupac_base(chunk[offset]).is_none() { + return Err(offset); + } + self.push_ascii(chunk[offset]); + offset += 1; + } + + while offset + 8 <= chunk.len() { + let c0 = BASE_ENCODING[usize::from(chunk[offset])]; + let c1 = BASE_ENCODING[usize::from(chunk[offset + 1])]; + let c2 = BASE_ENCODING[usize::from(chunk[offset + 2])]; + let c3 = BASE_ENCODING[usize::from(chunk[offset + 3])]; + let c4 = BASE_ENCODING[usize::from(chunk[offset + 4])]; + let c5 = BASE_ENCODING[usize::from(chunk[offset + 5])]; + let c6 = BASE_ENCODING[usize::from(chunk[offset + 6])]; + let c7 = BASE_ENCODING[usize::from(chunk[offset + 7])]; + if (c0 | c1 | c2 | c3 | c4 | c5 | c6 | c7) & UNSUPPORTED_BASE != 0 { + for index in 0..8 { + if encode_iupac_base(chunk[offset + index]).is_none() { + return Err(offset + index); + } + self.push_ascii(chunk[offset + index]); + } + offset += 8; + continue; + } + let low = (c0 & 3) | ((c1 & 3) << 2) | ((c2 & 3) << 4) | ((c3 & 3) << 6); + let high = (c4 & 3) | ((c5 & 3) << 2) | ((c6 & 3) << 4) | ((c7 & 3) << 6); + let validity = ((c0 >> 2) & 1) + | (((c1 >> 2) & 1) << 1) + | (((c2 >> 2) & 1) << 2) + | (((c3 >> 2) & 1) << 3) + | (((c4 >> 2) & 1) << 4) + | (((c5 >> 2) & 1) << 5) + | (((c6 >> 2) & 1) << 6) + | (((c7 >> 2) & 1) << 7); + self.bases.push(low); + self.bases.push(high); + if validity != u8::MAX { + let block_start = + usize::try_from(self.len).expect("sequence exceeds addressable memory"); + for index in 0..8 { + if validity & (1 << index) == 0 { + self.invalid.insert(block_start + index); + } + } + } + let position = self.len; + let (identity_left, identity_right) = advance_identity( + self.identity_left, + self.identity_right, + position, + c0 & 0b111, + ); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 1, c1 & 0b111); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 2, c2 & 0b111); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 3, c3 & 0b111); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 4, c4 & 0b111); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 5, c5 & 0b111); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 6, c6 & 0b111); + let (identity_left, identity_right) = + advance_identity(identity_left, identity_right, position + 7, c7 & 0b111); + self.identity_left = identity_left; + self.identity_right = identity_right; + self.len = position + 8; + offset += 8; + } + + while offset < chunk.len() { + if encode_iupac_base(chunk[offset]).is_none() { + return Err(offset); + } + self.push_ascii(chunk[offset]); + offset += 1; + } + Ok(()) + } + + /// Returns the two-bit base and validity at `position`. + pub fn get(&self, position: u64) -> Option<(u8, bool)> { + if position >= self.len { + return None; + } + let position = usize::try_from(position).ok()?; + let code = (self.bases[position / 4] >> ((position % 4) * 2)) & 0b11; + Some((code, !self.invalid.contains(position))) + } + + /// Returns packed base bytes for persistence or Wasm transfer. + pub fn packed_bases(&self) -> &[u8] { + &self.bases + } + + /// Approximate bytes owned by heap allocations, excluding allocator overhead. + pub fn estimated_heap_bytes(&self) -> usize { + self.name + .capacity() + .saturating_add(self.description.capacity()) + .saturating_add(self.bases.capacity()) + .saturating_add(self.invalid.estimated_heap_bytes()) + } + + /// Releases material geometric growth slack after a streamed FASTA record is complete. + /// Small or proportionally modest slack is retained to avoid copying an entire record + /// merely to recover a negligible amount of memory. + pub(crate) fn compact_storage(&mut self) { + compact_string_if_excessive(&mut self.name); + compact_string_if_excessive(&mut self.description); + compact_vec_if_excessive(&mut self.bases); + self.invalid.compact_storage(); + } + + /// Copies a half-open coordinate range into an independently owned sequence. + /// + /// This bounded helper is used by scientific fixtures and future sequence shards; + /// it preserves invalid-base positions as `N` without materializing the complete + /// source as ASCII. + /// + /// # Panics + /// + /// Panics only if the requested copy cannot fit in addressable memory. + #[must_use] + pub fn subsequence(&self, name: impl Into, start: u64, end: u64) -> Self { + let start = start.min(self.len); + let end = end.min(self.len).max(start); + let mut result = Self::new(name); + for position in start..end { + let (code, valid) = self + .get(position) + .expect("a position clipped to the sequence is present"); + result.push_ascii(if valid { + b"ACGT"[usize::from(code)] + } else { + b'N' + }); + } + result + } + + /// Iterates canonical k-mers whose starts fall in `[start, end)`. + /// + /// K-mers extending beyond the sequence or containing an invalid base are omitted. + /// `k` is restricted to 31 so both orientations fit in 64 bits. + /// + /// # Panics + /// + /// Panics if `k` is not in `1..=31`. + pub fn canonical_kmers(&self, k: u8, start: u64, end: u64) -> CanonicalKmerIter<'_> { + assert!((1..=31).contains(&k), "k must be in 1..=31"); + CanonicalKmerIter::new(self, k, start.min(self.len), end.min(self.len)) + } +} + +#[inline] +fn advance_identity( + identity_left: u64, + identity_right: u64, + position: u64, + identity_token: u8, +) -> (u64, u64) { + let identity_left = + (identity_left ^ u64::from(identity_token)).wrapping_mul(0x0000_0100_0000_01b3); + let identity_right = identity_right + ^ u64::from(identity_token) + .wrapping_add(position.rotate_left(17)) + .wrapping_mul(0x9e37_79b9_7f4a_7c15); + let identity_right = identity_right + .rotate_left(27) + .wrapping_mul(0x94d0_49bb_1331_11eb); + (identity_left, identity_right) +} + +/// Iterator returned by [`PackedSequence::canonical_kmers`]. +pub struct CanonicalKmerIter<'a> { + sequence: &'a PackedSequence, + k: u8, + next_base: u64, + first_start: u64, + end_start: u64, + forward: u64, + reverse: u64, + valid_run: u8, + mask: u64, +} + +impl<'a> CanonicalKmerIter<'a> { + fn new(sequence: &'a PackedSequence, k: u8, start: u64, end: u64) -> Self { + let context_start = start.saturating_sub(u64::from(k - 1)); + Self { + sequence, + k, + next_base: context_start, + first_start: start, + end_start: end, + forward: 0, + reverse: 0, + valid_run: 0, + mask: (1_u64 << (2 * k)) - 1, + } + } +} + +impl Iterator for CanonicalKmerIter<'_> { + type Item = CanonicalKmer; + + fn next(&mut self) -> Option { + while self.next_base < self.sequence.len { + let position = self.next_base; + self.next_base += 1; + let (code, valid) = self.sequence.get(position)?; + + if !valid { + self.forward = 0; + self.reverse = 0; + self.valid_run = 0; + continue; + } + + self.forward = ((self.forward << 2) | u64::from(code)) & self.mask; + let complement = u64::from(3 - code); + self.reverse = (self.reverse >> 2) | (complement << (2 * (self.k - 1))); + self.valid_run = self.valid_run.saturating_add(1).min(self.k); + + if self.valid_run < self.k { + continue; + } + + let kmer_start = position + 1 - u64::from(self.k); + if kmer_start < self.first_start { + continue; + } + if kmer_start >= self.end_start { + return None; + } + + let (bits, orientation) = match self.forward.cmp(&self.reverse) { + std::cmp::Ordering::Less => (self.forward, Orientation::Forward), + std::cmp::Ordering::Greater => (self.reverse, Orientation::Reverse), + std::cmp::Ordering::Equal => (self.forward, Orientation::Both), + }; + return Some(CanonicalKmer { + start: kmer_start, + bits, + orientation, + }); + } + None + } +} + +fn encode_base(base: u8) -> (u8, bool) { + match base.to_ascii_uppercase() { + b'A' => (0, true), + b'C' => (1, true), + b'G' => (2, true), + b'T' | b'U' => (3, true), + _ => (0, false), + } +} + +fn encode_iupac_base(base: u8) -> Option<(u8, bool)> { + let encoded = BASE_ENCODING[usize::from(base)]; + (encoded & UNSUPPORTED_BASE == 0).then_some((encoded & 3, encoded & VALID_BASE != 0)) +} + +const VALID_BASE: u8 = 0b0000_0100; +const UNSUPPORTED_BASE: u8 = 0b1000_0000; +const BASE_ENCODING: [u8; 256] = base_encoding(); + +const fn base_encoding() -> [u8; 256] { + let mut table = [UNSUPPORTED_BASE; 256]; + table[b'A' as usize] = VALID_BASE; + table[b'a' as usize] = VALID_BASE; + table[b'C' as usize] = VALID_BASE | 1; + table[b'c' as usize] = VALID_BASE | 1; + table[b'G' as usize] = VALID_BASE | 2; + table[b'g' as usize] = VALID_BASE | 2; + table[b'T' as usize] = VALID_BASE | 3; + table[b't' as usize] = VALID_BASE | 3; + table[b'U' as usize] = VALID_BASE | 3; + table[b'u' as usize] = VALID_BASE | 3; + let ambiguous = b"RYSWKMBDHVNryswkmbdhvn-."; + let mut index = 0; + while index < ambiguous.len() { + table[ambiguous[index] as usize] = 0; + index += 1; + } + table +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn packed_round_trip_preserves_validity() { + let sequence = PackedSequence::from_ascii("test", b"ACGTNacgt"); + assert_eq!(sequence.len(), 9); + let decoded: Vec<_> = (0..sequence.len()) + .map(|position| sequence.get(position).unwrap()) + .collect(); + assert_eq!(decoded[0], (0, true)); + assert_eq!(decoded[3], (3, true)); + assert_eq!(decoded[4], (0, false)); + assert_eq!(decoded[8], (3, true)); + } + + #[test] + fn sparse_invalid_bits_cross_block_boundaries() { + let mut bits = SparseBitVector::default(); + for position in [ + 0, + SPARSE_BIT_BLOCK_BITS - 1, + SPARSE_BIT_BLOCK_BITS, + 1_000_000, + ] { + bits.insert(position); + } + for position in [ + 0, + SPARSE_BIT_BLOCK_BITS - 1, + SPARSE_BIT_BLOCK_BITS, + 1_000_000, + ] { + assert!(bits.contains(position)); + } + for position in [ + 1, + SPARSE_BIT_BLOCK_BITS - 2, + SPARSE_BIT_BLOCK_BITS + 1, + 999_999, + ] { + assert!(!bits.contains(position)); + } + assert_eq!(bits.blocks.len(), 3); + assert!(bits.estimated_heap_bytes() < 4_096); + } + + #[test] + fn canonical_sequence_does_not_allocate_invalidity_storage() { + let mut sequence = PackedSequence::from_ascii("valid", &vec![b'A'; 1_000_000]); + sequence.compact_storage(); + assert_eq!(sequence.invalid.estimated_heap_bytes(), 0); + assert_eq!(sequence.bases.len(), 250_000); + } + + #[test] + fn completed_record_compaction_retains_modest_slack() { + let mut sequence = PackedSequence::from_ascii("valid", b"ACGT"); + sequence.bases.reserve_exact(4_096); + let capacity = sequence.bases.capacity(); + assert!(!should_compact_capacity::( + sequence.bases.len(), + capacity + )); + + sequence.compact_storage(); + + assert_eq!(sequence.bases.capacity(), capacity); + } + + #[test] + fn completed_record_compaction_reclaims_material_slack() { + let mut sequence = PackedSequence::from_ascii("valid", b"ACGT"); + sequence + .bases + .reserve_exact(STORAGE_COMPACTION_MIN_UNUSED_BYTES + sequence.bases.len()); + let capacity = sequence.bases.capacity(); + assert!(should_compact_capacity::( + sequence.bases.len(), + capacity + )); + + sequence.compact_storage(); + + assert!(sequence.bases.capacity() < capacity); + assert_eq!(sequence.bases, vec![0b1110_0100]); + } + + #[test] + fn bulk_iupac_packing_matches_scalar_packing_at_unaligned_boundaries() { + let mut bulk = PackedSequence::from_ascii("test", b"ACG"); + bulk.push_iupac_chunk(b"TacgUN.-ACGT").unwrap(); + let scalar = PackedSequence::from_ascii("test", b"ACGTacgUN.-ACGT"); + assert_eq!(bulk, scalar); + + let invalid = bulk.push_iupac_chunk(b"ACGT!AAA").unwrap_err(); + assert_eq!(invalid, 4); + assert_eq!(bulk.len(), scalar.len() + 4); + } + + #[test] + fn unrolled_bulk_identity_matches_scalar_at_every_alignment() { + const PREFIX: &[u8] = b"ACGTACG"; + const BODY: &[u8] = b"ACGTURYSWKMBDHVN-.acgtuACGTACGT"; + + for prefix_length in 0..=PREFIX.len() { + let mut bulk = PackedSequence::from_ascii("test", &PREFIX[..prefix_length]); + bulk.push_iupac_chunk(BODY).unwrap(); + + let mut ascii = PREFIX[..prefix_length].to_vec(); + ascii.extend_from_slice(BODY); + let scalar = PackedSequence::from_ascii("test", &ascii); + assert_eq!(bulk, scalar, "identity drift at alignment {prefix_length}"); + } + } + + #[test] + fn sequence_identity_matches_stable_golden() { + const INPUT: &[u8] = b"ACGTURYSWKMBDHVN-.acgtu"; + const EXPECTED: [u8; 16] = [ + 187, 118, 212, 181, 13, 28, 61, 167, 229, 61, 117, 118, 174, 195, 148, 93, + ]; + + let scalar = PackedSequence::from_ascii("scalar", INPUT); + assert_eq!(scalar.identity().as_bytes(), &EXPECTED); + + let mut bulk = PackedSequence::new("bulk"); + bulk.push_iupac_chunk(INPUT).unwrap(); + assert_eq!(bulk.identity().as_bytes(), &EXPECTED); + } + + #[test] + fn invalid_bases_break_kmers_without_changing_coordinates() { + let sequence = PackedSequence::from_ascii("test", b"AAANAAA"); + let starts: Vec<_> = sequence + .canonical_kmers(3, 0, sequence.len()) + .map(|kmer| kmer.start) + .collect(); + assert_eq!(starts, vec![0, 4]); + } + + #[test] + fn reverse_complements_have_identical_canonical_bits() { + let forward = PackedSequence::from_ascii("f", b"AACCG"); + let reverse = PackedSequence::from_ascii("r", b"CGGTT"); + let left = forward.canonical_kmers(5, 0, 1).next().unwrap(); + let right = reverse.canonical_kmers(5, 0, 1).next().unwrap(); + assert_eq!(left.bits, right.bits); + assert_ne!(left.orientation, right.orientation); + } + + #[test] + fn range_uses_kmer_start_coordinates() { + let sequence = PackedSequence::from_ascii("test", b"ACGTACGT"); + let starts: Vec<_> = sequence + .canonical_kmers(3, 2, 5) + .map(|kmer| kmer.start) + .collect(); + assert_eq!(starts, vec![2, 3, 4]); + } + + #[test] + fn subsequence_preserves_bases_and_invalid_positions() { + let sequence = PackedSequence::from_ascii("whole", b"AACGTNCCA"); + let slice = sequence.subsequence("slice", 2, 8); + let expected = PackedSequence::from_ascii("slice", b"CGTNCC"); + assert_eq!(slice, expected); + } + + #[test] + fn uracil_is_normalized_to_thymine_without_coordinate_changes() { + let uracil = PackedSequence::from_ascii("rna", b"aCuUGcUu"); + let thymine = PackedSequence::from_ascii("dna", b"ACTTGCTT"); + assert_eq!(uracil.len(), thymine.len()); + assert_eq!( + uracil + .canonical_kmers(5, 0, uracil.len()) + .collect::>(), + thymine + .canonical_kmers(5, 0, thymine.len()) + .collect::>(), + ); + } +} diff --git a/crates/core/src/fasta.rs b/crates/core/src/fasta.rs new file mode 100644 index 0000000..aee82a2 --- /dev/null +++ b/crates/core/src/fasta.rs @@ -0,0 +1,398 @@ +//! Incremental FASTA parsing without chromosome-scale JavaScript strings. + +use crate::dna::PackedSequence; +use std::fmt; + +/// Supported upper bound for one FASTA record. +pub const MAX_SEQUENCE_LENGTH: u64 = 1_000_000_000; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Mode { + AwaitHeader, + Header, + Sequence, + Comment, +} + +/// A positional FASTA parsing error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FastaError { + /// Human-readable description. + pub message: String, + /// Zero-based byte offset in the input stream. + pub byte_offset: u64, + /// One-based input line. + pub line: u64, +} + +impl fmt::Display for FastaError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + formatter, + "{} at line {}, byte {}", + self.message, self.line, self.byte_offset + ) + } +} + +impl std::error::Error for FastaError {} + +/// Stateful streaming FASTA parser. +/// +/// Completed records are returned as soon as the next header is encountered. Call +/// [`Self::finish`] once at end of input to obtain the final record. +#[derive(Debug)] +pub struct FastaParser { + mode: Mode, + return_mode: Mode, + header: Vec, + current: Option, + at_line_start: bool, + byte_offset: u64, + line: u64, + saw_record: bool, + max_sequence_length: u64, +} + +impl Default for FastaParser { + fn default() -> Self { + Self { + mode: Mode::AwaitHeader, + return_mode: Mode::AwaitHeader, + header: Vec::new(), + current: None, + at_line_start: true, + byte_offset: 0, + line: 1, + saw_record: false, + max_sequence_length: MAX_SEQUENCE_LENGTH, + } + } +} + +impl FastaParser { + /// Creates a new parser. + pub fn new() -> Self { + Self::default() + } + + /// Creates a parser with a custom per-record limit. + /// + /// This is primarily useful for bounded embedding environments and tests. The web + /// application uses [`MAX_SEQUENCE_LENGTH`]. + pub fn with_max_sequence_length(max_sequence_length: u64) -> Self { + Self { + max_sequence_length, + ..Self::default() + } + } + + /// Consumes a byte chunk and returns records completed within it. + /// + /// # Errors + /// + /// Returns [`FastaError`] for malformed headers, invalid sequence bytes, sequence + /// data before the first header, or an empty completed record. + pub fn push_chunk(&mut self, chunk: &[u8]) -> Result, FastaError> { + let mut completed = Vec::new(); + let mut index = 0; + while index < chunk.len() { + let byte = chunk[index]; + let offset = self.byte_offset; + + if self.mode == Mode::Sequence + && !byte.is_ascii_whitespace() + && !(self.at_line_start && matches!(byte, b'>' | b';')) + { + let run_length = chunk[index..] + .iter() + .position(u8::is_ascii_whitespace) + .unwrap_or(chunk.len() - index); + let run = &chunk[index..index + run_length]; + self.push_sequence_run(run, offset)?; + index += run.len(); + continue; + } + + self.byte_offset += 1; + index += 1; + + if byte == b'\r' { + continue; + } + if byte == b'\n' { + self.end_line()?; + continue; + } + + if self.at_line_start && byte.is_ascii_whitespace() { + continue; + } + + if self.at_line_start && byte == b'>' { + if self.mode == Mode::Sequence { + completed.push(self.take_current()?); + } else if self.mode != Mode::AwaitHeader { + return Err(self.error_at("unexpected FASTA header", offset)); + } + self.mode = Mode::Header; + self.header.clear(); + self.at_line_start = false; + continue; + } + + if self.at_line_start && byte == b';' { + self.return_mode = self.mode; + self.mode = Mode::Comment; + self.at_line_start = false; + continue; + } + + self.at_line_start = false; + match self.mode { + Mode::AwaitHeader => { + return Err( + self.error_at("sequence data appeared before a FASTA header", offset) + ); + } + Mode::Header => self.header.push(byte), + Mode::Sequence => { + if byte.is_ascii_whitespace() { + continue; + } + if is_iupac_or_gap(byte) { + let Some(current) = self.current.as_mut() else { + return Err(self.error_at("internal FASTA parser state error", offset)); + }; + if current.len() >= self.max_sequence_length { + return Err(self.error_at( + &format!( + "FASTA record exceeds the supported {}-base limit", + self.max_sequence_length + ), + offset, + )); + } + current.push_ascii(byte); + } else { + return Err(self.error_at( + &format!("invalid FASTA sequence byte 0x{byte:02x}"), + offset, + )); + } + } + Mode::Comment => {} + } + } + Ok(completed) + } + + fn push_sequence_run(&mut self, run: &[u8], offset: u64) -> Result<(), FastaError> { + let Some(current) = self.current.as_mut() else { + return Err(self.error_at("internal FASTA parser state error", offset)); + }; + let remaining = self.max_sequence_length.saturating_sub(current.len()); + let accepted = run + .len() + .min(usize::try_from(remaining).unwrap_or(usize::MAX)); + self.at_line_start = false; + if let Err(relative) = current.push_iupac_chunk(&run[..accepted]) { + self.byte_offset += u64::try_from(relative).unwrap_or(u64::MAX) + 1; + let invalid = run[relative]; + return Err(self.error_at( + &format!("invalid FASTA sequence byte 0x{invalid:02x}"), + offset + u64::try_from(relative).unwrap_or(u64::MAX), + )); + } + self.byte_offset += u64::try_from(accepted).unwrap_or(u64::MAX); + if accepted < run.len() { + self.byte_offset += 1; + return Err(self.error_at( + &format!( + "FASTA record exceeds the supported {}-base limit", + self.max_sequence_length + ), + offset + u64::try_from(accepted).unwrap_or(u64::MAX), + )); + } + Ok(()) + } + + /// Finishes the stream and returns the final completed record. + /// + /// # Errors + /// + /// Returns [`FastaError`] for an empty header, empty final record, or input with no + /// FASTA records. + pub fn finish(mut self) -> Result, FastaError> { + if self.mode == Mode::Header { + self.finish_header()?; + } + + let mut completed = Vec::new(); + if self.mode == Mode::Sequence { + completed.push(self.take_current()?); + } + if !self.saw_record && completed.is_empty() { + return Err(self.error_at("input contained no FASTA records", self.byte_offset)); + } + Ok(completed) + } + + fn end_line(&mut self) -> Result<(), FastaError> { + match self.mode { + Mode::Header => self.finish_header()?, + Mode::Comment => self.mode = self.return_mode, + Mode::AwaitHeader | Mode::Sequence => {} + } + self.line += 1; + self.at_line_start = true; + Ok(()) + } + + fn finish_header(&mut self) -> Result<(), FastaError> { + let header = String::from_utf8_lossy(&self.header).trim().to_owned(); + if header.is_empty() { + return Err(self.error_at("FASTA header is empty", self.byte_offset)); + } + let (identifier, description) = header.find(char::is_whitespace).map_or_else( + || (header.as_str(), ""), + |boundary| (header[..boundary].trim(), header[boundary..].trim()), + ); + if identifier.is_empty() { + return Err(self.error_at("FASTA sequence identifier is empty", self.byte_offset)); + } + self.current = Some(PackedSequence::with_description(identifier, description)); + self.header.clear(); + self.mode = Mode::Sequence; + self.saw_record = true; + Ok(()) + } + + fn take_current(&mut self) -> Result { + let mut sequence = self.current.take().expect("current record is present"); + if sequence.is_empty() { + return Err(self.error_at( + &format!("FASTA record '{}' contains no sequence", sequence.name()), + self.byte_offset, + )); + } + sequence.compact_storage(); + Ok(sequence) + } + + fn error_at(&self, message: &str, byte_offset: u64) -> FastaError { + FastaError { + message: message.to_owned(), + byte_offset, + line: self.line, + } + } +} + +fn is_iupac_or_gap(byte: u8) -> bool { + matches!( + byte.to_ascii_uppercase(), + b'A' | b'C' + | b'G' + | b'T' + | b'U' + | b'R' + | b'Y' + | b'S' + | b'W' + | b'K' + | b'M' + | b'B' + | b'D' + | b'H' + | b'V' + | b'N' + | b'-' + | b'.' + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_multiple_records_across_arbitrary_chunks() { + let mut parser = FastaParser::new(); + let mut records = parser.push_chunk(b">chr").unwrap(); + records.extend(parser.push_chunk(b"1 description\nACG").unwrap()); + records.extend(parser.push_chunk(b"TN\n>chr2\ngg").unwrap()); + records.extend(parser.finish().unwrap()); + + assert_eq!(records.len(), 2); + assert_eq!(records[0].name(), "chr1"); + assert_eq!(records[0].description(), "description"); + assert_eq!(records[0].len(), 5); + assert_eq!(records[1].name(), "chr2"); + assert_eq!(records[1].description(), ""); + assert_eq!(records[1].len(), 2); + } + + #[test] + fn separates_identifier_from_tabbed_and_spaced_descriptions() { + let mut parser = FastaParser::new(); + let records = parser + .push_chunk(b">first\tlong description here\nAC\n>second another description\nGT\n") + .unwrap(); + let mut records = records; + records.extend(parser.finish().unwrap()); + assert_eq!(records[0].name(), "first"); + assert_eq!(records[0].description(), "long description here"); + assert_eq!(records[1].name(), "second"); + assert_eq!(records[1].description(), "another description"); + } + + #[test] + fn ignores_comments_and_blank_lines() { + let mut parser = FastaParser::new(); + let records = parser + .push_chunk(b"; file comment\n\n>chr1\nAC\n; record comment\nGT\n") + .unwrap(); + assert!(records.is_empty()); + let records = parser.finish().unwrap(); + assert_eq!(records[0].len(), 4); + } + + #[test] + fn reports_data_before_header() { + let mut parser = FastaParser::new(); + let error = parser.push_chunk(b"ACGT\n").unwrap_err(); + assert_eq!(error.line, 1); + assert!(error.message.contains("before")); + } + + #[test] + fn rejects_empty_records() { + let mut parser = FastaParser::new(); + let error = parser.push_chunk(b">empty\n>next\nA\n").unwrap_err(); + assert!(error.message.contains("no sequence")); + } + + #[test] + fn rejects_a_record_before_exceeding_its_configured_limit() { + let mut parser = FastaParser::with_max_sequence_length(4); + let error = parser.push_chunk(b">too-long\nACGTA\n").unwrap_err(); + assert!(error.message.contains("4-base limit")); + assert_eq!(error.line, 2); + } + + #[test] + fn streaming_fasta_normalizes_uracil_like_thymine() { + let mut rna_parser = FastaParser::new(); + rna_parser.push_chunk(b">seq\nacU").unwrap(); + rna_parser.push_chunk(b"UGCuu\n").unwrap(); + let rna = rna_parser.finish().unwrap().remove(0); + + let mut dna_parser = FastaParser::new(); + dna_parser.push_chunk(b">seq\nACTTGCTT\n").unwrap(); + let dna = dna_parser.finish().unwrap().remove(0); + + assert_eq!(rna, dna); + } +} diff --git a/crates/core/src/gc.rs b/crates/core/src/gc.rs new file mode 100644 index 0000000..fce7f78 --- /dev/null +++ b/crates/core/src/gc.rs @@ -0,0 +1,460 @@ +//! Compact, queryable sequence-composition summaries for browser feature tracks. + +use crate::dna::PackedSequence; + +/// Default number of bases represented by one cumulative index block. +pub const DEFAULT_COMPOSITION_BLOCK_SIZE: u64 = 1_024; + +/// Incrementally builds a [`CompositionIndex`] without monopolizing a browser worker turn. +#[derive(Clone, Debug)] +pub struct CompositionIndexBuilder { + block_size: u64, + block_count: usize, + next_block: usize, + gc_prefix: Vec, + cytosine_prefix: Vec, + guanine_prefix: Vec, + valid_prefix: Vec, + cpg_prefix: Vec, +} + +impl CompositionIndexBuilder { + /// Creates an empty index builder for `sequence`. + /// + /// # Panics + /// + /// Panics if `block_size` is zero or the resulting index cannot fit in memory. + #[must_use] + pub fn new(sequence: &PackedSequence, block_size: u64) -> Self { + assert!(block_size > 0, "GC block size must be positive"); + let blocks = sequence.len().div_ceil(block_size); + let block_count = usize::try_from(blocks).expect("GC block count fits addressable memory"); + let mut gc_prefix = Vec::with_capacity(block_count.saturating_add(1)); + let mut cytosine_prefix = Vec::with_capacity(block_count.saturating_add(1)); + let mut guanine_prefix = Vec::with_capacity(block_count.saturating_add(1)); + let mut valid_prefix = Vec::with_capacity(block_count.saturating_add(1)); + let mut cpg_prefix = Vec::with_capacity(block_count.saturating_add(1)); + gc_prefix.push(0); + cytosine_prefix.push(0); + guanine_prefix.push(0); + valid_prefix.push(0); + cpg_prefix.push(0); + Self { + block_size, + block_count, + next_block: 0, + gc_prefix, + cytosine_prefix, + guanine_prefix, + valid_prefix, + cpg_prefix, + } + } + + /// Processes at most `maximum_blocks` and returns completion in `[0, 1]`. + /// + /// # Panics + /// + /// Panics if `maximum_blocks` is zero or the builder was created for a longer + /// sequence than the supplied sequence. + pub fn advance(&mut self, sequence: &PackedSequence, maximum_blocks: usize) -> f64 { + assert!(maximum_blocks > 0, "GC preparation chunk must be positive"); + let end_block = self + .next_block + .saturating_add(maximum_blocks) + .min(self.block_count); + let mut gc_total = *self.gc_prefix.last().expect("GC prefix starts at zero"); + let mut cytosine_total = *self + .cytosine_prefix + .last() + .expect("C prefix starts at zero"); + let mut guanine_total = *self.guanine_prefix.last().expect("G prefix starts at zero"); + let mut valid_total = *self + .valid_prefix + .last() + .expect("valid prefix starts at zero"); + let mut cpg_total = *self.cpg_prefix.last().expect("CpG prefix starts at zero"); + for block in self.next_block..end_block { + let start = u64::try_from(block) + .expect("GC block index fits u64") + .saturating_mul(self.block_size); + let end = start.saturating_add(self.block_size).min(sequence.len()); + for position in start..end { + let (code, valid) = sequence + .get(position) + .expect("GC preparation range is clipped to the sequence"); + if valid { + valid_total = valid_total.saturating_add(1); + match code { + 1 => { + cytosine_total = cytosine_total.saturating_add(1); + gc_total = gc_total.saturating_add(1); + } + 2 => { + guanine_total = guanine_total.saturating_add(1); + gc_total = gc_total.saturating_add(1); + } + _ => {} + } + if code == 1 + && sequence + .get(position.saturating_add(1)) + .is_some_and(|(next, next_valid)| next_valid && next == 2) + { + cpg_total = cpg_total.saturating_add(1); + } + } + } + self.gc_prefix.push(gc_total); + self.cytosine_prefix.push(cytosine_total); + self.guanine_prefix.push(guanine_total); + self.valid_prefix.push(valid_total); + self.cpg_prefix.push(cpg_total); + } + self.next_block = end_block; + if self.block_count == 0 { + 1.0 + } else { + let completed = + u32::try_from(self.next_block).expect("supported GC block count fits u32"); + let total = u32::try_from(self.block_count).expect("supported GC block count fits u32"); + f64::from(completed) / f64::from(total) + } + } + + /// Returns true after every sequence block has been processed. + #[must_use] + pub fn is_complete(&self) -> bool { + self.next_block == self.block_count + } + + /// Approximate bytes owned by the partially built prefix vectors. + #[must_use] + pub fn estimated_heap_bytes(&self) -> usize { + self.gc_prefix + .capacity() + .saturating_add(self.cytosine_prefix.capacity()) + .saturating_add(self.guanine_prefix.capacity()) + .saturating_add(self.valid_prefix.capacity()) + .saturating_add(self.cpg_prefix.capacity()) + .saturating_mul(std::mem::size_of::()) + } + + /// Converts a completed builder into a queryable index. + #[must_use] + pub fn finish(self) -> Option { + self.is_complete().then_some(CompositionIndex { + block_size: self.block_size, + gc_prefix: self.gc_prefix, + cytosine_prefix: self.cytosine_prefix, + guanine_prefix: self.guanine_prefix, + valid_prefix: self.valid_prefix, + cpg_prefix: self.cpg_prefix, + }) + } +} + +/// Cumulative GC and canonical-base counts at fixed sequence block boundaries. +#[derive(Clone, Debug)] +pub struct CompositionIndex { + block_size: u64, + gc_prefix: Vec, + cytosine_prefix: Vec, + guanine_prefix: Vec, + valid_prefix: Vec, + cpg_prefix: Vec, +} + +impl CompositionIndex { + /// Returns one exact GC fraction per equal genomic window in `[start, end)`. + /// + /// Ambiguous bases are excluded from the denominator. Windows with less than + /// 25% canonical sequence are returned as `NaN` so the renderer can leave them blank. + #[must_use] + pub fn sample(&self, sequence: &PackedSequence, start: u64, end: u64, bins: usize) -> Vec { + self.sample_gc(sequence, start, end, bins) + } + + /// Returns one exact GC fraction per equal genomic window in `[start, end)`. + #[must_use] + pub fn sample_gc( + &self, + sequence: &PackedSequence, + start: u64, + end: u64, + bins: usize, + ) -> Vec { + if bins == 0 { + return Vec::new(); + } + let start = start.min(sequence.len()); + let end = end.min(sequence.len()).max(start); + let span = end - start; + let mut output = Vec::with_capacity(bins); + for bin in 0..bins { + let bin_start = partition_coordinate(start, span, bin, bins); + let bin_end = partition_coordinate(start, span, bin + 1, bins); + if bin_end <= bin_start { + output.push(f64::NAN); + continue; + } + let (gc, valid) = self.range_counts(sequence, bin_start, bin_end); + let window_length = bin_end - bin_start; + if u64::from(valid).saturating_mul(4) < window_length { + output.push(f64::NAN); + } else { + output.push(f64::from(gc) / f64::from(valid.max(1))); + } + } + output + } + + /// Returns the `CpG` observed/expected ratio for equal windows in `[start, end)`. + /// + /// The expected count is `C * G / N`, so the returned ratio is + /// `CpG * N / (C * G)`. Ambiguous bases are excluded from `N`; windows with + /// less than 25% canonical sequence or no C/G denominator are missing. + #[must_use] + pub fn sample_cpg_observed_expected( + &self, + sequence: &PackedSequence, + start: u64, + end: u64, + bins: usize, + ) -> Vec { + if bins == 0 { + return Vec::new(); + } + let start = start.min(sequence.len()); + let end = end.min(sequence.len()).max(start); + let span = end - start; + let mut output = Vec::with_capacity(bins); + for bin in 0..bins { + let bin_start = partition_coordinate(start, span, bin, bins); + let bin_end = partition_coordinate(start, span, bin + 1, bins); + if bin_end.saturating_sub(bin_start) < 2 { + output.push(f64::NAN); + continue; + } + let start_counts = self.prefix_counts(sequence, bin_start); + let end_counts = self.prefix_counts(sequence, bin_end); + let cpg_end = self.prefix_counts(sequence, bin_end.saturating_sub(1)).cpg; + let valid = end_counts.valid - start_counts.valid; + let c = end_counts.c - start_counts.c; + let g = end_counts.g - start_counts.g; + let cpg = cpg_end - start_counts.cpg; + let window_length = bin_end - bin_start; + if u64::from(valid).saturating_mul(4) < window_length || c == 0 || g == 0 { + output.push(f64::NAN); + } else { + output.push(f64::from(cpg) * f64::from(valid) / (f64::from(c) * f64::from(g))); + } + } + output + } + + /// Approximate bytes owned by the index vectors. + #[must_use] + pub fn estimated_heap_bytes(&self) -> usize { + self.gc_prefix + .capacity() + .saturating_add(self.cytosine_prefix.capacity()) + .saturating_add(self.guanine_prefix.capacity()) + .saturating_add(self.valid_prefix.capacity()) + .saturating_add(self.cpg_prefix.capacity()) + .saturating_mul(std::mem::size_of::()) + } + + fn range_counts(&self, sequence: &PackedSequence, start: u64, end: u64) -> (u32, u32) { + let first_full = start.div_ceil(self.block_size); + let last_full = end / self.block_size; + if first_full >= last_full { + return scan_counts(sequence, start, end); + } + + let first_index = usize::try_from(first_full).expect("GC block index fits usize"); + let last_index = usize::try_from(last_full).expect("GC block index fits usize"); + let mut gc = self.gc_prefix[last_index] - self.gc_prefix[first_index]; + let mut valid = self.valid_prefix[last_index] - self.valid_prefix[first_index]; + + let left_end = first_full.saturating_mul(self.block_size).min(end); + let right_start = last_full.saturating_mul(self.block_size).max(start); + let (left_gc, left_valid) = scan_counts(sequence, start, left_end); + let (right_gc, right_valid) = scan_counts(sequence, right_start, end); + gc = gc.saturating_add(left_gc).saturating_add(right_gc); + valid = valid.saturating_add(left_valid).saturating_add(right_valid); + (gc, valid) + } + + fn prefix_counts(&self, sequence: &PackedSequence, coordinate: u64) -> CompositionCounts { + let coordinate = coordinate.min(sequence.len()); + let block = usize::try_from(coordinate / self.block_size) + .expect("composition block index fits usize") + .min(self.gc_prefix.len().saturating_sub(1)); + let block_start = u64::try_from(block) + .expect("composition block index fits u64") + .saturating_mul(self.block_size); + let mut counts = CompositionCounts { + gc: self.gc_prefix[block], + c: self.cytosine_prefix[block], + g: self.guanine_prefix[block], + valid: self.valid_prefix[block], + cpg: self.cpg_prefix[block], + }; + for position in block_start..coordinate { + let (code, valid) = sequence + .get(position) + .expect("composition prefix range is clipped to the sequence"); + if !valid { + continue; + } + counts.valid = counts.valid.saturating_add(1); + match code { + 1 => { + counts.c = counts.c.saturating_add(1); + counts.gc = counts.gc.saturating_add(1); + } + 2 => { + counts.g = counts.g.saturating_add(1); + counts.gc = counts.gc.saturating_add(1); + } + _ => {} + } + if code == 1 + && sequence + .get(position.saturating_add(1)) + .is_some_and(|(next, next_valid)| next_valid && next == 2) + { + counts.cpg = counts.cpg.saturating_add(1); + } + } + counts + } +} + +#[derive(Clone, Copy, Debug, Default)] +struct CompositionCounts { + gc: u32, + c: u32, + g: u32, + valid: u32, + cpg: u32, +} + +fn partition_coordinate(start: u64, span: u64, bin: usize, bins: usize) -> u64 { + let offset = u128::from(span).saturating_mul(bin as u128) / bins as u128; + start.saturating_add(u64::try_from(offset).expect("partition offset fits u64")) +} + +fn scan_counts(sequence: &PackedSequence, start: u64, end: u64) -> (u32, u32) { + let mut gc = 0_u32; + let mut valid = 0_u32; + for position in start..end { + let (code, is_valid) = sequence + .get(position) + .expect("GC query range is clipped to the sequence"); + if is_valid { + valid = valid.saturating_add(1); + if code == 1 || code == 2 { + gc = gc.saturating_add(1); + } + } + } + (gc, valid) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn index(sequence: &PackedSequence, block_size: u64) -> CompositionIndex { + let mut builder = CompositionIndexBuilder::new(sequence, block_size); + while !builder.is_complete() { + builder.advance(sequence, 2); + } + builder.finish().expect("completed GC index") + } + + #[test] + fn samples_exact_unsmoothed_windows_across_block_boundaries() { + let sequence = PackedSequence::from_ascii("gc", b"AAAACCCCGGGGTTTT"); + let result = index(&sequence, 3).sample(&sequence, 0, sequence.len(), 4); + assert!( + result + .iter() + .zip([0.0, 1.0, 1.0, 0.0]) + .all(|(observed, expected)| (observed - expected).abs() < f64::EPSILON) + ); + } + + #[test] + fn excludes_ambiguous_bases_and_marks_low_coverage_windows_missing() { + let sequence = PackedSequence::from_ascii("ambiguous", b"GCNNNNNNATNNNNNN"); + let result = index(&sequence, 4).sample(&sequence, 0, sequence.len(), 2); + assert!((result[0] - 1.0).abs() < f64::EPSILON); + assert!(result[1].abs() < f64::EPSILON); + let missing = index(&sequence, 4).sample(&sequence, 2, 8, 1); + assert!(missing[0].is_nan()); + } + + #[test] + fn handles_partial_terminal_windows_and_more_bins_than_bases() { + let sequence = PackedSequence::from_ascii("short", b"ACGTA"); + let index = index(&sequence, 2); + let partial = index.sample(&sequence, 0, 5, 2); + assert!((partial[0] - 0.5).abs() < f64::EPSILON); + assert!((partial[1] - 1.0 / 3.0).abs() < f64::EPSILON); + let oversampled = index.sample(&sequence, 0, 2, 4); + assert!(oversampled[0].is_nan()); + assert!(oversampled[1].abs() < f64::EPSILON); + assert!(oversampled[2].is_nan()); + assert!((oversampled[3] - 1.0).abs() < f64::EPSILON); + } + + #[test] + fn computes_cpg_observed_expected_across_index_blocks() { + let sequence = PackedSequence::from_ascii("cpg", b"AACGCGTTCCGG"); + let index = index(&sequence, 3); + let ratios = index.sample_cpg_observed_expected(&sequence, 2, 12, 2); + assert!((ratios[0] - 2.5).abs() < f64::EPSILON); + assert!((ratios[1] - 1.25).abs() < f64::EPSILON); + } + + #[test] + fn cpg_index_matches_a_direct_window_count_at_every_boundary() { + let bases = b"ACGTCGCCGATGCGTACCGG"; + let sequence = PackedSequence::from_ascii("differential", bases); + for block_size in 1..=7 { + let index = index(&sequence, block_size); + for start in 0..bases.len() - 1 { + for end in start + 2..=bases.len() { + let observed = + index.sample_cpg_observed_expected(&sequence, start as u64, end as u64, 1) + [0]; + let window = &bases[start..end]; + let (cytosines, guanines) = window.iter().fold( + (0_u32, 0_u32), + |(cytosine_count, guanine_count), base| { + ( + cytosine_count + u32::from(*base == b'C'), + guanine_count + u32::from(*base == b'G'), + ) + }, + ); + let cpgs = + u32::try_from(window.windows(2).filter(|pair| *pair == b"CG").count()) + .expect("test window length fits u32"); + if cytosines == 0 || guanines == 0 { + assert!(observed.is_nan()); + } else { + let window_length = + u32::try_from(window.len()).expect("test window length fits u32"); + let expected = f64::from(cpgs) * f64::from(window_length) + / f64::from(cytosines * guanines); + assert!((observed - expected).abs() < f64::EPSILON); + } + } + } + } + } +} diff --git a/crates/core/src/hash.rs b/crates/core/src/hash.rs new file mode 100644 index 0000000..156792e --- /dev/null +++ b/crates/core/src/hash.rs @@ -0,0 +1,341 @@ +//! Deterministic canonical ntHash2 stream used by the production estimator. + +use crate::dna::{Orientation, PackedSequence}; + +/// Selected production hash stream. +/// +/// The enum remains part of scientific provenance so a future hash change requires an +/// explicit configuration-version change. No runtime-selectable alternative is shipped. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum HashAlgorithm { + /// Official ntHash2 canonical split-rotation hash. + NtHash2 = 1, +} + +impl HashAlgorithm { + /// Stable identifier used by configuration digests and provenance. + pub const fn as_str(self) -> &'static str { + match self { + Self::NtHash2 => "nthash2", + } + } +} + +/// One coordinate-preserving canonical hash. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PositionedHash { + /// Zero-based k-mer start coordinate. + pub start: u64, + /// Strand-invariant 64-bit hash. + pub hash: u64, + /// Orientation used for forward/reverse match classification. + pub orientation: Orientation, +} + +/// Streams canonical ntHash2 hashes for k-mer starts in `[start, end)`. +/// +/// Seed zero returns the exact published ntHash2 canonical stream and is the only +/// production setting. Nonzero seeds apply `fmix64(hash ^ seed)` and exist solely for +/// multi-seed statistical validation; the direct production iterator pays no extra +/// finalizer or per-k-mer seed branch. +/// +/// # Panics +/// +/// Panics if `k` is outside `1..=31`. +pub fn canonical_hashes( + sequence: &PackedSequence, + k: u8, + start: u64, + end: u64, + algorithm: HashAlgorithm, + seed: u64, +) -> CanonicalHashIter<'_> { + match algorithm { + HashAlgorithm::NtHash2 if seed == 0 => { + CanonicalHashIter::NtHash2(NtHash2Iter::new(sequence, k, start, end)) + } + HashAlgorithm::NtHash2 => CanonicalHashIter::SeededNtHash2 { + inner: NtHash2Iter::new(sequence, k, start, end), + seed, + }, + } +} + +/// Direct production ntHash2 stream or a validation-only seeded permutation of it. +pub enum CanonicalHashIter<'a> { + /// Exact published ntHash2 output used by production seed zero. + NtHash2(NtHash2Iter<'a>), + /// Seeded avalanche used only to sample independent validation layouts. + SeededNtHash2 { + /// Underlying exact published ntHash2 stream. + inner: NtHash2Iter<'a>, + /// Nonzero validation-only avalanche seed. + seed: u64, + }, +} + +impl Iterator for CanonicalHashIter<'_> { + type Item = PositionedHash; + + fn next(&mut self) -> Option { + match self { + Self::NtHash2(inner) => inner.next(), + Self::SeededNtHash2 { inner, seed } => inner.next().map(|mut item| { + item.hash = fmix64(item.hash ^ *seed); + item + }), + } + } +} + +const NTHASH_SEEDS: [u64; 4] = [ + 0x3c8b_fbb3_95c6_0474, + 0x3193_c185_62a0_2b4c, + 0x2032_3ed0_8257_2324, + 0x2955_49f5_4be2_4456, +]; + +/// Rolling ntHash2 state over packed DNA. +pub struct NtHash2Iter<'a> { + sequence: &'a PackedSequence, + k: u8, + next_base: u64, + first_start: u64, + end_start: u64, + forward_bits: u64, + reverse_bits: u64, + forward_hash: u64, + reverse_hash: u64, + valid_run: u8, + mask: u64, +} + +impl<'a> NtHash2Iter<'a> { + fn new(sequence: &'a PackedSequence, k: u8, start: u64, end: u64) -> Self { + assert!((1..=31).contains(&k), "k must be in 1..=31"); + let start = start.min(sequence.len()); + Self { + sequence, + k, + next_base: start.saturating_sub(u64::from(k - 1)), + first_start: start, + end_start: end.min(sequence.len()), + forward_bits: 0, + reverse_bits: 0, + forward_hash: 0, + reverse_hash: 0, + valid_run: 0, + mask: (1_u64 << (2 * k)) - 1, + } + } + + fn reset(&mut self) { + self.forward_bits = 0; + self.reverse_bits = 0; + self.forward_hash = 0; + self.reverse_hash = 0; + self.valid_run = 0; + } +} + +impl Iterator for NtHash2Iter<'_> { + type Item = PositionedHash; + + fn next(&mut self) -> Option { + while self.next_base < self.sequence.len() { + let position = self.next_base; + self.next_base += 1; + let (code, valid) = self.sequence.get(position)?; + if !valid { + self.reset(); + continue; + } + + if self.valid_run < self.k { + self.forward_hash = nthash_srol(self.forward_hash) ^ nthash_seed(code); + self.reverse_hash ^= + nthash_srol_by(nthash_seed(3 - code), u32::from(self.valid_run)); + self.valid_run += 1; + } else { + let outgoing_position = position - u64::from(self.k); + let (outgoing, outgoing_valid) = self + .sequence + .get(outgoing_position) + .expect("the outgoing rolling base is within the sequence"); + debug_assert!(outgoing_valid); + self.forward_hash = nthash_srol(self.forward_hash) + ^ nthash_srol_by(nthash_seed(outgoing), u32::from(self.k)) + ^ nthash_seed(code); + self.reverse_hash ^= nthash_srol_by(nthash_seed(3 - code), u32::from(self.k)); + self.reverse_hash ^= nthash_seed(3 - outgoing); + self.reverse_hash = nthash_sror(self.reverse_hash); + } + + self.forward_bits = ((self.forward_bits << 2) | u64::from(code)) & self.mask; + self.reverse_bits = + (self.reverse_bits >> 2) | (u64::from(3 - code) << (2 * (self.k - 1))); + if self.valid_run < self.k { + continue; + } + + let kmer_start = position + 1 - u64::from(self.k); + if kmer_start < self.first_start { + continue; + } + if kmer_start >= self.end_start { + return None; + } + let orientation = match self.forward_bits.cmp(&self.reverse_bits) { + std::cmp::Ordering::Less => Orientation::Forward, + std::cmp::Ordering::Greater => Orientation::Reverse, + std::cmp::Ordering::Equal => Orientation::Both, + }; + return Some(PositionedHash { + start: kmer_start, + hash: self.forward_hash.wrapping_add(self.reverse_hash), + orientation, + }); + } + None + } +} + +fn nthash_seed(code: u8) -> u64 { + NTHASH_SEEDS[usize::from(code)] +} + +/// ntHash2 split-rotates independent 33- and 31-bit subwords. +fn nthash_srol(value: u64) -> u64 { + let wrapped = ((value & 0x8000_0000_0000_0000) >> 30) | ((value & 0x1_0000_0000) >> 32); + ((value << 1) & 0xffff_fffd_ffff_ffff) | wrapped +} + +fn nthash_srol_by(value: u64, distance: u32) -> u64 { + if distance == 0 { + return value; + } + debug_assert!(distance <= 31); + let rotated = value.rotate_left(distance); + let crossing = (rotated ^ (rotated >> 33)) & (u64::MAX >> (64 - distance)); + rotated ^ (crossing | (crossing << 33)) +} + +fn nthash_sror(value: u64) -> u64 { + let wrapped = ((value & 0x2_0000_0000) << 30) | ((value & 1) << 32); + ((value >> 1) & 0xffff_fffe_ffff_ffff) | wrapped +} + +/// `MurmurHash3`'s `fmix64` finalizer, retained only for internal decorrelation. +pub(crate) fn fmix64(mut value: u64) -> u64 { + value ^= value >> 33; + value = value.wrapping_mul(0xff51_afd7_ed55_8ccd); + value ^= value >> 33; + value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53); + value ^ (value >> 33) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn nthash2_matches_official_commit_c26bd45_vectors() { + let sequence = PackedSequence::from_ascii("official", b"ATCGTACGATGCATGCATGCTGACG"); + let observed = canonical_hashes(&sequence, 6, 0, sequence.len(), HashAlgorithm::NtHash2, 0) + .map(|item| item.hash) + .collect::>(); + let expected = [ + 0x245f_4291_74d6_e9b1, + 0xadc1_da5b_636f_030c, + 0x9597_8274_7cd4_3a12, + 0xadc1_da5b_636f_030c, + 0x245f_4291_74d6_e9b1, + 0x2879_d1c7_55ac_3c6d, + 0x1f0b_21c5_3d08_9b2c, + 0x8f39_107f_fe94_2fd4, + ]; + assert_eq!(&observed[..expected.len()], expected); + } + + #[test] + fn reverse_complements_have_equal_hashes_and_opposite_strands() { + let forward = PackedSequence::from_ascii("forward", b"GATTACAGATTACAGATTACA"); + let reverse = PackedSequence::from_ascii("reverse", b"TGTAATCTGTAATCTGTAATC"); + let left = canonical_hashes(&forward, 21, 0, 1, HashAlgorithm::NtHash2, 0) + .next() + .unwrap(); + let right = canonical_hashes(&reverse, 21, 0, 1, HashAlgorithm::NtHash2, 0) + .next() + .unwrap(); + assert_eq!(left.hash, right.hash); + assert_ne!(left.orientation, right.orientation); + } + + #[test] + fn validation_seed_changes_layout_without_changing_canonical_strand() { + let forward = PackedSequence::from_ascii("forward", b"GATTACAGATTACAGATTACA"); + let reverse = PackedSequence::from_ascii("reverse", b"TGTAATCTGTAATCTGTAATC"); + let direct = canonical_hashes(&forward, 21, 0, 1, HashAlgorithm::NtHash2, 0) + .next() + .unwrap(); + let seeded = canonical_hashes(&forward, 21, 0, 1, HashAlgorithm::NtHash2, 17) + .next() + .unwrap(); + let seeded_reverse = canonical_hashes(&reverse, 21, 0, 1, HashAlgorithm::NtHash2, 17) + .next() + .unwrap(); + assert_ne!(direct.hash, seeded.hash); + assert_eq!(seeded.hash, seeded_reverse.hash); + assert_ne!(seeded.orientation, seeded_reverse.orientation); + } + + #[test] + fn nthash2_range_and_ambiguity_match_coordinate_semantics() { + let sequence = PackedSequence::from_ascii("range", b"ACGTACGTNACGTACGTACGT"); + let all = canonical_hashes(&sequence, 5, 0, sequence.len(), HashAlgorithm::NtHash2, 0) + .collect::>(); + let range = + canonical_hashes(&sequence, 5, 10, 14, HashAlgorithm::NtHash2, 0).collect::>(); + let expected = all + .into_iter() + .filter(|item| (10..14).contains(&item.start)) + .collect::>(); + assert_eq!(range, expected); + } + + #[test] + fn nthash2_has_uniform_oph_buckets_without_observed_collisions() { + let mut state = 0x6a09_e667_f3bc_c909_u64; + let mut ascii = Vec::with_capacity(131_072); + for _ in 0..ascii.capacity() { + state = state + .wrapping_mul(2_862_933_555_777_941_757) + .wrapping_add(3_037_000_493); + ascii.push(b"ACGT"[((state >> 61) & 3) as usize]); + } + let sequence = PackedSequence::from_ascii("uniformity", &ascii); + let mut buckets = vec![0_u32; 1_024]; + let mut observed = HashMap::::new(); + let kmers = sequence.canonical_kmers(21, 0, sequence.len()); + let hashes = canonical_hashes(&sequence, 21, 0, sequence.len(), HashAlgorithm::NtHash2, 0); + for (kmer, item) in kmers.zip(hashes) { + let bucket = usize::try_from(item.hash >> 54).expect("ten bits fit usize"); + buckets[bucket] += 1; + if let Some(previous) = observed.insert(item.hash, kmer.bits) { + assert_eq!(previous, kmer.bits, "64-bit hash collision"); + } + } + let total = buckets.iter().sum::(); + let expected = f64::from(total) / 1_024.0; + let chi_square = buckets + .into_iter() + .map(|count| { + let delta = f64::from(count) - expected; + delta * delta / expected + }) + .sum::(); + assert!(chi_square < 1_350.0, "nonuniform OPH buckets: {chi_square}"); + } +} diff --git a/crates/core/src/kmer.rs b/crates/core/src/kmer.rs new file mode 100644 index 0000000..b4adb2e --- /dev/null +++ b/crates/core/src/kmer.rs @@ -0,0 +1,651 @@ +//! Exact canonical k-mer dotplot tiles for maximum zoom. + +use crate::dna::{Orientation, PackedSequence}; +use crate::matrix::{MISSING_IDENTITY, MatrixTile, TileBuildError}; + +/// Geometry used to display equal canonical k-mer pairs in an exact tile. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum KmerTileGeometry { + /// Paint every base in the complete `k`-base alignment footprint. + #[default] + Footprints, + /// Paint only the pair of k-mer start coordinates. + Anchors, +} + +/// Request for a row-major exact k-mer tile. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct KmerTileRequest { + /// Shared padded coordinate domain. + pub domain_length: u64, + /// First x-axis k-mer start. + pub x_start: u64, + /// First y-axis k-mer start. + pub y_start: u64, + /// Requested tile width. + pub width: usize, + /// Requested tile height. + pub height: usize, + /// Exact canonical k-mer length. + pub k: u8, + /// Geometry used to paint equal canonical k-mers. + pub geometry: KmerTileGeometry, +} + +/// Computes an exact canonical k-mer equality tile. +/// +/// Equal canonical k-mers receive identity 1. [`KmerTileGeometry::Footprints`] +/// paints their complete `k`-base alignment footprint, while +/// [`KmerTileGeometry::Anchors`] paints only the k-mer start pair. Opposite +/// canonical orientations use reverse direction evidence; unequal valid k-mer +/// starts receive identity 0. Missing, padded, or ambiguity-overlapping positions +/// use the missing sentinel unless covered by a valid exact match. +/// +/// # Errors +/// +/// Returns [`TileBuildError`] before allocation for an invalid domain, k-mer length, +/// coordinate, or output size. +pub fn compute_kmer_tile( + x: &PackedSequence, + y: &PackedSequence, + request: KmerTileRequest, +) -> Result { + if request.domain_length == 0 || !(1..=31).contains(&request.k) { + return Err(TileBuildError::Parameters); + } + if request.x_start >= request.domain_length || request.y_start >= request.domain_length { + return Err(TileBuildError::Range); + } + if request.width == 0 || request.height == 0 { + return Err(TileBuildError::Dimensions); + } + let width = request + .width + .min(usize::try_from(request.domain_length - request.x_start).unwrap_or(usize::MAX)); + let height = request + .height + .min(usize::try_from(request.domain_length - request.y_start).unwrap_or(usize::MAX)); + let cell_count = width + .checked_mul(height) + .ok_or(TileBuildError::Dimensions)?; + let mut tile = MatrixTile { + width, + height, + identity: vec![MISSING_IDENTITY; cell_count], + direction: vec![0; cell_count], + direction_support: vec![0; cell_count], + }; + let x_kmers = exact_range(x, request.x_start, width, request.k)?; + let y_kmers = exact_range(y, request.y_start, height, request.k)?; + for (row, right) in y_kmers.iter().enumerate() { + let Some((right_bits, _)) = right else { + continue; + }; + for (column, left) in x_kmers.iter().enumerate() { + let Some((left_bits, _)) = left else { + continue; + }; + if left_bits != right_bits { + tile.identity[row * width + column] = 0; + } + } + } + + match request.geometry { + KmerTileGeometry::Footprints => paint_match_footprints(x, y, request, &mut tile)?, + KmerTileGeometry::Anchors => paint_match_anchors(&x_kmers, &y_kmers, &mut tile), + } + Ok(tile) +} + +fn paint_match_anchors( + x_kmers: &[Option<(u64, Orientation)>], + y_kmers: &[Option<(u64, Orientation)>], + tile: &mut MatrixTile, +) { + for (row, right) in y_kmers.iter().enumerate() { + let Some((right_bits, right_orientation)) = right else { + continue; + }; + for (column, left) in x_kmers.iter().enumerate() { + let Some((left_bits, left_orientation)) = left else { + continue; + }; + if left_bits != right_bits { + continue; + } + let output = row * tile.width + column; + tile.identity[output] = 10_000; + tile.direction_support[output] = 1; + tile.direction[output] = + match alignment_footprints(*left_orientation, *right_orientation) { + [ + AlignmentFootprint::Forward(direction) + | AlignmentFootprint::Reverse(direction), + ] => direction_to_i16(*direction), + _ => 0, + }; + } + } +} + +fn paint_match_footprints( + x: &PackedSequence, + y: &PackedSequence, + request: KmerTileRequest, + tile: &mut MatrixTile, +) -> Result<(), TileBuildError> { + let flank = u64::from(request.k - 1); + let x_origin_start = request.x_start.saturating_sub(flank); + let y_origin_start = request.y_start.saturating_sub(flank); + let x_output_end = request + .x_start + .saturating_add(u64::try_from(tile.width).map_err(|_| TileBuildError::Dimensions)?); + let y_output_end = request + .y_start + .saturating_add(u64::try_from(tile.height).map_err(|_| TileBuildError::Dimensions)?); + let x_origin_count = + usize::try_from(x_output_end - x_origin_start).map_err(|_| TileBuildError::Dimensions)?; + let y_origin_count = + usize::try_from(y_output_end - y_origin_start).map_err(|_| TileBuildError::Dimensions)?; + let x_origins = exact_range(x, x_origin_start, x_origin_count, request.k)?; + let y_origins = exact_range(y, y_origin_start, y_origin_count, request.k)?; + let mut direction_balance = vec![0_i32; tile.identity.len()]; + + for (right_offset, right) in y_origins.iter().enumerate() { + let Some((right_bits, right_orientation)) = right else { + continue; + }; + let right_start = y_origin_start + .saturating_add(u64::try_from(right_offset).map_err(|_| TileBuildError::Range)?); + for (left_offset, left) in x_origins.iter().enumerate() { + let Some((left_bits, left_orientation)) = left else { + continue; + }; + if left_bits != right_bits { + continue; + } + let left_start = x_origin_start + .saturating_add(u64::try_from(left_offset).map_err(|_| TileBuildError::Range)?); + for footprint in alignment_footprints(*left_orientation, *right_orientation) { + for offset in 0..u64::from(request.k) { + let x_position = left_start + offset; + let (y_position, direction) = match footprint { + AlignmentFootprint::Forward(direction) => { + (right_start + offset, *direction) + } + AlignmentFootprint::Reverse(direction) => { + (right_start + u64::from(request.k - 1) - offset, *direction) + } + }; + if x_position < request.x_start + || x_position >= x_output_end + || y_position < request.y_start + || y_position >= y_output_end + { + continue; + } + let column = usize::try_from(x_position - request.x_start) + .map_err(|_| TileBuildError::Range)?; + let row = usize::try_from(y_position - request.y_start) + .map_err(|_| TileBuildError::Range)?; + let output = row * tile.width + column; + tile.identity[output] = 10_000; + tile.direction_support[output] = + tile.direction_support[output].saturating_add(1); + direction_balance[output] += direction; + } + } + } + } + + for (index, support) in tile.direction_support.iter().copied().enumerate() { + if support == 0 { + continue; + } + let scaled = i64::from(direction_balance[index]) * i64::from(i16::MAX) / i64::from(support); + tile.direction[index] = + i16::try_from(scaled).unwrap_or(if scaled < 0 { i16::MIN } else { i16::MAX }); + } + Ok(()) +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum AlignmentFootprint { + Forward(i32), + Reverse(i32), +} + +fn alignment_footprints(left: Orientation, right: Orientation) -> &'static [AlignmentFootprint] { + const FORWARD: &[AlignmentFootprint] = &[AlignmentFootprint::Forward(1)]; + const REVERSE: &[AlignmentFootprint] = &[AlignmentFootprint::Reverse(-1)]; + const PALINDROMIC: &[AlignmentFootprint] = &[ + AlignmentFootprint::Forward(0), + AlignmentFootprint::Reverse(0), + ]; + const NONE: &[AlignmentFootprint] = &[]; + + match (left, right) { + (Orientation::Forward, Orientation::Forward) + | (Orientation::Reverse, Orientation::Reverse) => FORWARD, + (Orientation::Forward, Orientation::Reverse) + | (Orientation::Reverse, Orientation::Forward) => REVERSE, + // Canonical bits can only be palindromic on both axes simultaneously. + // Treat a mixed state conservatively if a future producer violates that + // invariant: both alignment geometries are possible and neither supplies + // signed direction evidence. + (Orientation::Both, _) | (_, Orientation::Both) => PALINDROMIC, + (Orientation::Unknown, _) | (_, Orientation::Unknown) => NONE, + } +} + +fn direction_to_i16(direction: i32) -> i16 { + match direction.cmp(&0) { + std::cmp::Ordering::Less => -i16::MAX, + std::cmp::Ordering::Equal => 0, + std::cmp::Ordering::Greater => i16::MAX, + } +} + +fn exact_range( + sequence: &PackedSequence, + start: u64, + count: usize, + k: u8, +) -> Result>, TileBuildError> { + let count_u64 = u64::try_from(count).map_err(|_| TileBuildError::Dimensions)?; + let end = start.saturating_add(count_u64); + let mut result = vec![None; count]; + for kmer in sequence.canonical_kmers(k, start, end) { + let local = usize::try_from(kmer.start - start).map_err(|_| TileBuildError::Range)?; + if let Some(cell) = result.get_mut(local) { + *cell = Some((kmer.bits, kmer.orientation)); + } + } + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + const PERIODIC_LENGTH: usize = 72; + const PERIODIC_K: u8 = 21; + + fn periodic_sequence() -> PackedSequence { + PackedSequence::from_ascii("periodic", "ACGT".repeat(18).as_bytes()) + } + + fn periodic_tile(geometry: KmerTileGeometry) -> MatrixTile { + let sequence = periodic_sequence(); + compute_kmer_tile( + &sequence, + &sequence, + KmerTileRequest { + domain_length: PERIODIC_LENGTH as u64, + x_start: 0, + y_start: 0, + width: PERIODIC_LENGTH, + height: PERIODIC_LENGTH, + k: PERIODIC_K, + geometry, + }, + ) + .unwrap() + } + + fn stitch_periodic_tiles( + geometry: KmerTileGeometry, + x_boundaries: &[usize], + y_boundaries: &[usize], + ) -> MatrixTile { + let sequence = periodic_sequence(); + let cell_count = PERIODIC_LENGTH * PERIODIC_LENGTH; + let mut stitched = MatrixTile { + width: PERIODIC_LENGTH, + height: PERIODIC_LENGTH, + identity: vec![MISSING_IDENTITY; cell_count], + direction: vec![0; cell_count], + direction_support: vec![0; cell_count], + }; + + for y_bounds in y_boundaries.windows(2) { + for x_bounds in x_boundaries.windows(2) { + let x_start = x_bounds[0]; + let y_start = y_bounds[0]; + let tile = compute_kmer_tile( + &sequence, + &sequence, + KmerTileRequest { + domain_length: PERIODIC_LENGTH as u64, + x_start: x_start as u64, + y_start: y_start as u64, + width: x_bounds[1] - x_start, + height: y_bounds[1] - y_start, + k: PERIODIC_K, + geometry, + }, + ) + .unwrap(); + for row in 0..tile.height { + let source = row * tile.width; + let destination = (y_start + row) * PERIODIC_LENGTH + x_start; + stitched.identity[destination..destination + tile.width] + .copy_from_slice(&tile.identity[source..source + tile.width]); + stitched.direction[destination..destination + tile.width] + .copy_from_slice(&tile.direction[source..source + tile.width]); + stitched.direction_support[destination..destination + tile.width] + .copy_from_slice(&tile.direction_support[source..source + tile.width]); + } + } + } + stitched + } + + fn assert_symmetric(tile: &MatrixTile) { + assert_eq!(tile.width, tile.height); + for row in 0..tile.height { + for column in 0..tile.width { + let cell = row * tile.width + column; + let transpose = column * tile.width + row; + assert_eq!(tile.identity[cell], tile.identity[transpose]); + assert_eq!(tile.direction[cell], tile.direction[transpose]); + assert_eq!( + tile.direction_support[cell], + tile.direction_support[transpose] + ); + } + } + } + + #[test] + fn exact_kmers_distinguish_forward_reverse_mismatch_and_ambiguity() { + let x = PackedSequence::from_ascii("x", b"AACCGTN"); + let y = PackedSequence::from_ascii("y", b"AACCGTN"); + let tile = compute_kmer_tile( + &x, + &y, + KmerTileRequest { + domain_length: 7, + x_start: 0, + y_start: 0, + width: 7, + height: 7, + k: 3, + geometry: KmerTileGeometry::Footprints, + }, + ) + .unwrap(); + assert_eq!(tile.identity[0], 10_000); + assert_eq!(tile.identity[1], 0); + assert_eq!(tile.identity[6 * 7 + 6], MISSING_IDENTITY); + + let reverse = PackedSequence::from_ascii("reverse", b"CGGTT"); + let reverse_tile = compute_kmer_tile( + &x, + &reverse, + KmerTileRequest { + domain_length: 7, + x_start: 0, + y_start: 0, + width: 5, + height: 5, + k: 5, + geometry: KmerTileGeometry::Footprints, + }, + ) + .unwrap(); + assert_eq!(reverse_tile.identity[0], MISSING_IDENTITY); + assert_eq!(reverse_tile.identity[4 * 5], 10_000); + assert_eq!(reverse_tile.direction[4 * 5], -i16::MAX); + } + + #[test] + fn exact_kmers_extend_forward_matches_that_begin_outside_the_tile() { + let x = PackedSequence::from_ascii("x", b"AAACG"); + let y = PackedSequence::from_ascii("y", b"AAATT"); + let tile = compute_kmer_tile( + &x, + &y, + KmerTileRequest { + domain_length: 5, + x_start: 2, + y_start: 2, + width: 1, + height: 1, + k: 3, + geometry: KmerTileGeometry::Footprints, + }, + ) + .unwrap(); + assert_eq!(tile.identity, vec![10_000]); + assert_eq!(tile.direction, vec![i16::MAX]); + } + + #[test] + fn exact_kmers_extend_reverse_matches_from_start_to_start_plus_k_minus_one() { + let x = PackedSequence::from_ascii("x", b"AAACG"); + let y = PackedSequence::from_ascii("y", b"TTTGG"); + let tile = compute_kmer_tile( + &x, + &y, + KmerTileRequest { + domain_length: 5, + x_start: 2, + y_start: 0, + width: 1, + height: 1, + k: 3, + geometry: KmerTileGeometry::Footprints, + }, + ) + .unwrap(); + assert_eq!(tile.identity, vec![10_000]); + assert_eq!(tile.direction, vec![-i16::MAX]); + } + + #[test] + fn periodic_footprints_have_expected_counts_symmetry_and_diagonals() { + let tile = periodic_tile(KmerTileGeometry::Footprints); + assert_eq!( + tile.identity + .iter() + .filter(|&&value| value == 10_000) + .count(), + 2_352 + ); + assert_eq!( + tile.identity.iter().filter(|&&value| value == 0).count(), + 1_352 + ); + assert_eq!( + tile.identity + .iter() + .filter(|&&value| value == MISSING_IDENTITY) + .count(), + 1_480 + ); + assert_eq!( + tile.direction + .iter() + .filter(|&&value| value == i16::MAX) + .count(), + 1_176 + ); + assert_eq!( + tile.direction + .iter() + .filter(|&&value| value == -i16::MAX) + .count(), + 1_176 + ); + let supported = tile + .direction_support + .iter() + .copied() + .filter(|&support| support > 0); + assert_eq!(supported.clone().min(), Some(1)); + assert_eq!(supported.max(), Some(u16::from(PERIODIC_K))); + + assert_symmetric(&tile); + for coordinate in 0..PERIODIC_LENGTH { + let forward = coordinate * PERIODIC_LENGTH + coordinate; + assert_eq!(tile.identity[forward], 10_000); + assert_eq!(tile.direction[forward], i16::MAX); + + let reverse = coordinate * PERIODIC_LENGTH + (PERIODIC_LENGTH - 1 - coordinate); + assert_eq!(tile.identity[reverse], 10_000); + assert_eq!(tile.direction[reverse], -i16::MAX); + } + } + + #[test] + fn periodic_anchors_paint_only_equal_kmer_start_pairs() { + let tile = periodic_tile(KmerTileGeometry::Anchors); + assert_eq!( + tile.identity + .iter() + .filter(|&&value| value == 10_000) + .count(), + 1_352 + ); + assert_eq!( + tile.identity.iter().filter(|&&value| value == 0).count(), + 1_352 + ); + assert_eq!( + tile.identity + .iter() + .filter(|&&value| value == MISSING_IDENTITY) + .count(), + 2_480 + ); + assert_eq!( + tile.direction + .iter() + .filter(|&&value| value == i16::MAX) + .count(), + 676 + ); + assert_eq!( + tile.direction + .iter() + .filter(|&&value| value == -i16::MAX) + .count(), + 676 + ); + assert!(tile.direction_support.iter().all(|&support| support <= 1)); + assert_eq!( + tile.direction_support + .iter() + .filter(|&&support| support == 1) + .count(), + 1_352 + ); + + assert_symmetric(&tile); + let valid_starts = PERIODIC_LENGTH - usize::from(PERIODIC_K) + 1; + for coordinate in 0..valid_starts { + let forward = coordinate * PERIODIC_LENGTH + coordinate; + assert_eq!(tile.identity[forward], 10_000); + assert_eq!(tile.direction[forward], i16::MAX); + + let reverse = coordinate * PERIODIC_LENGTH + (valid_starts - 1 - coordinate); + assert_eq!(tile.identity[reverse], 10_000); + assert_eq!(tile.direction[reverse], -i16::MAX); + } + for coordinate in valid_starts..PERIODIC_LENGTH { + let diagonal = coordinate * PERIODIC_LENGTH + coordinate; + assert_eq!(tile.identity[diagonal], MISSING_IDENTITY); + assert_eq!(tile.direction_support[diagonal], 0); + } + } + + #[test] + fn periodic_full_and_split_tiles_are_identical_for_each_geometry() { + let x_boundaries = [0, 7, 29, 51, PERIODIC_LENGTH]; + let y_boundaries = [0, 13, 32, 60, PERIODIC_LENGTH]; + for geometry in [KmerTileGeometry::Footprints, KmerTileGeometry::Anchors] { + assert_eq!( + periodic_tile(geometry), + stitch_periodic_tiles(geometry, &x_boundaries, &y_boundaries), + "split tile seam changed {geometry:?} output" + ); + } + } + + #[test] + fn even_k_palindrome_paints_both_footprints_with_neutral_direction() { + let sequence = PackedSequence::from_ascii("palindrome", b"AT"); + let tile = compute_kmer_tile( + &sequence, + &sequence, + KmerTileRequest { + domain_length: 2, + x_start: 0, + y_start: 0, + width: 2, + height: 2, + k: 2, + geometry: KmerTileGeometry::Footprints, + }, + ) + .unwrap(); + + assert_eq!(tile.identity, vec![10_000; 4]); + assert_eq!(tile.direction, vec![0; 4]); + assert_eq!(tile.direction_support, vec![1; 4]); + } + + #[test] + fn overlapping_even_k_palindromes_count_each_origin_geometry_once() { + let sequence = PackedSequence::from_ascii("palindromes", b"ATAT"); + let tile = compute_kmer_tile( + &sequence, + &sequence, + KmerTileRequest { + domain_length: 4, + x_start: 0, + y_start: 0, + width: 4, + height: 4, + k: 2, + geometry: KmerTileGeometry::Footprints, + }, + ) + .unwrap(); + + assert_eq!(tile.identity, vec![10_000; 16]); + assert_eq!(tile.direction, vec![0; 16]); + assert_eq!( + tile.direction_support, + vec![1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 1] + ); + } + + #[test] + fn even_k_palindrome_anchor_is_single_neutral_evidence_cell() { + let sequence = PackedSequence::from_ascii("palindrome", b"AT"); + let tile = compute_kmer_tile( + &sequence, + &sequence, + KmerTileRequest { + domain_length: 2, + x_start: 0, + y_start: 0, + width: 2, + height: 2, + k: 2, + geometry: KmerTileGeometry::Anchors, + }, + ) + .unwrap(); + + assert_eq!( + tile.identity, + vec![10_000, MISSING_IDENTITY, MISSING_IDENTITY, MISSING_IDENTITY] + ); + assert_eq!(tile.direction, vec![0; 4]); + assert_eq!(tile.direction_support, vec![1, 0, 0, 0]); + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..e89f8c9 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,52 @@ +//! Browser-independent scientific algorithms for moddotplot-interactive. +//! +//! Coordinates are zero-based and half-open throughout the core. The web interface +//! may present one-based inclusive labels, but conversions happen only at the UI +//! boundary. Expensive optimized algorithms have simple reference counterparts so +//! that scientific behavior can be tested differentially. + +pub mod config; +pub mod containment; +pub mod dna; +pub mod fasta; +pub mod gc; +pub mod hash; +pub mod kmer; +mod matrix; +pub mod reference; +pub mod sketch; + +#[cfg(feature = "validation")] +pub mod validation; + +pub use config::{ + AniTransform, AxisExpansionPolicy, ConfigError, DEFAULT_B_BITS, DEFAULT_HLL_PRECISION, + DEFAULT_IDENTITY_SCALE, DEFAULT_MISSING_IDENTITY, DEFAULT_VERIFICATION_BITS, + SCIENTIFIC_CONFIG_VERSION, ScientificConfig, ScientificConfigIdentity, +}; +pub use containment::{ + ContainmentEstimate, DirectionEvidence, estimate_containment, estimate_containment_bit_sliced, + estimate_containment_from_comparison, +}; +pub use dna::{CanonicalKmer, Orientation, PackedSequence, SequenceIdentity}; +pub use fasta::{FastaError, FastaParser, MAX_SEQUENCE_LENGTH}; +pub use gc::{CompositionIndex, CompositionIndexBuilder, DEFAULT_COMPOSITION_BLOCK_SIZE}; +pub use kmer::{KmerTileGeometry, KmerTileRequest, compute_kmer_tile}; +pub use matrix::{ + AxisOverview, FrozenAxis, MatrixBuildError, MatrixTile, TileBuildError, TileRequest, + build_axis_overview, build_axis_range, compute_frozen_tile, compute_tile, +}; +pub use reference::{ExactKmerSet, ExactSimilarity, exact_moddotplot_score}; +pub use sketch::{ + BitSlicedSignature, HllSketch, OphJaccardEstimator, OphSketch, RegisterComparison, + verified_winner_jaccard, +}; + +/// Default nucleotide k-mer length, matching current `ModDotPlot` behavior. +pub const DEFAULT_K: u8 = 21; + +/// Production seed selecting the exact published ntHash2 stream. +/// +/// Nonzero seeds are reserved for offline multi-layout statistical validation and +/// apply an additional avalanche; browser production always records zero. +pub const DEFAULT_HASH_SEED: u64 = 0; diff --git a/crates/core/src/matrix.rs b/crates/core/src/matrix.rs new file mode 100644 index 0000000..3b37f00 --- /dev/null +++ b/crates/core/src/matrix.rs @@ -0,0 +1,1643 @@ +//! Axis summary construction and numeric matrix tiles. + +mod axis; +mod frozen; +mod ordinary; +#[cfg(feature = "validation")] +use crate::validation::sparse; + +use axis::divide_ceil; +#[cfg(feature = "validation")] +pub use sparse::SparseCorrectionPolicy; + +use crate::config::ScientificConfig; +#[cfg(test)] +use crate::containment::estimate_containment_bit_sliced_scalar; +use crate::containment::{ContainmentEstimate, DirectionEvidence, estimate_containment_bit_sliced}; +use crate::dna::{PackedSequence, SequenceIdentity}; +#[cfg(feature = "validation")] +use crate::hash::HashAlgorithm; +use crate::hash::canonical_hashes; +use crate::sketch::{BitSlicedSignature, OphSketch}; +#[cfg(feature = "validation")] +use crate::validation::adaptive::needs_exact_correction; +use std::collections::HashMap; + +/// Sentinel used for a matrix cell without valid k-mer evidence. +pub const MISSING_IDENTITY: u16 = u16::MAX; + +const DIRECTION_FINGERPRINT_BITS: u8 = 32; + +/// A complete distinct canonical-hash set retained for a bounded sparse core. +#[derive(Clone, Debug, Default)] +pub struct SparseCoreHashSet { + items: HashMap, +} + +impl SparseCoreHashSet { + /// Number of distinct canonical hashes. + pub fn len(&self) -> usize { + self.items.len() + } + + /// Returns true when the core has no valid canonical k-mers. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Iterates over distinct hashes and their combined source orientation. + pub fn iter(&self) -> impl Iterator { + self.items.iter() + } + + fn estimated_heap_bytes(&self) -> usize { + self.items + .capacity() + .saturating_mul(std::mem::size_of::<(u64, crate::dna::Orientation)>()) + } +} + +/// Core and neighbor-expanded interval sketches for one sequence axis. +#[derive(Clone, Debug)] +pub struct AxisOverview { + /// Complete immutable scientific configuration used to build this axis. + pub scientific_config: ScientificConfig, + /// Stable identity of the complete source base stream. + pub sequence_identity: SequenceIdentity, + /// FASTA record name. + pub sequence_name: String, + /// Length of this sequence in bases. + pub sequence_length: u64, + /// Shared coordinate domain used by both selected axes. + pub domain_length: u64, + /// Logical number of intervals across the common domain. + pub resolution: usize, + /// First global interval represented by the vectors in this summary. + pub offset: usize, + /// Half-open, sequence-clipped interval bounds. + pub bounds: Vec<(u64, u64)>, + /// Core interval sketches. + pub core: Vec, + /// Intervals expanded by half a core width on either side. + pub expanded: Vec, + /// Word-parallel encodings of core intervals. + pub core_encoded: Vec, + /// Word-parallel encodings of expanded intervals. + pub expanded_encoded: Vec, + /// Complete hashes for cores under an optional cardinality cap. + /// + /// This vector is empty for ordinary OPH-only builders. With the adaptive + /// builder it has one entry per represented core; `None` means the cap was + /// exceeded and the partial set was released. + pub sparse_core: Vec>, +} + +impl AxisOverview { + /// Approximate bytes owned by heap allocations, excluding allocator overhead. + pub fn estimated_heap_bytes(&self) -> usize { + self.sequence_name + .capacity() + .saturating_add( + self.bounds + .capacity() + .saturating_mul(std::mem::size_of::<(u64, u64)>()), + ) + .saturating_add( + self.core + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + self.expanded + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + self.core_encoded + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + self.expanded_encoded + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + self.core + .iter() + .chain(&self.expanded) + .map(OphSketch::estimated_heap_bytes) + .sum::(), + ) + .saturating_add( + self.sparse_core + .capacity() + .saturating_mul(std::mem::size_of::>()), + ) + .saturating_add( + self.sparse_core + .iter() + .filter_map(Option::as_ref) + .map(SparseCoreHashSet::estimated_heap_bytes) + .sum::(), + ) + .saturating_add( + self.core_encoded + .iter() + .chain(&self.expanded_encoded) + .map(BitSlicedSignature::estimated_heap_bytes) + .sum::(), + ) + } + + /// Discards mutable build sketches and retains only production tile data. + pub fn freeze(self) -> FrozenAxis { + FrozenAxis { + scientific_config: self.scientific_config, + sequence_identity: self.sequence_identity, + sequence_name: self.sequence_name, + sequence_length: self.sequence_length, + domain_length: self.domain_length, + resolution: self.resolution, + offset: self.offset, + bounds: self.bounds, + core_encoded: self.core_encoded, + expanded_encoded: self.expanded_encoded, + sparse_core: self.sparse_core, + } + } +} + +/// Immutable compact axis retained by the browser after sketch construction. +#[derive(Clone, Debug)] +pub struct FrozenAxis { + /// FASTA record name. + sequence_name: String, + /// Stable identity of the complete source base stream. + sequence_identity: SequenceIdentity, + /// Complete immutable scientific configuration used to build this axis. + scientific_config: ScientificConfig, + /// Length of this sequence in bases. + sequence_length: u64, + /// Shared coordinate domain used by both selected axes. + domain_length: u64, + /// Logical number of intervals across the common domain. + resolution: usize, + /// First global interval represented by the vectors in this summary. + offset: usize, + /// Half-open, sequence-clipped interval bounds. + bounds: Vec<(u64, u64)>, + /// Word-parallel encodings of core intervals. + core_encoded: Vec, + /// Word-parallel encodings of expanded intervals. + expanded_encoded: Vec, + /// Complete hashes for cores retained by the optional sparse policy. + sparse_core: Vec>, +} + +impl FrozenAxis { + /// Complete scientific configuration from which this axis was built. + pub const fn scientific_config(&self) -> ScientificConfig { + self.scientific_config + } + + /// Stable content identity of the complete source sequence. + pub const fn sequence_identity(&self) -> SequenceIdentity { + self.sequence_identity + } + + /// FASTA record name. + pub fn sequence_name(&self) -> &str { + &self.sequence_name + } + + /// Source sequence length in bases. + pub const fn sequence_length(&self) -> u64 { + self.sequence_length + } + + /// Shared coordinate domain length. + pub const fn domain_length(&self) -> u64 { + self.domain_length + } + + /// Logical number of intervals in the shared domain. + pub const fn resolution(&self) -> usize { + self.resolution + } + + /// First global interval represented by this axis range. + pub const fn offset(&self) -> usize { + self.offset + } + + /// Half-open, sequence-clipped interval bounds. + pub fn bounds(&self) -> &[(u64, u64)] { + &self.bounds + } + + /// Word-parallel core signatures. + pub fn core_encoded(&self) -> &[BitSlicedSignature] { + &self.core_encoded + } + + /// Word-parallel neighbor-expanded signatures. + pub fn expanded_encoded(&self) -> &[BitSlicedSignature] { + &self.expanded_encoded + } + + /// Joins contiguous ranges produced from one source axis. + /// + /// # Errors + /// + /// Returns [`TileBuildError::IncompatibleAxes`] when any part has different + /// scientific, sequence, coordinate, or layout provenance, or is not contiguous. + pub fn join_contiguous(mut parts: Vec) -> Result { + let mut joined = parts + .drain(..1) + .next() + .ok_or(TileBuildError::IncompatibleAxes)?; + for mut part in parts { + if !frozen::compatible_contiguous(&joined, &part) { + return Err(TileBuildError::IncompatibleAxes); + } + joined.bounds.append(&mut part.bounds); + joined.core_encoded.append(&mut part.core_encoded); + joined.expanded_encoded.append(&mut part.expanded_encoded); + joined.sparse_core.append(&mut part.sparse_core); + } + Ok(joined) + } + + /// Approximate retained heap bytes, excluding allocator overhead. + pub fn estimated_heap_bytes(&self) -> usize { + self.sequence_name + .capacity() + .saturating_add( + self.bounds + .capacity() + .saturating_mul(std::mem::size_of::<(u64, u64)>()), + ) + .saturating_add( + self.core_encoded + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + self.expanded_encoded + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + self.sparse_core + .capacity() + .saturating_mul(std::mem::size_of::>()), + ) + .saturating_add( + self.sparse_core + .iter() + .filter_map(Option::as_ref) + .map(SparseCoreHashSet::estimated_heap_bytes) + .sum::(), + ) + .saturating_add( + self.core_encoded + .iter() + .chain(&self.expanded_encoded) + .map(BitSlicedSignature::estimated_heap_bytes) + .sum::(), + ) + } +} + +pub(crate) trait AxisData { + fn scientific_config(&self) -> ScientificConfig; + #[cfg(feature = "validation")] + fn sequence_name(&self) -> &str; + #[cfg(feature = "validation")] + fn sequence_identity(&self) -> SequenceIdentity; + #[cfg(feature = "validation")] + fn sequence_length(&self) -> u64; + fn domain_length(&self) -> u64; + fn resolution(&self) -> usize; + fn offset(&self) -> usize; + fn core_encoded(&self) -> &[BitSlicedSignature]; + fn expanded_encoded(&self) -> &[BitSlicedSignature]; + fn sparse_core(&self) -> &[Option]; +} + +impl AxisData for AxisOverview { + fn scientific_config(&self) -> ScientificConfig { + self.scientific_config + } + #[cfg(feature = "validation")] + fn sequence_name(&self) -> &str { + &self.sequence_name + } + #[cfg(feature = "validation")] + fn sequence_identity(&self) -> SequenceIdentity { + self.sequence_identity + } + #[cfg(feature = "validation")] + fn sequence_length(&self) -> u64 { + self.sequence_length + } + fn domain_length(&self) -> u64 { + self.domain_length + } + fn resolution(&self) -> usize { + self.resolution + } + fn offset(&self) -> usize { + self.offset + } + fn core_encoded(&self) -> &[BitSlicedSignature] { + &self.core_encoded + } + fn expanded_encoded(&self) -> &[BitSlicedSignature] { + &self.expanded_encoded + } + fn sparse_core(&self) -> &[Option] { + &self.sparse_core + } +} + +impl AxisData for FrozenAxis { + fn scientific_config(&self) -> ScientificConfig { + self.scientific_config + } + #[cfg(feature = "validation")] + fn sequence_name(&self) -> &str { + &self.sequence_name + } + #[cfg(feature = "validation")] + fn sequence_identity(&self) -> SequenceIdentity { + self.sequence_identity + } + #[cfg(feature = "validation")] + fn sequence_length(&self) -> u64 { + self.sequence_length + } + fn domain_length(&self) -> u64 { + self.domain_length + } + fn resolution(&self) -> usize { + self.resolution + } + fn offset(&self) -> usize { + self.offset + } + fn core_encoded(&self) -> &[BitSlicedSignature] { + &self.core_encoded + } + fn expanded_encoded(&self) -> &[BitSlicedSignature] { + &self.expanded_encoded + } + fn sparse_core(&self) -> &[Option] { + &self.sparse_core + } +} + +/// Request for one row-major numeric matrix tile. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct TileRequest { + /// First x interval. + pub x_start: usize, + /// First y interval. + pub y_start: usize, + /// Requested tile width. + pub width: usize, + /// Requested tile height. + pub height: usize, +} + +/// Numeric matrix data consumed directly by the renderer and future exporters. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MatrixTile { + /// Actual width after clipping to the axis resolution. + pub width: usize, + /// Actual height after clipping to the axis resolution. + pub height: usize, + /// ANI multiplied by 10,000, or [`MISSING_IDENTITY`]. + pub identity: Vec, + /// Signed relative direction scaled to `[-32767, 32767]`. + pub direction: Vec, + /// Number of full-hash informative direction matches, saturated to `u16`. + pub direction_support: Vec, +} + +/// Invalid coordinates rejected before any axis allocation. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MatrixBuildError { + /// The padded domain is empty or shorter than the sequence. + Domain, + /// The requested resolution is zero or cannot be represented safely. + Resolution, + /// The requested bin range is empty or starts beyond the resolution. + Range, +} + +impl std::fmt::Display for MatrixBuildError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str(match self { + Self::Domain => "axis domain must be nonempty and cover the complete sequence", + Self::Resolution => "axis resolution must be positive and safely representable", + Self::Range => "axis bin range must be nonempty and begin inside the resolution", + }) + } +} + +impl std::error::Error for MatrixBuildError {} + +/// Invalid tile request rejected before allocating output channels. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TileBuildError { + /// The two axes do not describe compatible domains, layouts, or sketches. + IncompatibleAxes, + /// A source sequence does not match the axis from which it was built. + IncompatibleSequence, + /// K-mer, bit-width, or register parameters are unsupported. + Parameters, + /// Tile dimensions are zero or overflow addressable memory. + Dimensions, + /// The requested origin lies outside one of the represented axis ranges. + Range, +} + +impl std::fmt::Display for TileBuildError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str(match self { + Self::IncompatibleAxes => "tile axes have incompatible domains or sketch layouts", + Self::IncompatibleSequence => "tile source sequence does not match its prepared axis", + Self::Parameters => "tile k-mer, bit-width, or register parameters are invalid", + Self::Dimensions => "tile dimensions must be positive and safely representable", + Self::Range => "tile origin lies outside the represented axis range", + }) + } +} + +impl std::error::Error for TileBuildError {} + +/// Builds aligned core and half-window-expanded sketches in one k-mer scan. +/// +/// # Errors +/// +/// Returns [`MatrixBuildError`] when the domain or resolution is invalid. +pub fn build_axis_overview( + sequence: &PackedSequence, + domain_length: u64, + resolution: usize, + config: ScientificConfig, +) -> Result { + build_axis_range(sequence, domain_length, resolution, 0, resolution, config) +} + +/// Builds sketches for a contiguous range of intervals at an arbitrary resolution. +/// +/// Only the half-bin segments needed by `[bin_start, bin_start + bin_count)` and +/// its containment halo are scanned and retained. The result is therefore suitable +/// for demand-driven zoom tiles without allocating an entire high-resolution axis. +/// +/// # Errors +/// +/// Returns [`MatrixBuildError`] when the domain, resolution, or bin range is invalid. +/// +pub fn build_axis_range( + sequence: &PackedSequence, + domain_length: u64, + resolution: usize, + bin_start: usize, + bin_count: usize, + config: ScientificConfig, +) -> Result { + axis::validate_request( + sequence.len(), + domain_length, + resolution, + bin_start, + bin_count, + )?; + Ok(build_axis_range_internal( + sequence, + domain_length, + resolution, + bin_start, + bin_count, + config, + )) +} + +fn build_axis_range_internal( + sequence: &PackedSequence, + domain_length: u64, + resolution: usize, + bin_start: usize, + bin_count: usize, + config: ScientificConfig, +) -> AxisOverview { + assert!(domain_length >= sequence.len() && domain_length > 0); + assert!(resolution > 0 && resolution <= usize::MAX / 2); + assert!(bin_count > 0); + assert!(bin_start < resolution); + let bin_end = bin_start.saturating_add(bin_count).min(resolution); + let half_count = resolution * 2; + let first_half = (2 * bin_start).saturating_sub(1); + let end_half = (2 * bin_end + 1).min(half_count); + let represented_half_count = end_half - first_half; + let register_count = config.register_count(); + let sparse_cap = (config.sparse_core_cap() > 0).then_some(config.sparse_core_cap()); + let empty = OphSketch::new(register_count, config.hll_precision()); + let mut halves = vec![empty.clone(); represented_half_count]; + let represented_bins = bin_end - bin_start; + let mut sparse_core = sparse_cap.map(|_| { + (0..represented_bins) + .map(|_| Some(SparseCoreHashSet::default())) + .collect::>() + }); + + // Segment boundaries use ceilings because assignment uses floor(start * H / D). + // The conservative bounds are clipped by the sequence iterator and the explicit + // segment range check below. + let genomic_start = divide_ceil(first_half as u128 * u128::from(domain_length), half_count); + let genomic_end = divide_ceil(end_half as u128 * u128::from(domain_length), half_count); + + for kmer in canonical_hashes( + sequence, + config.k(), + genomic_start, + genomic_end, + config.hash_algorithm(), + config.hash_seed(), + ) { + let segment = + ((u128::from(kmer.start) * half_count as u128) / u128::from(domain_length)) as usize; + let global_bin = segment / 2; + if let (Some(cap), Some(cores), Some(local_bin)) = ( + sparse_cap, + sparse_core.as_mut(), + global_bin.checked_sub(bin_start), + ) && let Some(slot) = cores.get_mut(local_bin) + && let Some(core) = slot.as_mut() + { + if let Some(orientation) = core.items.get_mut(&kmer.hash) { + *orientation = orientation.combine(kmer.orientation); + } else if core.items.len() < cap { + core.items.insert(kmer.hash, kmer.orientation); + } else { + *slot = None; + } + } + if let Some(sketch) = segment + .checked_sub(first_half) + .and_then(|local| halves.get_mut(local)) + { + sketch.insert(kmer.hash, kmer.orientation); + } + } + + let mut core = Vec::with_capacity(represented_bins); + let mut expanded = Vec::with_capacity(represented_bins); + let mut bounds = Vec::with_capacity(represented_bins); + for bin in bin_start..bin_end { + let mut core_sketch = empty.clone(); + core_sketch.merge(&halves[2 * bin - first_half]); + core_sketch.merge(&halves[2 * bin + 1 - first_half]); + core.push(core_sketch); + + let expanded_first = (2 * bin).saturating_sub(1); + let expanded_end = (2 * bin + 3).min(half_count); + let mut expanded_sketch = empty.clone(); + for half in &halves[expanded_first - first_half..expanded_end - first_half] { + expanded_sketch.merge(half); + } + expanded.push(expanded_sketch); + + let start = divide_ceil(bin as u128 * u128::from(domain_length), resolution); + let end = divide_ceil((bin + 1) as u128 * u128::from(domain_length), resolution); + bounds.push((start.min(sequence.len()), end.min(sequence.len()))); + } + + let core_encoded = core + .iter() + .map(|sketch| sketch.bit_sliced(DIRECTION_FINGERPRINT_BITS, register_count)) + .collect(); + let expanded_encoded = expanded + .iter() + .map(|sketch| sketch.bit_sliced(DIRECTION_FINGERPRINT_BITS, register_count)) + .collect(); + + AxisOverview { + scientific_config: config, + sequence_identity: sequence.identity(), + sequence_name: sequence.name().to_owned(), + sequence_length: sequence.len(), + domain_length, + resolution, + offset: bin_start, + bounds, + core, + expanded, + core_encoded, + expanded_encoded, + sparse_core: sparse_core.unwrap_or_default(), + } +} + +/// Computes a clipped matrix tile using `ModDotPlot`'s maximum directed containment. +/// +/// # Errors +/// +/// Returns [`TileBuildError`] before allocation when axes or request fields are invalid. +pub fn compute_tile( + x: &AxisOverview, + y: &AxisOverview, + request: TileRequest, +) -> Result { + compute_encoded_tile(x, y, request, std::ptr::eq(x, y)) +} + +/// Computes a tile from the compact immutable representation retained by production. +/// +/// # Errors +/// +/// Returns [`TileBuildError`] before allocation when axes or request fields are invalid. +pub fn compute_frozen_tile( + x: &FrozenAxis, + y: &FrozenAxis, + request: TileRequest, +) -> Result { + compute_encoded_tile(x, y, request, std::ptr::eq(x, y)) +} + +fn compute_encoded_tile( + x: &A, + y: &A, + request: TileRequest, + is_self_axis: bool, +) -> Result { + let layout = ordinary::validate(x, y, request)?; + let config = x.scientific_config(); + let (width, height, cells) = (layout.width, layout.height, layout.cells); + let mut tile = MatrixTile { + width, + height, + identity: vec![MISSING_IDENTITY; cells], + direction: vec![0; cells], + direction_support: vec![0; cells], + }; + let mirror_within_tile = is_self_axis && request.x_start == request.y_start && width == height; + for row in 0..height { + let y_index = request.y_start + row; + let y_local = y_index - y.offset(); + for column in 0..width { + let x_index = request.x_start + column; + let x_local = x_index - x.offset(); + let output = row * width + column; + if mirror_within_tile && column < row { + let transpose = column * width + row; + tile.identity[output] = tile.identity[transpose]; + tile.direction[output] = tile.direction[transpose]; + tile.direction_support[output] = tile.direction_support[transpose]; + continue; + } + if x.core_encoded()[x_local].observations() == 0 + || y.core_encoded()[y_local].observations() == 0 + { + continue; + } + if is_self_axis && x_index == y_index { + tile.identity[output] = 10_000; + tile.direction[output] = i16::MAX; + tile.direction_support[output] = 1; + continue; + } + let left = estimate_containment_bit_sliced( + &x.core_encoded()[x_local], + &y.expanded_encoded()[y_local], + config.k(), + config.b_bits(), + config.register_count(), + ); + let right = estimate_containment_bit_sliced( + &y.core_encoded()[y_local], + &x.expanded_encoded()[x_local], + config.k(), + config.b_bits(), + config.register_count(), + ); + let Some((ani, direction)) = choose_score(left, right) else { + continue; + }; + tile.identity[output] = encode_identity(ani); + tile.direction[output] = direction.signed().map_or(0, encode_direction); + tile.direction_support[output] = + u16::try_from(direction.informative().min(u32::from(u16::MAX))).unwrap_or(u16::MAX); + } + } + Ok(tile) +} + +/// Computes a tile and exactly corrects only statistically underpowered sparse directions. +/// +/// The ordinary OPH estimate is produced first. Complete core hash sets retained by the +/// adaptive builder are considered for correction only when the probability model predicts +/// insufficient match detection at `policy.ani_floor`. Target sequence hashes are streamed +/// only across the requested tile range and shared across all eligible query cores. +/// +/// # Errors +/// +/// Returns [`TileBuildError`] before allocation when axes, source sequences, or request +/// fields are incompatible. +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "validation")] +pub fn compute_tile_adaptive( + x: &AxisOverview, + y: &AxisOverview, + x_sequence: &PackedSequence, + y_sequence: &PackedSequence, + hash_algorithm: HashAlgorithm, + seed: u64, + request: TileRequest, + policy: SparseCorrectionPolicy, +) -> Result { + sparse::validate_source_sequences(x, y, x_sequence, y_sequence)?; + let mut tile = compute_tile(x, y, request)?; + let config = x.scientific_config; + if x.sparse_core.is_empty() && y.sparse_core.is_empty() { + return Ok(tile); + } + let x_exact = exact_sparse_tile_direction( + x, + y, + y_sequence, + hash_algorithm, + seed, + request, + policy, + true, + ); + let y_exact = exact_sparse_tile_direction( + y, + x, + x_sequence, + hash_algorithm, + seed, + request, + policy, + false, + ); + for row in 0..tile.height { + let y_local = request.y_start + row - y.offset; + for column in 0..tile.width { + let output = row * tile.width + column; + if x_exact[output].is_none() && y_exact[output].is_none() { + continue; + } + let x_local = request.x_start + column - x.offset; + let left = x_exact[output].or_else(|| { + estimate_containment_bit_sliced( + &x.core_encoded[x_local], + &y.expanded_encoded[y_local], + config.k(), + config.b_bits(), + config.register_count(), + ) + .map(DirectedScore::from) + }); + let right = y_exact[output].or_else(|| { + estimate_containment_bit_sliced( + &y.core_encoded[y_local], + &x.expanded_encoded[x_local], + config.k(), + config.b_bits(), + config.register_count(), + ) + .map(DirectedScore::from) + }); + let Some(score) = choose_directed_score(left, right) else { + continue; + }; + tile.identity[output] = encode_identity(score.ani); + tile.direction[output] = score.direction.signed().map_or(0, encode_direction); + tile.direction_support[output] = + u16::try_from(score.direction.informative().min(u32::from(u16::MAX))) + .unwrap_or(u16::MAX); + } + } + Ok(tile) +} + +/// Computes a compact production tile with optional sparse exact correction. +/// +/// # Errors +/// +/// Returns [`TileBuildError`] before allocation when axes, source sequences, or request +/// fields are incompatible. +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "validation")] +pub fn compute_frozen_tile_adaptive( + x: &FrozenAxis, + y: &FrozenAxis, + x_sequence: &PackedSequence, + y_sequence: &PackedSequence, + hash_algorithm: HashAlgorithm, + seed: u64, + request: TileRequest, + policy: SparseCorrectionPolicy, +) -> Result { + sparse::validate_source_sequences(x, y, x_sequence, y_sequence)?; + let mut tile = compute_frozen_tile(x, y, request)?; + let config = x.scientific_config; + if x.sparse_core.is_empty() && y.sparse_core.is_empty() { + return Ok(tile); + } + let x_exact = exact_sparse_tile_direction( + x, + y, + y_sequence, + hash_algorithm, + seed, + request, + policy, + true, + ); + let y_exact = exact_sparse_tile_direction( + y, + x, + x_sequence, + hash_algorithm, + seed, + request, + policy, + false, + ); + for row in 0..tile.height { + let y_local = request.y_start + row - y.offset; + for column in 0..tile.width { + let output = row * tile.width + column; + if x_exact[output].is_none() && y_exact[output].is_none() { + continue; + } + let x_local = request.x_start + column - x.offset; + let left = x_exact[output].or_else(|| { + estimate_containment_bit_sliced( + &x.core_encoded[x_local], + &y.expanded_encoded[y_local], + config.k(), + config.b_bits(), + config.register_count(), + ) + .map(DirectedScore::from) + }); + let right = y_exact[output].or_else(|| { + estimate_containment_bit_sliced( + &y.core_encoded[y_local], + &x.expanded_encoded[x_local], + config.k(), + config.b_bits(), + config.register_count(), + ) + .map(DirectedScore::from) + }); + let Some(score) = choose_directed_score(left, right) else { + continue; + }; + tile.identity[output] = encode_identity(score.ani); + tile.direction[output] = score.direction.signed().map_or(0, encode_direction); + tile.direction_support[output] = + u16::try_from(score.direction.informative().min(u32::from(u16::MAX))) + .unwrap_or(u16::MAX); + } + } + Ok(tile) +} + +#[derive(Clone, Copy, Debug)] +#[cfg(feature = "validation")] +struct DirectedScore { + containment: f64, + ani: f64, + direction: DirectionEvidence, +} + +#[cfg(feature = "validation")] +impl From for DirectedScore { + fn from(value: ContainmentEstimate) -> Self { + Self { + containment: value.containment, + ani: value.ani, + direction: value.direction, + } + } +} + +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] +#[cfg(feature = "validation")] +fn exact_sparse_tile_direction( + query: &impl AxisData, + target: &impl AxisData, + target_sequence: &PackedSequence, + hash_algorithm: HashAlgorithm, + seed: u64, + request: TileRequest, + policy: SparseCorrectionPolicy, + query_is_x: bool, +) -> Vec> { + let query_end = query.offset() + query.core_encoded().len(); + let target_end = target.offset() + target.core_encoded().len(); + let (x_end, y_end) = if query_is_x { + (query_end, target_end) + } else { + (target_end, query_end) + }; + let width = request.width.min(x_end.saturating_sub(request.x_start)); + let height = request.height.min(y_end.saturating_sub(request.y_start)); + let mut scores = vec![None; width.saturating_mul(height)]; + let (query_start, query_count, target_start, target_count) = if query_is_x { + (request.x_start, width, request.y_start, height) + } else { + (request.y_start, height, request.x_start, width) + }; + if query_count == 0 || target_count == 0 || query.sparse_core().is_empty() { + return scores; + } + + let mut corrections = vec![false; query_count.saturating_mul(target_count)]; + let mut postings = HashMap::>::new(); + for query_offset in 0..query_count { + let query_local = query_start + query_offset - query.offset(); + let Some(core) = query + .sparse_core() + .get(query_local) + .and_then(Option::as_ref) + else { + continue; + }; + let mut eligible = false; + for target_offset in 0..target_count { + let target_local = target_start + target_offset - target.offset(); + let correct = needs_exact_correction( + core.len(), + target.expanded_encoded()[target_local].estimated_cardinality(), + query.scientific_config().register_count(), + query.scientific_config().k(), + policy.ani_floor, + policy.minimum_detection_probability, + ); + corrections[query_offset * target_count + target_offset] = correct; + eligible |= correct; + } + if eligible { + for (&hash, &orientation) in core.iter() { + postings + .entry(hash) + .or_default() + .push((query_offset, orientation)); + } + } + } + if postings.is_empty() { + return scores; + } + + let mut queries = postings.into_iter().collect::>(); + queries.sort_unstable_by_key(|(hash, _)| *hash); + let index = PrehashedQueryIndex::new(queries.iter().map(|(hash, _)| *hash)); + let mut target_orientation = + vec![crate::dna::Orientation::Unknown; queries.len() * target_count]; + let half_count = target.resolution() * 2; + let first_half = (2 * target_start).saturating_sub(1); + let end_half = (2 * (target_start + target_count) + 1).min(half_count); + let genomic_start = divide_ceil( + first_half as u128 * u128::from(target.domain_length()), + half_count, + ); + let genomic_end = divide_ceil( + end_half as u128 * u128::from(target.domain_length()), + half_count, + ); + for item in canonical_hashes( + target_sequence, + query.scientific_config().k(), + genomic_start, + genomic_end, + hash_algorithm, + seed, + ) { + let Some(query_id) = index.get(item.hash) else { + continue; + }; + let half = ((u128::from(item.start) * half_count as u128) + / u128::from(target.domain_length())) as usize; + let (first, last) = expanded_target_cells(half, target.resolution()); + for target_cell in [first, last] { + if !(target_start..target_start + target_count).contains(&target_cell) { + continue; + } + let slot = &mut target_orientation + [query_id * target_count + target_cell.saturating_sub(target_start)]; + *slot = slot.combine(item.orientation); + } + } + + let mut shared = vec![0_u32; scores.len()]; + let mut direction = vec![DirectionEvidence::default(); scores.len()]; + for (query_id, (_, query_postings)) in queries.iter().enumerate() { + for target_offset in 0..target_count { + let target_strand = target_orientation[query_id * target_count + target_offset]; + if target_strand == crate::dna::Orientation::Unknown { + continue; + } + for &(query_offset, query_strand) in query_postings { + if !corrections[query_offset * target_count + target_offset] { + continue; + } + let output = if query_is_x { + target_offset * width + query_offset + } else { + query_offset * width + target_offset + }; + shared[output] = shared[output].saturating_add(1); + add_exact_direction(&mut direction[output], query_strand, target_strand); + } + } + } + + for query_offset in 0..query_count { + let query_local = query_start + query_offset - query.offset(); + let Some(denominator) = query.sparse_core()[query_local] + .as_ref() + .map(SparseCoreHashSet::len) + else { + continue; + }; + if denominator == 0 { + continue; + } + for target_offset in 0..target_count { + if !corrections[query_offset * target_count + target_offset] { + continue; + } + let output = if query_is_x { + target_offset * width + query_offset + } else { + query_offset * width + target_offset + }; + let containment = f64::from(shared[output]) + / f64::from(u32::try_from(denominator).expect("the sparse cap fits u32")); + scores[output] = Some(DirectedScore { + containment, + ani: if containment == 0.0 { + 0.0 + } else { + containment.powf(1.0 / f64::from(query.scientific_config().k())) + }, + direction: direction[output], + }); + } + } + scores +} + +#[cfg(feature = "validation")] +fn expanded_target_cells(half: usize, resolution: usize) -> (usize, usize) { + let base = half / 2; + let first = if half.is_multiple_of(2) { + base.saturating_sub(1) + } else { + base + }; + let last = if half.is_multiple_of(2) { + base + } else { + base + 1 + } + .min(resolution - 1); + (first, last) +} + +#[cfg(feature = "validation")] +fn add_exact_direction( + evidence: &mut DirectionEvidence, + query: crate::dna::Orientation, + target: crate::dna::Orientation, +) { + use crate::dna::Orientation::{Both, Forward, Reverse, Unknown}; + match (query, target) { + (Forward, Forward) | (Reverse, Reverse) => { + evidence.forward = evidence.forward.saturating_add(1); + } + (Forward, Reverse) | (Reverse, Forward) => { + evidence.reverse = evidence.reverse.saturating_add(1); + } + (Both | Unknown, _) | (_, Both | Unknown) => { + evidence.ambiguous = evidence.ambiguous.saturating_add(1); + } + } +} + +#[cfg(feature = "validation")] +fn choose_directed_score( + left: Option, + right: Option, +) -> Option { + match (left, right) { + (Some(left), Some(right)) => Some(DirectedScore { + containment: left.containment.max(right.containment), + ani: left.ani.max(right.ani), + direction: left.direction.combine(right.direction), + }), + (Some(value), None) | (None, Some(value)) => Some(value), + (None, None) => None, + } +} + +/// Minimal open-addressing index for a set of already uniform 64-bit hashes. +#[cfg(feature = "validation")] +struct PrehashedQueryIndex { + keys: Vec, + values: Vec, + occupied: Vec, + mask: usize, + index_shift: u32, +} + +#[cfg(feature = "validation")] +impl PrehashedQueryIndex { + fn new(hashes: impl IntoIterator) -> Self { + let hashes = hashes.into_iter().collect::>(); + let capacity = hashes.len().saturating_mul(2).max(2).next_power_of_two(); + let mut index = Self { + keys: vec![0; capacity], + values: vec![0; capacity], + occupied: vec![false; capacity], + mask: capacity - 1, + index_shift: 64 - capacity.trailing_zeros(), + }; + for (value, hash) in hashes.into_iter().enumerate() { + index.insert(hash, value); + } + index + } + + fn insert(&mut self, hash: u64, value: usize) { + let mut slot = self.index(hash); + while self.occupied[slot] { + if self.keys[slot] == hash { + return; + } + slot = (slot + 1) & self.mask; + } + self.occupied[slot] = true; + self.keys[slot] = hash; + self.values[slot] = value; + } + + fn get(&self, hash: u64) -> Option { + let mut slot = self.index(hash); + while self.occupied[slot] { + if self.keys[slot] == hash { + return Some(self.values[slot]); + } + slot = (slot + 1) & self.mask; + } + None + } + + fn index(&self, hash: u64) -> usize { + let mixed = hash.wrapping_mul(0x9e37_79b9_7f4a_7c15); + usize::try_from(mixed >> self.index_shift).expect("a table index must fit usize") + } +} + +/// Computes a tile with the scalar register comparison used for differential testing. +/// +/// # Panics +/// +/// Panics under the same incompatible-axis and invalid-parameter conditions as +/// [`compute_tile`]. +#[cfg(test)] +fn compute_tile_reference(x: &AxisOverview, y: &AxisOverview, request: TileRequest) -> MatrixTile { + compute_tile_impl(x, y, request, false) +} + +#[cfg(test)] +#[allow(clippy::too_many_lines)] +fn compute_tile_impl( + x: &AxisOverview, + y: &AxisOverview, + request: TileRequest, + use_bit_sliced: bool, +) -> MatrixTile { + assert_eq!(x.domain_length, y.domain_length); + assert_eq!(x.resolution, y.resolution); + assert!(request.x_start >= x.offset); + assert!(request.y_start >= y.offset); + let x_end = x.offset + x.core.len(); + let y_end = y.offset + y.core.len(); + let width = request.width.min(x_end.saturating_sub(request.x_start)); + let height = request.height.min(y_end.saturating_sub(request.y_start)); + let cell_count = width.saturating_mul(height); + let mut tile = MatrixTile { + width, + height, + identity: vec![MISSING_IDENTITY; cell_count], + direction: vec![0; cell_count], + direction_support: vec![0; cell_count], + }; + let config = x.scientific_config; + let is_self_axis = std::ptr::eq(x, y); + let mirror_within_tile = is_self_axis && request.x_start == request.y_start && width == height; + + for row in 0..height { + let y_index = request.y_start + row; + let y_local = y_index - y.offset; + for column in 0..width { + let x_index = request.x_start + column; + let x_local = x_index - x.offset; + let output = row * width + column; + if mirror_within_tile && column < row { + let transpose = column * width + row; + tile.identity[output] = tile.identity[transpose]; + tile.direction[output] = tile.direction[transpose]; + tile.direction_support[output] = tile.direction_support[transpose]; + continue; + } + if x.core[x_local].observations() == 0 || y.core[y_local].observations() == 0 { + continue; + } + + // A self interval compared with its own expanded interval has exact + // containment one. Do not let sketch variance introduce a false gap in + // the main diagonal, which is a known scientific invariant rather than + // an estimated value. + if is_self_axis && x_index == y_index { + tile.identity[output] = 10_000; + tile.direction[output] = i16::MAX; + tile.direction_support[output] = 1; + continue; + } + + #[cfg(test)] + let (x_in_y, y_in_x) = if use_bit_sliced { + ( + estimate_containment_bit_sliced( + &x.core_encoded[x_local], + &y.expanded_encoded[y_local], + config.k(), + config.b_bits(), + config.register_count(), + ), + estimate_containment_bit_sliced( + &y.core_encoded[y_local], + &x.expanded_encoded[x_local], + config.k(), + config.b_bits(), + config.register_count(), + ), + ) + } else { + ( + estimate_containment_bit_sliced_scalar( + &x.core_encoded[x_local], + &y.expanded_encoded[y_local], + config.k(), + config.b_bits(), + config.register_count(), + ), + estimate_containment_bit_sliced_scalar( + &y.core_encoded[y_local], + &x.expanded_encoded[x_local], + config.k(), + config.b_bits(), + config.register_count(), + ), + ) + }; + #[cfg(not(test))] + let (x_in_y, y_in_x) = { + debug_assert!(use_bit_sliced); + ( + estimate_containment_bit_sliced( + &x.core_encoded[x_local], + &y.expanded_encoded[y_local], + config.k(), + config.b_bits(), + config.register_count(), + ), + estimate_containment_bit_sliced( + &y.core_encoded[y_local], + &x.expanded_encoded[x_local], + config.k(), + config.b_bits(), + config.register_count(), + ), + ) + }; + let Some((ani, direction)) = choose_score(x_in_y, y_in_x) else { + continue; + }; + + tile.identity[output] = encode_identity(ani); + if let Some(signed) = direction.signed() { + tile.direction[output] = encode_direction(signed); + } + tile.direction_support[output] = + u16::try_from(direction.informative().min(u32::from(u16::MAX))).unwrap_or(u16::MAX); + } + } + tile +} + +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] +fn encode_identity(ani: f64) -> u16 { + // The clamp proves that the rounded value is in 0..=10_000. + (ani.clamp(0.0, 1.0) * 10_000.0).round() as u16 +} + +#[allow(clippy::cast_possible_truncation)] +fn encode_direction(direction: f64) -> i16 { + // DirectionEvidence::signed guarantees [-1, 1]. Clamp defensively at the storage + // boundary so future estimators cannot overflow the fixed-point representation. + (direction.clamp(-1.0, 1.0) * 32_767.0).round() as i16 +} + +fn choose_score( + left: Option, + right: Option, +) -> Option<(f64, DirectionEvidence)> { + match (left, right) { + (Some(left), Some(right)) => Some(( + left.ani.max(right.ani), + left.direction.combine(right.direction), + )), + (Some(value), None) | (None, Some(value)) => Some((value.ani, value.direction)), + (None, None) => None, + } +} + +#[cfg(test)] +#[allow(deprecated)] +mod tests { + use super::*; + + fn config(k: u8, registers: usize, hll_precision: u8, sparse: bool) -> ScientificConfig { + ScientificConfig::production(k, registers, hll_precision, sparse).unwrap() + } + + #[test] + fn identical_sequence_has_full_diagonal() { + let sequence = PackedSequence::from_ascii("seq", &pseudo_random_sequence(20_000)); + let axis = + build_axis_overview(&sequence, sequence.len(), 16, config(15, 256, 10, false)).unwrap(); + let tile = compute_tile( + &axis, + &axis, + TileRequest { + x_start: 0, + y_start: 0, + width: 16, + height: 16, + }, + ) + .unwrap(); + for index in 0..16 { + assert_eq!(tile.identity[index * 16 + index], 10_000); + assert!(tile.direction[index * 16 + index] > 0); + } + for row in 0..16 { + for column in 0..16 { + assert_eq!( + tile.identity[row * 16 + column], + tile.identity[column * 16 + row] + ); + assert_eq!( + tile.direction[row * 16 + column], + tile.direction[column * 16 + row] + ); + } + } + } + + #[test] + fn padded_axis_cells_are_missing() { + let sequence = PackedSequence::from_ascii("short", &pseudo_random_sequence(5_000)); + let axis = build_axis_overview(&sequence, 10_000, 10, config(15, 128, 8, false)).unwrap(); + let tile = compute_tile( + &axis, + &axis, + TileRequest { + x_start: 0, + y_start: 0, + width: 10, + height: 10, + }, + ) + .unwrap(); + assert_eq!(tile.identity[9 * 10 + 9], MISSING_IDENTITY); + } + + #[test] + fn invalid_tile_requests_fail_before_output_allocation() { + let sequence = PackedSequence::from_ascii("sequence", &pseudo_random_sequence(5_000)); + let axis = + build_axis_overview(&sequence, sequence.len(), 4, config(15, 128, 8, false)).unwrap(); + let request = TileRequest { + x_start: 0, + y_start: 0, + width: 0, + height: usize::MAX, + }; + assert_eq!( + compute_tile(&axis, &axis, request), + Err(TileBuildError::Dimensions) + ); + + let incompatible = + build_axis_overview(&sequence, sequence.len(), 4, config(15, 256, 8, false)).unwrap(); + assert_eq!( + compute_tile( + &axis, + &incompatible, + TileRequest { + width: 1, + height: 1, + ..request + } + ), + Err(TileBuildError::IncompatibleAxes) + ); + + let different_k = + build_axis_overview(&sequence, sequence.len(), 4, config(17, 128, 8, false)).unwrap(); + assert_eq!( + compute_tile( + &axis, + &different_k, + TileRequest { + width: 1, + height: 1, + ..request + } + ), + Err(TileBuildError::IncompatibleAxes) + ); + } + + #[test] + fn frozen_ranges_own_and_validate_scientific_and_sequence_provenance() { + let bases = pseudo_random_sequence(20_000); + let sequence = PackedSequence::from_ascii("source", &bases); + let scientific = config(15, 128, 8, false); + let first = build_axis_range(&sequence, sequence.len(), 8, 0, 4, scientific) + .unwrap() + .freeze(); + let second = build_axis_range(&sequence, sequence.len(), 8, 4, 4, scientific) + .unwrap() + .freeze(); + let joined = FrozenAxis::join_contiguous(vec![first.clone(), second]).unwrap(); + assert_eq!(joined.scientific_config().identity(), scientific.identity()); + assert_eq!(joined.sequence_identity(), sequence.identity()); + assert_eq!(joined.sequence_name(), sequence.name()); + assert_eq!(joined.offset(), 0); + assert_eq!(joined.core_encoded().len(), 8); + + let different_bases = pseudo_random_sequence(20_001)[1..].to_vec(); + let different = PackedSequence::from_ascii("source", &different_bases); + let incompatible = build_axis_range(&different, different.len(), 8, 4, 4, scientific) + .unwrap() + .freeze(); + assert!(matches!( + FrozenAxis::join_contiguous(vec![first, incompatible]), + Err(TileBuildError::IncompatibleAxes) + )); + } + + #[test] + fn optimized_tile_matches_scalar_reference() { + let left = PackedSequence::from_ascii("left", &pseudo_random_sequence(30_000)); + let mut right_bases = pseudo_random_sequence(30_000); + for index in (100..right_bases.len()).step_by(17) { + right_bases[index] = b"ACGT"[(right_bases[index] as usize + 1) & 3]; + } + let right = PackedSequence::from_ascii("right", &right_bases); + let scientific = config(15, 256, 10, false); + let left_axis = build_axis_overview(&left, left.len(), 20, scientific).unwrap(); + let right_axis = build_axis_overview(&right, right.len(), 20, scientific).unwrap(); + let request = TileRequest { + x_start: 0, + y_start: 0, + width: 20, + height: 20, + }; + assert_eq!( + compute_tile(&left_axis, &right_axis, request).unwrap(), + compute_tile_reference(&left_axis, &right_axis, request) + ); + } + + #[test] + fn ranged_axis_matches_the_same_part_of_a_complete_axis() { + let left = PackedSequence::from_ascii("left", &pseudo_random_sequence(50_003)); + let mut right_bases = pseudo_random_sequence(47_009); + for index in (250..right_bases.len()).step_by(31) { + right_bases[index] = b"ACGT"[(right_bases[index] as usize + 1) & 3]; + } + let right = PackedSequence::from_ascii("right", &right_bases); + let domain = left.len().max(right.len()); + let scientific = config(15, 256, 10, false); + let left_full = build_axis_overview(&left, domain, 97, scientific).unwrap(); + let right_full = build_axis_overview(&right, domain, 97, scientific).unwrap(); + let left_range = build_axis_range(&left, domain, 97, 23, 29, scientific).unwrap(); + let right_range = build_axis_range(&right, domain, 97, 41, 27, scientific).unwrap(); + let request = TileRequest { + x_start: 23, + y_start: 41, + width: 29, + height: 27, + }; + + assert_eq!( + compute_tile(&left_full, &right_full, request), + compute_tile(&left_range, &right_range, request) + ); + assert_eq!(&left_full.bounds[23..52], left_range.bounds); + assert_eq!(&right_full.bounds[41..68], right_range.bounds); + } + + #[test] + #[cfg(feature = "validation")] + fn adaptive_builder_retains_only_complete_bounded_core_sets() { + let random = PackedSequence::from_ascii("random", &pseudo_random_sequence(20_000)); + let dense = + build_axis_overview(&random, random.len(), 4, config(15, 256, 10, true)).unwrap(); + assert!(dense.sparse_core.iter().all(Option::is_none)); + + let repetitive = PackedSequence::from_ascii("repeat", &vec![b'A'; 20_000]); + let sparse = + build_axis_overview(&repetitive, repetitive.len(), 4, config(15, 256, 10, true)) + .unwrap(); + assert_eq!(sparse.sparse_core.len(), 4); + assert!( + sparse + .sparse_core + .iter() + .all(|core| core.as_ref().is_some_and(|hashes| hashes.len() == 1)) + ); + } + + #[test] + fn ordinary_builder_does_not_allocate_sparse_sets() { + let sequence = PackedSequence::from_ascii("seq", &pseudo_random_sequence(10_000)); + let axis = + build_axis_overview(&sequence, sequence.len(), 10, config(15, 128, 8, false)).unwrap(); + assert!(axis.sparse_core.is_empty()); + } + + #[test] + #[cfg(feature = "validation")] + fn adaptive_tile_recovers_a_sparse_core_contained_in_a_large_target() { + let x_bases = pseudo_random_sequence(1_000); + let mut y_bases = x_bases.clone(); + y_bases.extend(pseudo_random_sequence(50_000)); + let x = PackedSequence::from_ascii("x", &x_bases); + let y = PackedSequence::from_ascii("y", &y_bases); + let domain = y.len(); + let scientific = config(21, 2_048, 12, true); + let x_axis = build_axis_overview(&x, domain, 1, scientific).unwrap(); + let y_axis = build_axis_overview(&y, domain, 1, scientific).unwrap(); + let tile = compute_tile_adaptive( + &x_axis, + &y_axis, + &x, + &y, + HashAlgorithm::NtHash2, + 0, + TileRequest { + x_start: 0, + y_start: 0, + width: 1, + height: 1, + }, + SparseCorrectionPolicy { + ani_floor: 0.80, + minimum_detection_probability: 0.99, + }, + ) + .unwrap(); + assert_eq!(tile.identity, vec![10_000]); + assert!(tile.direction[0] > 0); + } + + fn pseudo_random_sequence(length: usize) -> Vec { + let mut state = 0x243f_6a88_85a3_08d3_u64; + let mut sequence = Vec::with_capacity(length); + for _ in 0..length { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + sequence.push(b"ACGT"[(state & 3) as usize]); + } + sequence + } +} diff --git a/crates/core/src/matrix/axis.rs b/crates/core/src/matrix/axis.rs new file mode 100644 index 0000000..bb48c8b --- /dev/null +++ b/crates/core/src/matrix/axis.rs @@ -0,0 +1,28 @@ +//! Axis-request validation and coordinate partitioning. + +use super::MatrixBuildError; + +pub(super) fn validate_request( + sequence_length: u64, + domain_length: u64, + resolution: usize, + bin_start: usize, + bin_count: usize, +) -> Result<(), MatrixBuildError> { + if domain_length < sequence_length || domain_length == 0 { + return Err(MatrixBuildError::Domain); + } + if resolution == 0 || resolution > usize::MAX / 2 { + return Err(MatrixBuildError::Resolution); + } + if bin_count == 0 || bin_start >= resolution { + return Err(MatrixBuildError::Range); + } + Ok(()) +} + +pub(super) fn divide_ceil(numerator: u128, denominator: usize) -> u64 { + let denominator = denominator as u128; + u64::try_from(numerator.div_ceil(denominator)) + .expect("a coordinate within a u64 domain must fit in u64") +} diff --git a/crates/core/src/matrix/frozen.rs b/crates/core/src/matrix/frozen.rs new file mode 100644 index 0000000..62edeff --- /dev/null +++ b/crates/core/src/matrix/frozen.rs @@ -0,0 +1,16 @@ +//! Frozen-axis provenance and contiguous-range validation. + +use super::FrozenAxis; + +pub(super) fn compatible_contiguous(left: &FrozenAxis, right: &FrozenAxis) -> bool { + left.offset + .checked_add(left.core_encoded.len()) + .is_some_and(|expected| right.offset == expected) + && right.scientific_config.identity() == left.scientific_config.identity() + && right.sequence_identity == left.sequence_identity + && right.sequence_name == left.sequence_name + && right.sequence_length == left.sequence_length + && right.domain_length == left.domain_length + && right.resolution == left.resolution + && right.core_encoded.len() == right.expanded_encoded.len() +} diff --git a/crates/core/src/matrix/ordinary.rs b/crates/core/src/matrix/ordinary.rs new file mode 100644 index 0000000..3e27f5e --- /dev/null +++ b/crates/core/src/matrix/ordinary.rs @@ -0,0 +1,74 @@ +//! Validation and allocation geometry for ordinary production tiles. + +use super::{AxisData, TileBuildError, TileRequest}; + +pub(super) struct ValidatedTile { + pub(super) width: usize, + pub(super) height: usize, + pub(super) cells: usize, +} + +pub(super) fn validate( + x: &impl AxisData, + y: &impl AxisData, + request: TileRequest, +) -> Result { + if x.scientific_config().identity() != y.scientific_config().identity() + || x.domain_length() != y.domain_length() + || x.resolution() != y.resolution() + || x.resolution() == 0 + || x.resolution() > usize::MAX / 2 + || x.core_encoded().len() != x.expanded_encoded().len() + || y.core_encoded().len() != y.expanded_encoded().len() + || (!x.sparse_core().is_empty() && x.sparse_core().len() != x.core_encoded().len()) + || (!y.sparse_core().is_empty() && y.sparse_core().len() != y.core_encoded().len()) + { + return Err(TileBuildError::IncompatibleAxes); + } + if request.width == 0 || request.height == 0 { + return Err(TileBuildError::Dimensions); + } + let x_end = x + .offset() + .checked_add(x.core_encoded().len()) + .ok_or(TileBuildError::Range)?; + let y_end = y + .offset() + .checked_add(y.core_encoded().len()) + .ok_or(TileBuildError::Range)?; + if !(x.offset()..x_end).contains(&request.x_start) + || !(y.offset()..y_end).contains(&request.y_start) + { + return Err(TileBuildError::Range); + } + let config = x.scientific_config(); + let mut expected = None; + for signature in x + .core_encoded() + .iter() + .chain(x.expanded_encoded()) + .chain(y.core_encoded()) + .chain(y.expanded_encoded()) + { + let dimensions = (signature.register_count(), signature.fingerprint_bits()); + if dimensions != (config.register_count(), config.verification_bits()) { + return Err(TileBuildError::Parameters); + } + if expected.is_some_and(|expected| expected != dimensions) { + return Err(TileBuildError::IncompatibleAxes); + } + expected = Some(dimensions); + } + expected.ok_or(TileBuildError::IncompatibleAxes)?; + + let width = request.width.min(x_end.saturating_sub(request.x_start)); + let height = request.height.min(y_end.saturating_sub(request.y_start)); + let cells = width + .checked_mul(height) + .ok_or(TileBuildError::Dimensions)?; + Ok(ValidatedTile { + width, + height, + cells, + }) +} diff --git a/crates/core/src/reference.rs b/crates/core/src/reference.rs new file mode 100644 index 0000000..c618b45 --- /dev/null +++ b/crates/core/src/reference.rs @@ -0,0 +1,147 @@ +//! Exact small-input set calculations used for scientific validation. + +use crate::dna::{Orientation, PackedSequence}; +use std::collections::BTreeMap; + +/// Exact distinct canonical k-mer set with combined strand observations. +/// +/// Canonical two-bit words are injective for the supported `k <= 31`, so this +/// reference is independent of every production hash function and its collision +/// behavior. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ExactKmerSet { + k: u8, + items: BTreeMap, +} + +impl ExactKmerSet { + /// Constructs the exact set of valid k-mers starting in `[start, end)`. + /// + /// # Panics + /// + /// Panics if `k` is outside `1..=31`. + pub fn from_interval(sequence: &PackedSequence, start: u64, end: u64, k: u8) -> Self { + let mut items = BTreeMap::::new(); + for kmer in sequence.canonical_kmers(k, start, end) { + items + .entry(kmer.bits) + .and_modify(|orientation| { + *orientation = orientation.combine(kmer.orientation); + }) + .or_insert(kmer.orientation); + } + Self { k, items } + } + + /// Number of distinct hashes. + pub fn len(&self) -> usize { + self.items.len() + } + + /// Returns true when no valid k-mer is present. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Exact directed containment of this set in `other`. + /// + /// # Panics + /// + /// Panics if the two exact sets were constructed with different k-mer lengths. + pub fn containment_in(&self, other: &Self) -> Option { + assert_eq!(self.k, other.k, "exact k-mer sets must use the same k"); + if self.is_empty() { + return None; + } + let shared = self + .items + .keys() + .filter(|hash| other.items.contains_key(hash)) + .count(); + Some(count_as_f64(shared) / count_as_f64(self.len())) + } + + /// Exact Jaccard similarity. + /// + /// # Panics + /// + /// Panics if the two exact sets were constructed with different k-mer lengths. + pub fn jaccard(&self, other: &Self) -> Option { + assert_eq!(self.k, other.k, "exact k-mer sets must use the same k"); + let union = self + .items + .keys() + .chain(other.items.keys()) + .collect::>(); + if union.is_empty() { + return None; + } + let intersection = self + .items + .keys() + .filter(|hash| other.items.contains_key(hash)) + .count(); + Some(count_as_f64(intersection) / count_as_f64(union.len())) + } +} + +fn count_as_f64(count: usize) -> f64 { + // Exact reference sets are bounded by the supported one-billion-base sequence + // length, so their cardinality is representable exactly as both u32 and f64. + f64::from(u32::try_from(count).expect("exact set exceeds supported sequence length")) +} + +/// Exact ModDotPlot-style maximum-containment result. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ExactSimilarity { + /// Maximum of the two directed core-to-expanded containments. + pub containment: f64, + /// ANI transform of `containment`. + pub ani: f64, +} + +/// Computes the exact score for two core sets and their expanded neighbors. +pub fn exact_moddotplot_score( + x_core: &ExactKmerSet, + x_expanded: &ExactKmerSet, + y_core: &ExactKmerSet, + y_expanded: &ExactKmerSet, + k: u8, +) -> Option { + let x_in_y = x_core.containment_in(y_expanded); + let y_in_x = y_core.containment_in(x_expanded); + let containment = match (x_in_y, y_in_x) { + (Some(left), Some(right)) => left.max(right), + (Some(value), None) | (None, Some(value)) => value, + (None, None) => return None, + }; + let ani = if containment == 0.0 { + 0.0 + } else { + containment.powf(1.0 / f64::from(k)) + }; + Some(ExactSimilarity { containment, ani }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn core_is_exactly_contained_in_its_expansion() { + let sequence = PackedSequence::from_ascii("seq", b"ACGTTGCATGTCGCATGATGCATGAGAGCT"); + let core = ExactKmerSet::from_interval(&sequence, 5, 15, 7); + let expanded = ExactKmerSet::from_interval(&sequence, 0, 25, 7); + assert_eq!(core.containment_in(&expanded), Some(1.0)); + } + + #[test] + fn uracil_and_thymine_have_identical_exact_sets() { + let rna = PackedSequence::from_ascii("rna", b"ACUUGCUUACGU"); + let dna = PackedSequence::from_ascii("dna", b"ACTTGCTTACGT"); + let rna_set = ExactKmerSet::from_interval(&rna, 0, rna.len(), 5); + let dna_set = ExactKmerSet::from_interval(&dna, 0, dna.len(), 5); + assert_eq!(rna_set, dna_set); + assert_eq!(rna_set.containment_in(&dna_set), Some(1.0)); + } +} diff --git a/crates/core/src/sketch.rs b/crates/core/src/sketch.rs new file mode 100644 index 0000000..581f3a2 --- /dev/null +++ b/crates/core/src/sketch.rs @@ -0,0 +1,638 @@ +//! Mergeable one-permutation and cardinality sketches. + +use crate::dna::Orientation; +use crate::hash::fmix64; + +/// One occupied one-permutation register. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub(crate) struct OphRegister { + pub(crate) hash: u64, + pub(crate) orientation: Orientation, + pub(crate) occupied: bool, +} + +/// Evidence obtained by comparing aligned one-permutation registers. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct RegisterComparison { + /// Collision-corrected b-bit Jaccard estimate. + pub jaccard: Option, + /// Registers occupied in at least one input. + pub considered: u32, + /// Registers whose truncated b-bit values matched. + pub bbit_matches: u32, + /// Registers whose winner verification fingerprint matched. + pub verified_matches: u32, + /// Verified matches with equal canonical orientation. + pub forward_matches: u32, + /// Verified matches with opposite canonical orientation. + pub reverse_matches: u32, + /// Verified matches without unambiguous direction. + pub ambiguous_matches: u32, +} + +/// Jaccard statistic derived from aligned OPH register winners. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum OphJaccardEstimator { + /// Legacy truncated-bit statistic with a constant collision correction. + #[cfg(feature = "validation")] + BbitCollisionCorrected = 1, + /// Standard OPH matched-winner count divided by non-jointly-empty bins. + VerifiedWinners = 2, +} + +impl OphJaccardEstimator { + /// Stable identifier used in scientific provenance. + pub const fn as_str(self) -> &'static str { + match self { + #[cfg(feature = "validation")] + Self::BbitCollisionCorrected => "bbit_collision_corrected", + Self::VerifiedWinners => "verified_winners", + } + } +} + +/// Word-parallel fixed-register signature derived from an [`OphSketch`]. +/// +/// Each hash bit is stored as a separate bit plane. Comparing 64 registers therefore +/// requires one word operation per retained bit rather than 64 branch-heavy scalar +/// comparisons. A wider verification fingerprint protects directional votes from the +/// expected collisions in the shorter similarity signature. +#[derive(Clone, Debug, PartialEq)] +pub struct BitSlicedSignature { + register_count: usize, + words: usize, + fingerprint_bits: u8, + occupancy: Vec, + planes: Vec, + reverse_orientation: Vec, + ambiguous_orientation: Vec, + estimated_cardinality: f64, + observations: u64, +} + +impl BitSlicedSignature { + /// Number of represented registers. + pub fn register_count(&self) -> usize { + self.register_count + } + + pub(crate) fn fingerprint_bits(&self) -> u8 { + self.fingerprint_bits + } + + /// Estimated distinct cardinality copied from the source sketch. + pub fn estimated_cardinality(&self) -> f64 { + self.estimated_cardinality + } + + /// Number of valid k-mer occurrences in the summarized interval. + pub fn observations(&self) -> u64 { + self.observations + } + + /// Registers occupied in at least one of two signatures. + /// + /// # Panics + /// + /// Panics if the signatures have incompatible dimensions. + pub fn considered_with(&self, other: &Self, register_limit: usize) -> u32 { + assert_eq!(self.register_count, other.register_count); + let effective_registers = register_limit.min(self.register_count); + let effective_words = effective_registers.div_ceil(64); + (0..effective_words) + .map(|word| { + let word_mask = + if word + 1 == effective_words && !effective_registers.is_multiple_of(64) { + (1_u64 << (effective_registers % 64)) - 1 + } else { + u64::MAX + }; + ((self.occupancy[word] | other.occupancy[word]) & word_mask).count_ones() + }) + .sum() + } + + /// Approximate bytes owned by heap allocations, excluding allocator overhead. + pub fn estimated_heap_bytes(&self) -> usize { + [ + self.occupancy.capacity(), + self.planes.capacity(), + self.reverse_orientation.capacity(), + self.ambiguous_orientation.capacity(), + ] + .into_iter() + .sum::() + .saturating_mul(std::mem::size_of::()) + } + + /// Compares two signatures using word-parallel equality. + /// + /// # Panics + /// + /// Panics if the signatures have different dimensions or if `b` is zero or wider + /// than the stored verification fingerprint. + #[inline] + pub fn compare(&self, other: &Self, b: u8, register_limit: usize) -> RegisterComparison { + assert_eq!(self.register_count, other.register_count); + assert_eq!(self.fingerprint_bits, other.fingerprint_bits); + assert!((1..=self.fingerprint_bits).contains(&b)); + + let effective_registers = register_limit.min(self.register_count); + let effective_words = effective_registers.div_ceil(64); + let mut comparison = RegisterComparison::default(); + for word in 0..effective_words { + let word_mask = + if word + 1 == effective_words && !effective_registers.is_multiple_of(64) { + (1_u64 << (effective_registers % 64)) - 1 + } else { + u64::MAX + }; + let left_occupied = self.occupancy[word] & word_mask; + let right_occupied = other.occupancy[word] & word_mask; + comparison.considered += (left_occupied | right_occupied).count_ones(); + + let mut equal = left_occupied & right_occupied; + for bit in 0..b { + let plane = usize::from(bit) * self.words + word; + equal &= !(self.planes[plane] ^ other.planes[plane]); + if equal == 0 { + break; + } + } + comparison.bbit_matches += equal.count_ones(); + + if equal != 0 { + for bit in b..self.fingerprint_bits { + let plane = usize::from(bit) * self.words + word; + equal &= !(self.planes[plane] ^ other.planes[plane]); + if equal == 0 { + break; + } + } + } + comparison.verified_matches += equal.count_ones(); + + let ambiguous = + equal & (self.ambiguous_orientation[word] | other.ambiguous_orientation[word]); + let informative = equal & !ambiguous; + let orientation_difference = + self.reverse_orientation[word] ^ other.reverse_orientation[word]; + comparison.forward_matches += (informative & !orientation_difference).count_ones(); + comparison.reverse_matches += (informative & orientation_difference).count_ones(); + comparison.ambiguous_matches += ambiguous.count_ones(); + } + + comparison.jaccard = + verified_winner_jaccard(comparison.verified_matches, comparison.considered); + comparison + } + + /// Scalar comparator retained solely as an independent check of bit-sliced logic. + #[cfg(test)] + pub(crate) fn compare_scalar( + &self, + other: &Self, + b: u8, + register_limit: usize, + ) -> RegisterComparison { + assert_eq!(self.register_count, other.register_count); + assert_eq!(self.fingerprint_bits, other.fingerprint_bits); + assert!((1..=self.fingerprint_bits).contains(&b)); + let mut comparison = RegisterComparison::default(); + for register in 0..register_limit.min(self.register_count) { + let word = register / 64; + let mask = 1_u64 << (register % 64); + let left_occupied = self.occupancy[word] & mask != 0; + let right_occupied = other.occupancy[word] & mask != 0; + if !left_occupied && !right_occupied { + continue; + } + comparison.considered += 1; + if !(left_occupied && right_occupied) { + continue; + } + let bit_equal = |bit: u8| { + let plane = usize::from(bit) * self.words + word; + (self.planes[plane] ^ other.planes[plane]) & mask == 0 + }; + if !(0..b).all(bit_equal) { + continue; + } + comparison.bbit_matches += 1; + if !(b..self.fingerprint_bits).all(bit_equal) { + continue; + } + comparison.verified_matches += 1; + let ambiguous = + (self.ambiguous_orientation[word] | other.ambiguous_orientation[word]) & mask != 0; + if ambiguous { + comparison.ambiguous_matches += 1; + } else if (self.reverse_orientation[word] ^ other.reverse_orientation[word]) & mask != 0 + { + comparison.reverse_matches += 1; + } else { + comparison.forward_matches += 1; + } + } + comparison.jaccard = + verified_winner_jaccard(comparison.verified_matches, comparison.considered); + comparison + } +} + +/// Mergeable one-permutation sketch with full register winners. +/// +/// Full winners are retained in the scientific core. A bit-sliced representation is +/// derived for the optimized matrix kernel, while full hashes remain available for +/// differential testing and collision-free direction votes. +#[derive(Clone, Debug, PartialEq)] +pub struct OphSketch { + pub(crate) registers: Vec, + pub(crate) cardinality: HllSketch, + pub(crate) observations: u64, +} + +impl OphSketch { + /// Creates an empty sketch. `register_count` must be a power of two. + /// + /// # Panics + /// + /// Panics if the register count is not a power of two of at least two, or if the + /// HLL precision is outside `4..=16`. + pub fn new(register_count: usize, hll_precision: u8) -> Self { + assert!(register_count >= 2 && register_count.is_power_of_two()); + Self { + registers: vec![OphRegister::default(); register_count], + cardinality: HllSketch::new(hll_precision), + observations: 0, + } + } + + /// Number of one-permutation registers. + pub fn register_count(&self) -> usize { + self.registers.len() + } + + /// Number of valid k-mer occurrences offered to the sketch. + pub fn observations(&self) -> u64 { + self.observations + } + + /// Estimated number of distinct canonical hashes. + pub fn estimated_cardinality(&self) -> f64 { + self.cardinality.estimate() + } + + /// Number of registers containing a retained winner. + pub fn occupied_registers(&self) -> usize { + self.registers + .iter() + .filter(|register| register.occupied) + .count() + } + + /// Returns an occupied register's full winner and combined orientation. + pub fn winner(&self, register: usize) -> Option<(u64, Orientation)> { + self.registers + .get(register) + .and_then(|winner| winner.occupied.then_some((winner.hash, winner.orientation))) + } + + /// Approximate bytes owned by heap allocations, excluding allocator overhead. + pub fn estimated_heap_bytes(&self) -> usize { + self.registers + .capacity() + .saturating_mul(std::mem::size_of::()) + .saturating_add(self.cardinality.estimated_heap_bytes()) + } + + /// Adds one pre-hashed canonical k-mer. + #[allow(clippy::cast_possible_truncation)] + pub fn insert(&mut self, hash: u64, orientation: Orientation) { + let bucket_bits = self.registers.len().trailing_zeros(); + // Only bucket_bits are retained, and register_count is addressable usize. + let bucket = (hash >> (64 - bucket_bits)) as usize; + let register = &mut self.registers[bucket]; + if !register.occupied || hash < register.hash { + *register = OphRegister { + hash, + orientation, + occupied: true, + }; + } else if hash == register.hash { + register.orientation = register.orientation.combine(orientation); + } + self.cardinality.insert(hash); + self.observations += 1; + } + + /// Merges a disjoint coordinate summary into this sketch. + /// + /// # Panics + /// + /// Panics if the sketches have different register counts or HLL precision. + pub fn merge(&mut self, other: &Self) { + assert_eq!(self.registers.len(), other.registers.len()); + for (left, right) in self.registers.iter_mut().zip(&other.registers) { + if !right.occupied { + continue; + } + if !left.occupied || right.hash < left.hash { + *left = *right; + } else if right.hash == left.hash { + left.orientation = left.orientation.combine(right.orientation); + } + } + self.cardinality.merge(&other.cardinality); + self.observations = self.observations.saturating_add(other.observations); + } + + /// Compares a prefix of aligned registers with `b` retained hash bits. + /// + /// Empty registers remain empty in the selected undensified production estimator. + /// + /// # Panics + /// + /// Panics if register counts differ or `b` is outside `1..=32`. + pub fn compare_bbit(&self, other: &Self, b: u8, register_limit: usize) -> RegisterComparison { + assert_eq!(self.registers.len(), other.registers.len()); + assert!((1..=32).contains(&b)); + let limit = register_limit.min(self.registers.len()); + let mask = (1_u64 << b) - 1; + let mut comparison = RegisterComparison::default(); + + for (left, right) in self.registers[..limit] + .iter() + .zip(&other.registers[..limit]) + { + if !left.occupied && !right.occupied { + continue; + } + comparison.considered += 1; + if left.occupied && right.occupied && (left.hash & mask) == (right.hash & mask) { + comparison.bbit_matches += 1; + } + + if left.occupied && right.occupied && left.hash == right.hash { + comparison.verified_matches += 1; + match (left.orientation, right.orientation) { + (Orientation::Forward, Orientation::Forward) + | (Orientation::Reverse, Orientation::Reverse) => { + comparison.forward_matches += 1; + } + (Orientation::Forward, Orientation::Reverse) + | (Orientation::Reverse, Orientation::Forward) => { + comparison.reverse_matches += 1; + } + _ => comparison.ambiguous_matches += 1, + } + } + } + + comparison.jaccard = + verified_winner_jaccard(comparison.verified_matches, comparison.considered); + comparison + } + + /// Encodes a register prefix into a word-parallel bit-sliced signature. + /// + /// # Panics + /// + /// Panics if `fingerprint_bits` is outside `1..=64`. + pub fn bit_sliced(&self, fingerprint_bits: u8, register_limit: usize) -> BitSlicedSignature { + assert!((1..=64).contains(&fingerprint_bits)); + let register_count = register_limit.min(self.registers.len()); + let words = register_count.div_ceil(64); + let mut signature = BitSlicedSignature { + register_count, + words, + fingerprint_bits, + occupancy: vec![0; words], + planes: vec![0; words * usize::from(fingerprint_bits)], + reverse_orientation: vec![0; words], + ambiguous_orientation: vec![0; words], + estimated_cardinality: self.estimated_cardinality(), + observations: self.observations, + }; + + for (index, register) in self.registers[..register_count].iter().enumerate() { + if !register.occupied { + continue; + } + let word = index / 64; + let flag = 1_u64 << (index % 64); + signature.occupancy[word] |= flag; + for bit in 0..fingerprint_bits { + if register.hash & (1_u64 << bit) != 0 { + signature.planes[usize::from(bit) * words + word] |= flag; + } + } + match register.orientation { + Orientation::Reverse => signature.reverse_orientation[word] |= flag, + Orientation::Both | Orientation::Unknown => { + signature.ambiguous_orientation[word] |= flag; + } + Orientation::Forward => {} + } + } + signature + } +} + +/// Estimates OPH resemblance from verified winner equality over union-occupied bins. +pub fn verified_winner_jaccard(matches: u32, considered: u32) -> Option { + (considered > 0).then(|| f64::from(matches) / f64::from(considered)) +} + +/// Mergeable `HyperLogLog` cardinality sidecar. +#[derive(Clone, Debug, PartialEq)] +pub struct HllSketch { + precision: u8, + registers: Vec, +} + +impl HllSketch { + /// Creates an HLL with `2^precision` registers. + /// + /// # Panics + /// + /// Panics if precision is outside `4..=16`. + pub fn new(precision: u8) -> Self { + assert!((4..=16).contains(&precision)); + Self { + precision, + registers: vec![0; 1 << precision], + } + } + + /// Adds one 64-bit hash. + #[allow(clippy::cast_possible_truncation)] + pub fn insert(&mut self, hash: u64) { + // A second avalanche prevents OPH bucket bits and HLL registers from being + // identical random variables while retaining deterministic behavior. + let hash = fmix64(hash ^ 0xd6e8_feb8_6659_fd93); + // Precision is at most 16, so the retained value is addressable on Wasm32. + let index = (hash >> (64 - self.precision)) as usize; + let remainder = hash << self.precision; + let max_rank = 64 - self.precision + 1; + let rank = (remainder.leading_zeros() + 1).min(u32::from(max_rank)) as u8; + self.registers[index] = self.registers[index].max(rank); + } + + /// Merges the union of another HLL into this one. + /// + /// # Panics + /// + /// Panics if the HLL precisions differ. + pub fn merge(&mut self, other: &Self) { + assert_eq!(self.precision, other.precision); + for (left, right) in self.registers.iter_mut().zip(&other.registers) { + *left = (*left).max(*right); + } + } + + /// Estimates distinct cardinality, including the standard small-range correction. + pub fn estimate(&self) -> f64 { + let register_count = 1_u32 << self.precision; + let m = f64::from(register_count); + let zeros = self.registers.iter().fold( + 0_u32, + |count, &value| if value == 0 { count + 1 } else { count }, + ); + if zeros == register_count { + return 0.0; + } + + let alpha = match self.registers.len() { + 16 => 0.673, + 32 => 0.697, + 64 => 0.709, + _ => 0.7213 / (1.0 + 1.079 / m), + }; + let harmonic: f64 = self + .registers + .iter() + .map(|&rank| 2_f64.powi(-i32::from(rank))) + .sum(); + let raw = alpha * m * m / harmonic; + if raw <= 2.5 * m && zeros > 0 { + m * (m / f64::from(zeros)).ln() + } else { + raw + } + } + + fn estimated_heap_bytes(&self) -> usize { + self.registers.capacity() + } +} + +#[cfg(test)] +#[allow(deprecated)] +mod tests { + use super::*; + + #[test] + fn identical_sketches_compare_as_one() { + let mut sketch = OphSketch::new(256, 10); + for value in 0..20_000_u64 { + sketch.insert(fmix64(value), Orientation::Forward); + } + let comparison = sketch.compare_bbit(&sketch, 14, 256); + assert_eq!(comparison.jaccard, Some(1.0)); + assert_eq!(comparison.forward_matches, comparison.verified_matches); + } + + #[test] + fn merge_matches_direct_insertion() { + let mut left = OphSketch::new(128, 8); + let mut right = OphSketch::new(128, 8); + let mut direct = OphSketch::new(128, 8); + for value in 0..10_000_u64 { + let hash = fmix64(value); + if value % 2 == 0 { + left.insert(hash, Orientation::Forward); + } else { + right.insert(hash, Orientation::Forward); + } + direct.insert(hash, Orientation::Forward); + } + left.merge(&right); + assert_eq!(left, direct); + } + + #[test] + fn orientation_votes_distinguish_reverse_matches() { + let mut left = OphSketch::new(64, 8); + let mut right = OphSketch::new(64, 8); + for value in 0..5000_u64 { + let hash = fmix64(value); + left.insert(hash, Orientation::Forward); + right.insert(hash, Orientation::Reverse); + } + let comparison = left.compare_bbit(&right, 14, 64); + assert_eq!(comparison.jaccard, Some(1.0)); + assert_eq!(comparison.reverse_matches, comparison.verified_matches); + assert_eq!(comparison.forward_matches, 0); + } + + #[test] + fn bbit_only_collision_cannot_create_similarity_without_verified_evidence() { + let mut left = OphSketch::new(2, 4); + let mut right = OphSketch::new(2, 4); + // Both hashes occupy register zero and share their low 14 bits, but their + // verification fingerprints differ. + left.insert(0x1000_0000_0000_1234, Orientation::Forward); + right.insert(0x2000_0000_0000_1234, Orientation::Forward); + let comparison = left.compare_bbit(&right, 14, 2); + assert_eq!(comparison.bbit_matches, 1); + assert_eq!(comparison.verified_matches, 0); + assert_eq!(comparison.jaccard, Some(0.0)); + } + + #[test] + fn bit_sliced_comparison_matches_scalar_reference() { + let mut left = OphSketch::new(512, 10); + let mut right = OphSketch::new(512, 10); + for value in 0..30_000_u64 { + let hash = fmix64(value); + left.insert(hash, Orientation::Forward); + if value % 5 != 0 { + right.insert(hash, Orientation::Reverse); + } + } + for value in 40_000..47_500_u64 { + right.insert(fmix64(value), Orientation::Forward); + } + + let scalar = left.compare_bbit(&right, 14, 512); + let encoded_left = left.bit_sliced(64, 512); + let encoded_right = right.bit_sliced(64, 512); + let bit_sliced = encoded_left.compare(&encoded_right, 14, 512); + assert_eq!(scalar, bit_sliced); + } + + #[test] + #[cfg(feature = "validation")] + fn projected_registers_match_direct_coarse_insertion() { + let mut detailed = OphSketch::new(2_048, 10); + let mut quick = OphSketch::new(256, 10); + for value in 0..100_000_u64 { + let hash = fmix64(value.wrapping_mul(0xd6e8_feb8_6659_fd93)); + detailed.insert(hash, Orientation::Forward); + quick.insert(hash, Orientation::Forward); + } + assert_eq!(detailed.project_registers(256), quick); + } + + #[test] + fn hll_cardinality_is_within_expected_error() { + let mut hll = HllSketch::new(12); + for value in 0..100_000_u64 { + hll.insert(fmix64(value)); + } + let relative_error = (hll.estimate() - 100_000.0).abs() / 100_000.0; + assert!(relative_error < 0.05, "relative error was {relative_error}"); + } +} diff --git a/crates/core/src/validation/adaptive.rs b/crates/core/src/validation/adaptive.rs new file mode 100644 index 0000000..4c5af5d --- /dev/null +++ b/crates/core/src/validation/adaptive.rs @@ -0,0 +1,141 @@ +//! Statistical policy for validation of the bounded exact sparse-window fallback. + +/// ANI floor below which an occasional missed window is acceptable in the interactive plot. +pub const DEFAULT_ANI_FLOOR: f64 = 0.80; + +/// Desired probability of observing at least one true matching OPH register at the ANI floor. +pub const DEFAULT_MATCH_DETECTION_PROBABILITY: f64 = 0.99; + +/// Maximum distinct canonical hashes retained exactly for one sparse core window. +pub const DEFAULT_EXACT_CORE_CAP: usize = 2_048; + +/// Fast first-pass register count used by the browser. +pub const DEFAULT_PREVIEW_REGISTERS: usize = 256; + +/// Default background-refinement register count used by the browser. +pub const DEFAULT_DETAILED_REGISTERS: usize = 1_024; + +/// Converts a desired nonzero-match probability into a Poisson expected-match threshold. +/// +/// For example, 99% requires `-ln(0.01)`, or approximately 4.605 expected matches. +/// +/// # Panics +/// +/// Panics unless `probability` lies in `[0, 1)`. +pub fn expected_matches_for_probability(probability: f64) -> f64 { + assert!((0.0..1.0).contains(&probability)); + -(1.0 - probability).ln() +} + +/// Expected true OPH register matches at a directed containment floor. +/// +/// The occupancy term accounts for an undensified sketch. Cardinalities may be exact +/// or estimated; the model is used as a scheduling decision, not as an accuracy claim. +/// +/// # Panics +/// +/// Panics if `register_count` does not fit in `u32`; supported browser tiers are at +/// most 4,096 registers. +pub fn expected_oph_matches( + core_cardinality: f64, + target_cardinality: f64, + register_count: usize, + containment_floor: f64, +) -> f64 { + if core_cardinality <= 0.0 || target_cardinality <= 0.0 { + return f64::INFINITY; + } + let intersection = containment_floor * core_cardinality; + if intersection > target_cardinality { + // This directed comparison cannot attain the requested containment floor. + return f64::INFINITY; + } + let union = core_cardinality + target_cardinality - intersection; + let registers = + f64::from(u32::try_from(register_count).expect("a supported register count must fit u32")); + let load = union / registers; + let occupied_union_registers = if load >= 20.0 { + registers + } else { + registers * (-load).exp_m1().abs() + }; + occupied_union_registers * intersection / union +} + +/// Poisson probability that an OPH comparison observes at least one true match. +pub fn probability_of_at_least_one_match(expected_matches: f64) -> f64 { + if expected_matches.is_infinite() { + return 1.0; + } + -(-expected_matches.max(0.0)).exp_m1() +} + +/// Whether a retained exact core should replace an underpowered sketch comparison. +/// +/// # Panics +/// +/// Panics if `core_cardinality` does not fit in `u32`; the production exact cap is +/// 2,048 distinct hashes. It also inherits the register-count constraint from +/// [`expected_oph_matches`]. +pub fn needs_exact_correction( + core_cardinality: usize, + target_cardinality: f64, + register_count: usize, + k: u8, + ani_floor: f64, + minimum_detection_probability: f64, +) -> bool { + let containment_floor = ani_floor.powi(i32::from(k)); + let expected = expected_oph_matches( + f64::from( + u32::try_from(core_cardinality).expect("an exact sparse-core cardinality must fit u32"), + ), + target_cardinality, + register_count, + containment_floor, + ); + probability_of_at_least_one_match(expected) < minimum_detection_probability +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ninety_nine_percent_threshold_is_four_point_six_matches() { + let threshold = expected_matches_for_probability(0.99); + assert!((threshold - 4.605_170_185_988_091).abs() < 1e-12); + assert!((probability_of_at_least_one_match(threshold) - 0.99).abs() < 1e-12); + } + + #[test] + fn equal_dense_windows_match_expected_detection_rates() { + let containment = DEFAULT_ANI_FLOOR.powi(21); + let expected_256 = expected_oph_matches(1_000_000.0, 1_000_000.0, 256, containment); + let expected_1024 = expected_oph_matches(1_000_000.0, 1_000_000.0, 1_024, containment); + let expected_2048 = expected_oph_matches(1_000_000.0, 1_000_000.0, 2_048, containment); + assert!((probability_of_at_least_one_match(expected_256) - 0.695).abs() < 0.002); + assert!(probability_of_at_least_one_match(expected_1024) > 0.99); + assert!(probability_of_at_least_one_match(expected_2048) > 0.9999); + } + + #[test] + fn cardinality_imbalance_can_trigger_exact_fallback() { + assert!(!needs_exact_correction( + 2_000, + 2_000.0, + DEFAULT_DETAILED_REGISTERS, + 21, + DEFAULT_ANI_FLOOR, + DEFAULT_MATCH_DETECTION_PROBABILITY, + )); + assert!(needs_exact_correction( + 2_000, + 20_000.0, + DEFAULT_DETAILED_REGISTERS, + 21, + DEFAULT_ANI_FLOOR, + DEFAULT_MATCH_DETECTION_PROBABILITY, + )); + } +} diff --git a/crates/core/src/validation/estimators.rs b/crates/core/src/validation/estimators.rs new file mode 100644 index 0000000..1d13202 --- /dev/null +++ b/crates/core/src/validation/estimators.rs @@ -0,0 +1,92 @@ +//! Candidate estimator kernels retained only for controlled scientific comparison. + +use crate::containment::{ContainmentEstimate, estimate_from_comparison}; +use crate::sketch::{ + BitSlicedSignature, OphJaccardEstimator, OphSketch, RegisterComparison, verified_winner_jaccard, +}; + +/// Estimates directed containment with an explicit OPH resemblance statistic. +pub fn estimate_containment_with( + x: &OphSketch, + y: &OphSketch, + k: u8, + b: u8, + register_limit: usize, + estimator: OphJaccardEstimator, +) -> Option { + let mut registers = x.compare_bbit(y, b, register_limit); + registers.jaccard = select_jaccard(registers, b, estimator); + estimate_from_comparison( + x.estimated_cardinality(), + y.estimated_cardinality(), + k, + registers, + ) +} + +/// Estimates directed containment from word-parallel signatures and an explicit statistic. +pub fn estimate_containment_bit_sliced_with( + x: &BitSlicedSignature, + y: &BitSlicedSignature, + k: u8, + b: u8, + register_limit: usize, + estimator: OphJaccardEstimator, +) -> Option { + let mut registers = x.compare(y, b, register_limit); + registers.jaccard = select_jaccard(registers, b, estimator); + estimate_from_comparison( + x.estimated_cardinality(), + y.estimated_cardinality(), + k, + registers, + ) +} + +/// Estimates containment from externally accumulated counters and an explicit statistic. +pub fn estimate_containment_from_comparison_with( + x_cardinality: f64, + y_cardinality: f64, + k: u8, + b: u8, + mut registers: RegisterComparison, + estimator: OphJaccardEstimator, +) -> Option { + registers.jaccard = select_jaccard(registers, b, estimator); + estimate_from_comparison(x_cardinality, y_cardinality, k, registers) +} + +fn select_jaccard( + registers: RegisterComparison, + b: u8, + estimator: OphJaccardEstimator, +) -> Option { + match estimator { + OphJaccardEstimator::BbitCollisionCorrected => collision_corrected_jaccard( + registers.bbit_matches, + registers.verified_matches, + registers.considered, + b, + ), + OphJaccardEstimator::VerifiedWinners => { + verified_winner_jaccard(registers.verified_matches, registers.considered) + } + } +} + +fn collision_corrected_jaccard( + matches: u32, + verified_matches: u32, + considered: u32, + b: u8, +) -> Option { + if considered == 0 { + return None; + } + if verified_matches == 0 { + return Some(0.0); + } + let raw = f64::from(matches) / f64::from(considered); + let collision = 2_f64.powi(-i32::from(b)); + Some(((raw - collision) / (1.0 - collision)).clamp(0.0, 1.0)) +} diff --git a/crates/core/src/validation/mod.rs b/crates/core/src/validation/mod.rs new file mode 100644 index 0000000..077c67c --- /dev/null +++ b/crates/core/src/validation/mod.rs @@ -0,0 +1,24 @@ +//! Non-default research estimators, projections, and correction kernels. +//! +//! Nothing in this namespace is linked into ordinary production builds. These APIs +//! exist only for controlled comparisons against the fixed browser estimator. + +pub mod adaptive; +mod estimators; +mod projection; +pub(crate) mod sparse; + +pub use crate::matrix::{ + SparseCoreHashSet, SparseCorrectionPolicy, compute_frozen_tile_adaptive, compute_tile_adaptive, +}; +pub use crate::sketch::OphJaccardEstimator; +pub use adaptive::{ + DEFAULT_ANI_FLOOR, DEFAULT_DETAILED_REGISTERS, DEFAULT_EXACT_CORE_CAP, + DEFAULT_MATCH_DETECTION_PROBABILITY, DEFAULT_PREVIEW_REGISTERS, + expected_matches_for_probability, expected_oph_matches, needs_exact_correction, + probability_of_at_least_one_match, +}; +pub use estimators::{ + estimate_containment_bit_sliced_with, estimate_containment_from_comparison_with, + estimate_containment_with, +}; diff --git a/crates/core/src/validation/projection.rs b/crates/core/src/validation/projection.rs new file mode 100644 index 0000000..c495e5d --- /dev/null +++ b/crates/core/src/validation/projection.rs @@ -0,0 +1,96 @@ +//! Rejected fused-build projections retained only for controlled benchmarks. + +use crate::ScientificConfig; +use crate::matrix::AxisOverview; +use crate::sketch::OphSketch; + +impl OphSketch { + /// Projects a fine OPH partition into an equivalent coarser partition. + /// + /// # Panics + /// + /// Panics unless the requested register count is a power-of-two divisor of the + /// source count and is at least two. + #[must_use] + pub fn project_registers(&self, register_count: usize) -> Self { + assert!(register_count >= 2 && register_count.is_power_of_two()); + assert!(register_count <= self.registers.len()); + assert_eq!(self.registers.len() % register_count, 0); + let group = self.registers.len() / register_count; + let registers = self + .registers + .chunks_exact(group) + .map(|candidates| { + candidates + .iter() + .copied() + .filter(|candidate| candidate.occupied) + .min_by_key(|candidate| candidate.hash) + .unwrap_or_default() + }) + .collect(); + Self { + registers, + cardinality: self.cardinality.clone(), + observations: self.observations, + } + } +} + +impl AxisOverview { + /// Derives an equivalent coarser OPH tier without rehashing the sequence. + /// + /// # Panics + /// + /// Panics unless the requested register count and fingerprint width form a valid + /// projection of the source scientific configuration. + #[must_use] + pub fn project_registers(&self, register_count: usize, fingerprint_bits: u8) -> Self { + let core = self + .core + .iter() + .map(|sketch| sketch.project_registers(register_count)) + .collect::>(); + let expanded = self + .expanded + .iter() + .map(|sketch| sketch.project_registers(register_count)) + .collect::>(); + let core_encoded = core + .iter() + .map(|sketch| sketch.bit_sliced(fingerprint_bits, register_count)) + .collect(); + let expanded_encoded = expanded + .iter() + .map(|sketch| sketch.bit_sliced(fingerprint_bits, register_count)) + .collect(); + Self { + scientific_config: ScientificConfig::new( + self.scientific_config.k(), + self.scientific_config.hash_algorithm(), + self.scientific_config.hash_seed(), + register_count, + fingerprint_bits.min(self.scientific_config.b_bits()), + fingerprint_bits, + self.scientific_config.oph_estimator(), + self.scientific_config.hll_precision(), + 0, + self.scientific_config.ani_floor(), + self.scientific_config.minimum_detection_probability(), + ) + .expect("a supported projection must produce a valid scientific identity"), + sequence_identity: self.sequence_identity, + sequence_name: self.sequence_name.clone(), + sequence_length: self.sequence_length, + domain_length: self.domain_length, + resolution: self.resolution, + offset: self.offset, + bounds: self.bounds.clone(), + core, + expanded, + core_encoded, + expanded_encoded, + sparse_core: Vec::new(), + } + } +} diff --git a/crates/core/src/validation/sparse.rs b/crates/core/src/validation/sparse.rs new file mode 100644 index 0000000..a877210 --- /dev/null +++ b/crates/core/src/validation/sparse.rs @@ -0,0 +1,33 @@ +//! Validation-only sparse-correction contract. + +use crate::PackedSequence; +use crate::matrix::{AxisData, TileBuildError}; + +/// Statistical decision used by detailed tiles with bounded exact sparse cores. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct SparseCorrectionPolicy { + /// ANI floor at which match detection is evaluated. + pub ani_floor: f64, + /// Minimum probability of observing at least one true matching register. + pub minimum_detection_probability: f64, +} + +pub(crate) fn validate_source_sequences( + x: &impl AxisData, + y: &impl AxisData, + x_sequence: &PackedSequence, + y_sequence: &PackedSequence, +) -> Result<(), TileBuildError> { + if x.sequence_length() != x_sequence.len() + || y.sequence_length() != y_sequence.len() + || x.sequence_name() != x_sequence.name() + || y.sequence_name() != y_sequence.name() + || x.sequence_identity() != x_sequence.identity() + || y.sequence_identity() != y_sequence.identity() + || x.domain_length() < x_sequence.len() + || y.domain_length() < y_sequence.len() + { + return Err(TileBuildError::IncompatibleSequence); + } + Ok(()) +} diff --git a/crates/core/tests/estimator_validation.rs b/crates/core/tests/estimator_validation.rs new file mode 100644 index 0000000..786a238 --- /dev/null +++ b/crates/core/tests/estimator_validation.rs @@ -0,0 +1,879 @@ +//! Multi-seed differential and spatial validation for OPH estimator candidates. + +#![allow(deprecated)] + +use moddotplot_core::hash::HashAlgorithm; +use moddotplot_core::validation::{ + DEFAULT_ANI_FLOOR, DEFAULT_MATCH_DETECTION_PROBABILITY, estimate_containment_bit_sliced_with, +}; +use moddotplot_core::{ + AxisOverview, DEFAULT_B_BITS, DEFAULT_VERIFICATION_BITS, ExactKmerSet, OphJaccardEstimator, + PackedSequence, ScientificConfig, build_axis_overview, exact_moddotplot_score, +}; +use std::time::{Duration, Instant}; + +const K: u8 = 21; +const RESOLUTION: usize = 12; +const ANI_FLOOR: f64 = 0.80; +const THRESHOLDS: [f64; 4] = [0.80, 0.85, 0.90, 0.95]; + +#[derive(Clone, Copy, Default)] +struct Confusion { + false_negative: usize, + false_positive: usize, +} + +#[derive(Default)] +struct Metrics { + relevant_errors: Vec, + bias_sum: f64, + squared_error_sum: f64, + confusion: [Confusion; THRESHOLDS.len()], + diagonal_holes_80: usize, + one_pixel_holes_85: usize, + isolated_false_pixels: usize, + exact_forward_edges: usize, + preserved_forward_edges: usize, + exact_reverse_edges: usize, + preserved_reverse_edges: usize, + local_3_error_sum: f64, + local_5_error_sum: f64, + local_3_count: usize, + local_5_count: usize, + neighbor_error_correlation_sum: f64, + top_feature_overlap_sum: f64, + component_count_error_sum: usize, + spatial_matrices: usize, + compared: usize, + union_occupancy_sum: f64, + minimum_union_occupancy: Option, + union_occupancy_samples: usize, + fingerprint_collisions: u64, + exact_zero_cells: usize, + estimated_zero_cells: usize, + comparison_time: Duration, +} + +impl Metrics { + fn record(&mut self, exact: f64, estimated: ApproximateScore, row: usize, column: usize) { + self.compared += 1; + self.exact_zero_cells += usize::from(exact == 0.0); + self.estimated_zero_cells += usize::from(estimated.ani_c == 0.0); + self.union_occupancy_sum += estimated.mean_union_occupancy; + self.minimum_union_occupancy = Some( + self.minimum_union_occupancy + .map_or(estimated.minimum_union_occupancy, |current| { + current.min(estimated.minimum_union_occupancy) + }), + ); + self.union_occupancy_samples += 1; + if exact >= ANI_FLOOR { + let difference = estimated.ani_c - exact; + self.relevant_errors.push(difference.abs()); + self.bias_sum += difference; + self.squared_error_sum += difference * difference; + if row == column && estimated.ani_c < ANI_FLOOR { + self.diagonal_holes_80 += 1; + } + } + for (index, threshold) in THRESHOLDS.into_iter().enumerate() { + self.confusion[index].false_negative += + usize::from(exact >= threshold && estimated.ani_c < threshold); + self.confusion[index].false_positive += + usize::from(exact < threshold && estimated.ani_c >= threshold); + } + } + + fn summary(&mut self) -> Summary { + self.relevant_errors.sort_by(f64::total_cmp); + if self.relevant_errors.is_empty() { + return Summary::default(); + } + let count = self.relevant_errors.len().max(1); + let count_float = f64::from(u32::try_from(count).expect("fixture count fits u32")); + let mean = self.relevant_errors.iter().sum::() / count_float; + let p95 = self.relevant_errors[(self.relevant_errors.len().saturating_sub(1) * 95) / 100]; + let bias = self.bias_sum / count_float; + let variance = (self.squared_error_sum / count_float - bias * bias).max(0.0); + Summary { + mean, + p95, + bias, + variance, + mean_union_occupancy: mean_error( + self.union_occupancy_sum, + self.union_occupancy_samples, + ), + minimum_union_occupancy: self.minimum_union_occupancy.unwrap_or(f64::NAN), + } + } + + fn record_spatial(&mut self, exact: &[Option], estimated: &[Option], width: usize) { + let exact_mask = threshold_mask(exact, 0.85); + let estimated_mask = threshold_mask(estimated, 0.85); + for index in 0..exact.len() { + let neighbors = neighbors(index, width); + if estimated_mask[index] + && !exact_mask[index] + && neighbors.iter().all(|&neighbor| !estimated_mask[neighbor]) + { + self.isolated_false_pixels += 1; + } + if exact_mask[index] + && !estimated_mask[index] + && neighbors + .iter() + .filter(|&&neighbor| exact_mask[neighbor] && estimated_mask[neighbor]) + .count() + >= 2 + { + self.one_pixel_holes_85 += 1; + } + } + + for row in 0..width.saturating_sub(1) { + for column in 0..width.saturating_sub(1) { + let here = row * width + column; + let forward = (row + 1) * width + column + 1; + if exact_mask[here] && exact_mask[forward] { + self.exact_forward_edges += 1; + self.preserved_forward_edges += + usize::from(estimated_mask[here] && estimated_mask[forward]); + } + let reverse_here = row * width + column + 1; + let reverse = (row + 1) * width + column; + if exact_mask[reverse_here] && exact_mask[reverse] { + self.exact_reverse_edges += 1; + self.preserved_reverse_edges += + usize::from(estimated_mask[reverse_here] && estimated_mask[reverse]); + } + } + } + + for index in 0..exact.len() { + if let (Some(exact_local), Some(estimated_local)) = ( + local_mean(exact, index, width, 1), + local_mean(estimated, index, width, 1), + ) { + self.local_3_error_sum += (estimated_local - exact_local).abs(); + self.local_3_count += 1; + } + if let (Some(exact_local), Some(estimated_local)) = ( + local_mean(exact, index, width, 2), + local_mean(estimated, index, width, 2), + ) { + self.local_5_error_sum += (estimated_local - exact_local).abs(); + self.local_5_count += 1; + } + } + + self.neighbor_error_correlation_sum += neighbor_error_correlation(exact, estimated, width); + self.top_feature_overlap_sum += top_feature_overlap(exact, estimated); + self.component_count_error_sum += + component_count(&exact_mask, width).abs_diff(component_count(&estimated_mask, width)); + self.spatial_matrices += 1; + } +} + +#[test] +#[ignore = "preregistered v0.5 production grid; run with --release --ignored --nocapture"] +fn report_v0_5_preregistered_production_grid() { + println!( + "registers\tmean_abs_ani_c\tp95_abs_ani_c\tani_c_bias\tani_c_error_variance\tmean_union_occupancy\tminimum_union_occupancy\tfn80\tfp80\tfn85\tfp85\tfn90\tfp90\tfn95\tfp95\tvisibility_crossings80\tcrossing_rate80\tzero_inflation\tdiagonal_holes80\tone_pixel_holes85\tisolated_false85\tforward_continuity85\treverse_continuity85\tlocal3_error\tlocal5_error\tneighbor_error_corr\ttop_feature_overlap\tcomponent_count_error85\tobserved_fingerprint_collisions\texpected_fingerprint_collisions_per_cell\texpected_fingerprint_collisions_per_12x12_matrix\tcomparison_ms\tselected_config_gate" + ); + for registers in [256, 1_024] { + let mut combined = Metrics::default(); + for seed in [0, 42, 7_919, 0xd6e8_feb8_6659_fd93] { + for (left, right) in v0_5_fixtures() { + evaluate_pair( + &left, + &right, + seed, + registers, + 10, + OphJaccardEstimator::VerifiedWinners, + &mut combined, + ); + } + } + let summary = combined.summary(); + let selected_config_gate = assert_selected_configuration_gate( + registers, + 10, + OphJaccardEstimator::VerifiedWinners, + &combined, + summary, + ); + let visibility_crossings = + combined.confusion[0].false_negative + combined.confusion[0].false_positive; + let expected_per_cell = + 2.0 * registers_as_f64(registers) * summary.mean_union_occupancy.powi(2) + / 2_f64.powi(i32::from(DEFAULT_VERIFICATION_BITS)); + assert_eq!( + combined.fingerprint_collisions, 0, + "the preregistered production grid observed a verified-winner fingerprint collision" + ); + assert!( + expected_per_cell < 1.0e-6, + "expected verified-winner fingerprint collisions per cell were material" + ); + println!( + "{registers}\t{:.6}\t{:.6}\t{:.6}\t{:.8}\t{:.6}\t{:.6}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{}\t{}\t{}\t{}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{}\t{expected_per_cell:.10}\t{:.8}\t{:.3}\t{selected_config_gate}", + summary.mean, + summary.p95, + summary.bias, + summary.variance, + summary.mean_union_occupancy, + summary.minimum_union_occupancy, + combined.confusion[0].false_negative, + combined.confusion[0].false_positive, + combined.confusion[1].false_negative, + combined.confusion[1].false_positive, + combined.confusion[2].false_negative, + combined.confusion[2].false_positive, + combined.confusion[3].false_negative, + combined.confusion[3].false_positive, + visibility_crossings, + ratio(visibility_crossings, combined.compared), + combined + .estimated_zero_cells + .saturating_sub(combined.exact_zero_cells), + combined.diagonal_holes_80, + combined.one_pixel_holes_85, + combined.isolated_false_pixels, + ratio( + combined.preserved_forward_edges, + combined.exact_forward_edges + ), + ratio( + combined.preserved_reverse_edges, + combined.exact_reverse_edges + ), + mean_error(combined.local_3_error_sum, combined.local_3_count), + mean_error(combined.local_5_error_sum, combined.local_5_count), + mean_error( + combined.neighbor_error_correlation_sum, + combined.spatial_matrices + ), + mean_error(combined.top_feature_overlap_sum, combined.spatial_matrices), + ratio( + combined.component_count_error_sum, + combined.spatial_matrices + ), + combined.fingerprint_collisions, + expected_per_cell + * f64::from( + u32::try_from(RESOLUTION * RESOLUTION) + .expect("validation matrix cell count fits u32"), + ), + combined.comparison_time.as_secs_f64() * 1_000.0, + ); + } +} + +#[derive(Clone, Copy, Default)] +struct Summary { + mean: f64, + p95: f64, + bias: f64, + variance: f64, + mean_union_occupancy: f64, + minimum_union_occupancy: f64, +} + +#[derive(Clone, Copy)] +struct ApproximateScore { + ani_c: f64, + mean_union_occupancy: f64, + minimum_union_occupancy: f64, +} + +#[test] +#[ignore = "explicit scientific benchmark; run with --release --ignored --nocapture"] +fn report_estimator_hll_and_fingerprint_sweep() { + println!( + "estimator\tregisters\thll\tmean_abs_ani_c\tp95_abs_ani_c\tani_c_bias\tani_c_error_variance\tmean_union_occupancy\tminimum_union_occupancy\tfn80\tfp80\tfn85\tfp85\tfn90\tfp90\tfn95\tfp95\tdiagonal_holes80\tone_pixel_holes85\tisolated_false85\tforward_continuity85\treverse_continuity85\tlocal3_error\tlocal5_error\tneighbor_error_corr\ttop_feature_overlap\tcomponent_count_error85\tfingerprint_collisions\tcomparison_ms\tselected_config_gate" + ); + for registers in [256, 1_024, 2_048] { + for hll_precision in [8, 9, 10, 11, 12] { + for estimator in [ + OphJaccardEstimator::BbitCollisionCorrected, + OphJaccardEstimator::VerifiedWinners, + ] { + let mut combined = Metrics::default(); + for seed in [0, 42, 7_919, 0xd6e8_feb8_6659_fd93] { + for (left, right) in fixtures() { + evaluate_pair( + &left, + &right, + seed, + registers, + hll_precision, + estimator, + &mut combined, + ); + } + } + let summary = combined.summary(); + let selected_config_gate = assert_selected_configuration_gate( + registers, + hll_precision, + estimator, + &combined, + summary, + ); + println!( + "{estimator:?}\t{registers}\t{hll_precision}\t{:.6}\t{:.6}\t{:.6}\t{:.8}\t{:.6}\t{:.6}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{}\t{:.3}\t{selected_config_gate}", + summary.mean, + summary.p95, + summary.bias, + summary.variance, + summary.mean_union_occupancy, + summary.minimum_union_occupancy, + combined.confusion[0].false_negative, + combined.confusion[0].false_positive, + combined.confusion[1].false_negative, + combined.confusion[1].false_positive, + combined.confusion[2].false_negative, + combined.confusion[2].false_positive, + combined.confusion[3].false_negative, + combined.confusion[3].false_positive, + combined.diagonal_holes_80, + combined.one_pixel_holes_85, + combined.isolated_false_pixels, + ratio( + combined.preserved_forward_edges, + combined.exact_forward_edges + ), + ratio( + combined.preserved_reverse_edges, + combined.exact_reverse_edges + ), + mean_error(combined.local_3_error_sum, combined.local_3_count), + mean_error(combined.local_5_error_sum, combined.local_5_count), + mean_error( + combined.neighbor_error_correlation_sum, + combined.spatial_matrices + ), + mean_error(combined.top_feature_overlap_sum, combined.spatial_matrices), + ratio( + combined.component_count_error_sum, + combined.spatial_matrices + ), + combined.fingerprint_collisions, + combined.comparison_time.as_secs_f64() * 1_000.0, + ); + } + } + } +} + +#[test] +fn verified_estimator_is_exact_for_identical_winner_sets() { + let sequence = PackedSequence::from_ascii("self", &pseudo_random_sequence(32_000, 17)); + let config = ScientificConfig::production(K, 256, 10, false).unwrap(); + let axis = build_axis_overview(&sequence, sequence.len(), 8, config).unwrap(); + for signature in &axis.core_encoded { + let estimate = estimate_containment_bit_sliced_with( + signature, + signature, + K, + 14, + 256, + OphJaccardEstimator::VerifiedWinners, + ) + .unwrap(); + assert!((estimate.jaccard - 1.0).abs() < f64::EPSILON); + assert!((estimate.containment - 1.0).abs() < f64::EPSILON); + } +} + +#[test] +#[ignore = "explicit scheduling benchmark; run with --release --ignored --nocapture"] +fn report_fused_projection_tradeoff() { + let sequence = PackedSequence::from_ascii( + "projection-benchmark", + &pseudo_random_sequence(8_000_000, 0x5eed), + ); + let quick_config = ScientificConfig::production(K, 256, 10, false).unwrap(); + let detailed_config = ScientificConfig::production(K, 2_048, 10, false).unwrap(); + + let quick_started = Instant::now(); + let quick = build_axis_overview(&sequence, sequence.len(), 1_000, quick_config).unwrap(); + let quick_elapsed = quick_started.elapsed(); + + let detailed_started = Instant::now(); + let detailed = build_axis_overview(&sequence, sequence.len(), 1_000, detailed_config).unwrap(); + let detailed_elapsed = detailed_started.elapsed(); + let projection_started = Instant::now(); + let projected = detailed.project_registers(256, 32); + let projection_elapsed = projection_started.elapsed(); + + assert_eq!(quick.core, projected.core); + assert_eq!(quick.expanded, projected.expanded); + println!( + "separate_quick_ms={:.3}\tdetailed_ms={:.3}\tprojection_ms={:.3}\tseparate_total_ms={:.3}\tfused_total_ms={:.3}", + quick_elapsed.as_secs_f64() * 1_000.0, + detailed_elapsed.as_secs_f64() * 1_000.0, + projection_elapsed.as_secs_f64() * 1_000.0, + (quick_elapsed + detailed_elapsed).as_secs_f64() * 1_000.0, + (detailed_elapsed + projection_elapsed).as_secs_f64() * 1_000.0, + ); +} + +#[allow(clippy::too_many_arguments)] +fn evaluate_pair( + left: &PackedSequence, + right: &PackedSequence, + seed: u64, + registers: usize, + hll_precision: u8, + estimator: OphJaccardEstimator, + metrics: &mut Metrics, +) { + let domain = left.len().max(right.len()); + let config = ScientificConfig::new( + K, + HashAlgorithm::NtHash2, + seed, + registers, + DEFAULT_B_BITS, + DEFAULT_VERIFICATION_BITS, + estimator, + hll_precision, + 0, + DEFAULT_ANI_FLOOR, + DEFAULT_MATCH_DETECTION_PROBABILITY, + ) + .unwrap(); + let left_axis = build_axis_overview(left, domain, RESOLUTION, config).unwrap(); + let right_axis = build_axis_overview(right, domain, RESOLUTION, config).unwrap(); + let left_exact = exact_axis(left, domain); + let right_exact = exact_axis(right, domain); + let mut exact_matrix = vec![None; RESOLUTION * RESOLUTION]; + let mut estimated_matrix = vec![None; RESOLUTION * RESOLUTION]; + for (row, right_sets) in right_exact.iter().enumerate() { + for (column, left_sets) in left_exact.iter().enumerate() { + let started = Instant::now(); + let estimated = + approximate_score(&left_axis, &right_axis, column, row, registers, estimator); + metrics.comparison_time += started.elapsed(); + let exact = + exact_moddotplot_score(&left_sets.0, &left_sets.1, &right_sets.0, &right_sets.1, K); + let index = row * RESOLUTION + column; + exact_matrix[index] = exact.map(|value| value.ani); + estimated_matrix[index] = estimated.map(|value| value.ani_c); + if let (Some(exact), Some(estimated)) = (exact, estimated) { + metrics.record(exact.ani, estimated, row, column); + } + metrics.fingerprint_collisions += + fingerprint_collision_count(&left_axis, &right_axis, column, row, registers); + } + } + metrics.record_spatial(&exact_matrix, &estimated_matrix, RESOLUTION); +} + +fn threshold_mask(values: &[Option], threshold: f64) -> Vec { + values + .iter() + .map(|value| value.is_some_and(|value| value >= threshold)) + .collect() +} + +fn neighbors(index: usize, width: usize) -> Vec { + let row = index / width; + let column = index % width; + let mut result = Vec::with_capacity(8); + for neighbor_row in row.saturating_sub(1)..=(row + 1).min(width - 1) { + for neighbor_column in column.saturating_sub(1)..=(column + 1).min(width - 1) { + let neighbor = neighbor_row * width + neighbor_column; + if neighbor != index { + result.push(neighbor); + } + } + } + result +} + +fn local_mean(values: &[Option], index: usize, width: usize, radius: usize) -> Option { + let row = index / width; + let column = index % width; + let mut sum = 0.0; + let mut count = 0_u32; + for local_row in row.saturating_sub(radius)..=(row + radius).min(width - 1) { + for local_column in column.saturating_sub(radius)..=(column + radius).min(width - 1) { + if let Some(value) = values[local_row * width + local_column] { + sum += value; + count += 1; + } + } + } + (count > 0).then(|| sum / f64::from(count)) +} + +fn neighbor_error_correlation( + exact: &[Option], + estimated: &[Option], + width: usize, +) -> f64 { + let errors = exact + .iter() + .zip(estimated) + .map(|(exact, estimated)| match (exact, estimated) { + (Some(exact), Some(estimated)) => Some(estimated - exact), + _ => None, + }) + .collect::>(); + let valid = errors.iter().flatten().copied().collect::>(); + if valid.is_empty() { + return 0.0; + } + let mean = valid.iter().sum::() + / f64::from(u32::try_from(valid.len()).expect("validation matrix fits u32")); + let mut product = 0.0; + let mut left_square = 0.0; + let mut right_square = 0.0; + for row in 0..width { + for column in 0..width { + let here = row * width + column; + for neighbor in [ + (column + 1 < width).then_some(here + 1), + (row + 1 < width).then_some(here + width), + ] + .into_iter() + .flatten() + { + if let (Some(left), Some(right)) = (errors[here], errors[neighbor]) { + let left = left - mean; + let right = right - mean; + product += left * right; + left_square += left * left; + right_square += right * right; + } + } + } + } + let scale = (left_square * right_square).sqrt(); + if scale > 0.0 { product / scale } else { 0.0 } +} + +fn top_feature_overlap(exact: &[Option], estimated: &[Option]) -> f64 { + let mut exact_rank = exact + .iter() + .enumerate() + .filter_map(|(index, value)| value.map(|value| (index, value))) + .collect::>(); + let mut estimated_rank = estimated + .iter() + .enumerate() + .filter_map(|(index, value)| value.map(|value| (index, value))) + .collect::>(); + exact_rank.sort_by(|left, right| right.1.total_cmp(&left.1)); + estimated_rank.sort_by(|left, right| right.1.total_cmp(&left.1)); + let count = exact_rank + .len() + .min(estimated_rank.len()) + .div_ceil(10) + .max(1); + let mut selected = vec![false; exact.len()]; + for &(index, _) in exact_rank.iter().take(count) { + selected[index] = true; + } + let overlap = estimated_rank + .iter() + .take(count) + .filter(|&&(index, _)| selected[index]) + .count(); + ratio(overlap, count) +} + +fn component_count(mask: &[bool], width: usize) -> usize { + let mut visited = vec![false; mask.len()]; + let mut components = 0; + for start in 0..mask.len() { + if !mask[start] || visited[start] { + continue; + } + components += 1; + visited[start] = true; + let mut pending = std::collections::VecDeque::from([start]); + while let Some(index) = pending.pop_front() { + for neighbor in neighbors(index, width) { + if mask[neighbor] && !visited[neighbor] { + visited[neighbor] = true; + pending.push_back(neighbor); + } + } + } + } + components +} + +fn ratio(numerator: usize, denominator: usize) -> f64 { + if denominator == 0 { + return 1.0; + } + let numerator = u32::try_from(numerator).expect("validation count fits u32"); + let denominator = u32::try_from(denominator).expect("validation count fits u32"); + f64::from(numerator) / f64::from(denominator) +} + +fn mean_error(sum: f64, count: usize) -> f64 { + let count = u32::try_from(count.max(1)).expect("validation count fits u32"); + sum / f64::from(count) +} + +fn approximate_score( + left: &AxisOverview, + right: &AxisOverview, + column: usize, + row: usize, + registers: usize, + estimator: OphJaccardEstimator, +) -> Option { + let left_score = estimate_containment_bit_sliced_with( + &left.core_encoded[column], + &right.expanded_encoded[row], + K, + 14, + registers, + estimator, + ); + let right_score = estimate_containment_bit_sliced_with( + &right.core_encoded[row], + &left.expanded_encoded[column], + K, + 14, + registers, + estimator, + ); + match (left_score, right_score) { + (Some(left), Some(right)) => { + let left_occupancy = f64::from(left.registers.considered) / registers_as_f64(registers); + let right_occupancy = + f64::from(right.registers.considered) / registers_as_f64(registers); + Some(ApproximateScore { + ani_c: left.ani.max(right.ani), + mean_union_occupancy: f64::midpoint(left_occupancy, right_occupancy), + minimum_union_occupancy: left_occupancy.min(right_occupancy), + }) + } + (Some(value), None) | (None, Some(value)) => { + let occupancy = f64::from(value.registers.considered) / registers_as_f64(registers); + Some(ApproximateScore { + ani_c: value.ani, + mean_union_occupancy: occupancy, + minimum_union_occupancy: occupancy, + }) + } + (None, None) => None, + } +} + +fn assert_selected_configuration_gate( + registers: usize, + hll_precision: u8, + estimator: OphJaccardEstimator, + metrics: &Metrics, + summary: Summary, +) -> &'static str { + if hll_precision != 10 || estimator != OphJaccardEstimator::VerifiedWinners { + return "not_applicable"; + } + let (mean_limit, p95_limit, crossing_percent) = match registers { + // The multi-layout aggregate includes three deliberately perturbed validation + // hashes. The exact browser seed has the stricter 0.050 gate in + // production_defaults.rs. + 256 => (0.080, 0.300, 10), + 1_024 => (0.010, 0.040, 5), + _ => return "not_applicable", + }; + assert!(summary.mean <= mean_limit); + assert!(summary.p95 <= p95_limit); + let visibility_crossings = + metrics.confusion[0].false_negative + metrics.confusion[0].false_positive; + let crossing_limit = (metrics.compared * crossing_percent).div_ceil(100); + assert!(visibility_crossings <= crossing_limit); + "pass" +} + +fn registers_as_f64(value: usize) -> f64 { + f64::from(u32::try_from(value).expect("validation value fits u32")) +} + +fn fingerprint_collision_count( + left: &AxisOverview, + right: &AxisOverview, + column: usize, + row: usize, + registers: usize, +) -> u64 { + let encoded_left = + left.core_encoded[column].compare(&right.expanded_encoded[row], 14, registers); + let full_left = left.core[column].compare_bbit(&right.expanded[row], 14, registers); + let encoded_right = + right.core_encoded[row].compare(&left.expanded_encoded[column], 14, registers); + let full_right = right.core[row].compare_bbit(&left.expanded[column], 14, registers); + u64::from( + encoded_left + .verified_matches + .saturating_sub(full_left.verified_matches), + ) + u64::from( + encoded_right + .verified_matches + .saturating_sub(full_right.verified_matches), + ) +} + +fn exact_axis(sequence: &PackedSequence, domain: u64) -> Vec<(ExactKmerSet, ExactKmerSet)> { + (0..RESOLUTION) + .map(|bin| { + let start = boundary(bin * 2, RESOLUTION * 2, domain).min(sequence.len()); + let end = boundary(bin * 2 + 2, RESOLUTION * 2, domain).min(sequence.len()); + let expanded_start = + boundary((bin * 2).saturating_sub(1), RESOLUTION * 2, domain).min(sequence.len()); + let expanded_end = boundary((bin * 2 + 3).min(RESOLUTION * 2), RESOLUTION * 2, domain) + .min(sequence.len()); + ( + ExactKmerSet::from_interval(sequence, start, end, K), + ExactKmerSet::from_interval(sequence, expanded_start, expanded_end, K), + ) + }) + .collect() +} + +fn fixtures() -> Vec<(PackedSequence, PackedSequence)> { + let random = pseudo_random_sequence(48_000, 1); + let periodic = (0..48_000) + .map(|index| b"ACGTTGCAAGTC"[index % 12]) + .collect::>(); + vec![ + pair("random-99", &random, &mutate_at_rate(&random, 0.01, 11)), + pair("random-90", &random, &mutate_at_rate(&random, 0.10, 13)), + pair("random-85", &random, &mutate_at_rate(&random, 0.15, 17)), + pair("random-80", &random, &mutate_at_rate(&random, 0.20, 19)), + pair( + "periodic-90", + &periodic, + &mutate_at_rate(&periodic, 0.10, 23), + ), + ( + PackedSequence::from_ascii("long", &random), + PackedSequence::from_ascii("short", &random[..24_000]), + ), + ] +} + +fn v0_5_fixtures() -> Vec<(PackedSequence, PackedSequence)> { + let mut result = Vec::new(); + for realization in [1, 0x22, 0x33, 0x44] { + let random = pseudo_random_sequence(48_000, realization); + for (label, rate, seed) in [("99", 0.01, 11), ("90", 0.10, 13), ("80", 0.20, 19)] { + result.push(pair( + &format!("point-{realization:x}-{label}"), + &random, + &mutate_at_rate(&random, rate, seed ^ realization), + )); + } + } + let random = pseudo_random_sequence(48_000, 1); + let periodic = (0..48_000) + .map(|index| b"ACGTTGCAAGTC"[index % 12]) + .collect::>(); + let motif = pseudo_random_sequence(257, 0x7a6d); + let tandem = (0..48_000) + .map(|index| motif[index % motif.len()]) + .collect::>(); + let mut ambiguous = mutate_at_rate(&random, 0.05, 0xa6b1); + for index in (97..ambiguous.len()).step_by(97) { + ambiguous[index] = b'N'; + } + let mut deletion = random.clone(); + deletion.drain(16_000..20_000); + let mut insertion = random[..24_000].to_vec(); + insertion.extend(pseudo_random_sequence(4_000, 0x1a5e)); + insertion.extend_from_slice(&random[24_000..]); + let mut inversion = random.clone(); + inversion[16_000..32_000].copy_from_slice(&reverse_complement(&random[16_000..32_000])); + let mut offset = pseudo_random_sequence(4_000, 0x000f_f5e7); + offset.extend_from_slice(&random); + result.extend([ + pair("point-85", &random, &mutate_at_rate(&random, 0.15, 17)), + pair( + "periodic-90", + &periodic, + &mutate_at_rate(&periodic, 0.10, 23), + ), + pair("tandem-90", &tandem, &mutate_at_rate(&tandem, 0.10, 29)), + pair("ambiguity", &random, &ambiguous), + pair("large-deletion", &random, &deletion), + pair("large-insertion", &random, &insertion), + pair("segment-inversion", &random, &inversion), + pair("coordinate-offset", &random, &offset), + ( + PackedSequence::from_ascii("cardinality-long", &random), + PackedSequence::from_ascii("cardinality-quarter", &random[..12_000]), + ), + ]); + result +} + +fn pair(name: &str, left: &[u8], right: &[u8]) -> (PackedSequence, PackedSequence) { + ( + PackedSequence::from_ascii(format!("{name}-left"), left), + PackedSequence::from_ascii(format!("{name}-right"), right), + ) +} + +fn boundary(index: usize, count: usize, domain: u64) -> u64 { + u64::try_from(index as u128 * u128::from(domain) / count as u128).unwrap() +} + +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] +fn mutate_at_rate(sequence: &[u8], rate: f64, seed: u64) -> Vec { + let threshold = (rate * f64::from(u32::MAX)) as u32; + sequence + .iter() + .enumerate() + .map(|(index, &base)| { + let random = splitmix64(seed ^ index as u64); + let low = u32::from_le_bytes(random.to_le_bytes()[..4].try_into().unwrap()); + if low <= threshold { mutate(base) } else { base } + }) + .collect() +} + +fn pseudo_random_sequence(length: usize, seed: u64) -> Vec { + (0..length) + .map(|index| b"ACGT"[(splitmix64(seed ^ index as u64) & 3) as usize]) + .collect() +} + +fn splitmix64(mut value: u64) -> u64 { + value = value.wrapping_add(0x9e37_79b9_7f4a_7c15); + value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + value ^ (value >> 31) +} + +fn mutate(base: u8) -> u8 { + match base { + b'A' => b'C', + b'C' => b'G', + b'G' => b'T', + _ => b'A', + } +} + +fn reverse_complement(sequence: &[u8]) -> Vec { + sequence + .iter() + .rev() + .map(|base| match base { + b'A' => b'T', + b'C' => b'G', + b'G' => b'C', + _ => b'A', + }) + .collect() +} diff --git a/crates/core/tests/production_defaults.rs b/crates/core/tests/production_defaults.rs new file mode 100644 index 0000000..624a5d2 --- /dev/null +++ b/crates/core/tests/production_defaults.rs @@ -0,0 +1,844 @@ +//! Assertion-backed acceptance for the exact browser-default production path. + +#[cfg(feature = "validation")] +use moddotplot_core::validation::{SparseCorrectionPolicy, compute_frozen_tile_adaptive}; +use moddotplot_core::{ + DEFAULT_B_BITS, ExactKmerSet, FrozenAxis, MatrixTile, PackedSequence, ScientificConfig, + TileRequest, build_axis_overview, compute_frozen_tile, estimate_containment_bit_sliced, + exact_moddotplot_score, +}; +#[cfg(feature = "validation")] +use std::time::Instant; + +const K: u8 = 21; +const RESOLUTION: usize = 12; +const THRESHOLDS: [f64; 4] = [0.80, 0.85, 0.90, 0.95]; + +#[derive(Clone, Copy, Default)] +struct Confusion { + false_negative: usize, + false_positive: usize, +} + +#[derive(Default)] +struct AcceptanceMetrics { + ani_c_errors: Vec, + confusion: [Confusion; THRESHOLDS.len()], + valid_cells: usize, + missing_disagreements: usize, +} + +impl AcceptanceMetrics { + fn record(&mut self, exact: Option<(f64, f64)>, estimated: Option<(f64, f64)>) { + match (exact, estimated) { + (Some((_, exact_ani_c)), Some((_, estimated_ani_c))) => { + self.valid_cells += 1; + for (index, threshold) in THRESHOLDS.into_iter().enumerate() { + self.confusion[index].false_negative += + usize::from(exact_ani_c >= threshold && estimated_ani_c < threshold); + self.confusion[index].false_positive += + usize::from(exact_ani_c < threshold && estimated_ani_c >= threshold); + } + if exact_ani_c >= THRESHOLDS[0] { + self.ani_c_errors + .push((estimated_ani_c - exact_ani_c).abs()); + } + } + (None, None) => {} + _ => self.missing_disagreements += 1, + } + } + + fn assert_within(&mut self, registers: usize) { + self.ani_c_errors.sort_by(f64::total_cmp); + assert!(!self.ani_c_errors.is_empty()); + assert_eq!(self.missing_disagreements, 0); + + let relevant = self.ani_c_errors.len(); + let mean_ani_c = mean(&self.ani_c_errors); + let p95_ani_c = percentile(&self.ani_c_errors, 95); + let (mean_ani_c_limit, p95_ani_c_limit, visibility_crossing_percent) = if registers == 256 { + (0.050, 0.300, 10) + } else { + (0.010, 0.040, 5) + }; + assert!( + mean_ani_c <= mean_ani_c_limit, + "{registers}-register mean ANI_c error {mean_ani_c:.6} exceeded {mean_ani_c_limit:.3}", + ); + assert!( + p95_ani_c <= p95_ani_c_limit, + "{registers}-register P95 ANI_c error {p95_ani_c:.6} exceeded {p95_ani_c_limit:.3}", + ); + let crossing_limit = (self.valid_cells * visibility_crossing_percent).div_ceil(100); + for (threshold, confusion) in THRESHOLDS.into_iter().zip(self.confusion) { + let crossings = confusion.false_negative + confusion.false_positive; + eprintln!( + "registers={registers} threshold={threshold:.2} false_negative={} false_positive={} crossing_rate={:.6}", + confusion.false_negative, + confusion.false_positive, + count_as_f64(crossings) / count_as_f64(self.valid_cells), + ); + } + let visibility_crossings = + self.confusion[0].false_negative + self.confusion[0].false_positive; + eprintln!( + "registers={registers} relevant={relevant} valid={} mean_abs_ani_c={mean_ani_c:.6} p95_abs_ani_c={p95_ani_c:.6}", + self.valid_cells, + ); + assert!( + visibility_crossings <= crossing_limit, + "{registers}-register ANI_c 0.80 crossing count {visibility_crossings} exceeded {crossing_limit}", + ); + } +} + +#[test] +fn browser_default_production_tiles_track_exact_kmer_containment() { + let mut results = Vec::new(); + for registers in [256, 1_024] { + let mut combined = AcceptanceMetrics::default(); + for (left, right) in fixtures() { + evaluate_fixture(&left, &right, RESOLUTION, registers, &mut combined); + } + results.push((registers, combined)); + } + for (registers, mut metrics) in results { + metrics.assert_within(registers); + } +} + +#[test] +fn browser_default_direction_sign_matches_controlled_orientation() { + let left_bases = pseudo_random_sequence(48_000, 0x51a7); + let forward_bases = mutate_at_rate(&left_bases, 0.01, 0x1234); + let reverse_bases = reverse_complement(&left_bases); + let left = PackedSequence::from_ascii("direction-left", &left_bases); + let forward = PackedSequence::from_ascii("direction-forward", &forward_bases); + let reverse = PackedSequence::from_ascii("direction-reverse", &reverse_bases); + + for registers in [256, 1_024] { + let forward_tile = production_tile(&left, &forward, RESOLUTION, registers).2; + let reverse_tile = production_tile(&left, &reverse, RESOLUTION, registers).2; + for index in 0..RESOLUTION { + let forward_cell = index * RESOLUTION + index; + assert!(forward_tile.identity[forward_cell] >= 9_500); + assert!(forward_tile.direction_support[forward_cell] > 0); + assert!(forward_tile.direction[forward_cell] > 0); + + let reverse_cell = (RESOLUTION - index - 1) * RESOLUTION + index; + assert!(reverse_tile.identity[reverse_cell] >= 9_500); + assert!(reverse_tile.direction_support[reverse_cell] > 0); + assert!(reverse_tile.direction[reverse_cell] < 0); + } + } +} + +#[test] +fn schema_1_numeric_fixture_matches_v0_2_1_bytes() { + let left_bases = pseudo_random_sequence(48_000, 0x0210_0300); + let right_bases = mutate_at_rate(&left_bases, 0.07, 0x0210_0301); + let left = PackedSequence::from_ascii("schema-1-left", &left_bases); + let right = PackedSequence::from_ascii("schema-1-right", &right_bases); + for (registers, expected) in [ + (256, 6_287_528_316_830_669_084_u64), + (1_024, 15_310_234_810_913_278_692_u64), + ] { + let tile = production_tile(&left, &right, RESOLUTION, registers).2; + assert_eq!(tile_digest(&tile), expected, "registers={registers}"); + } +} + +#[test] +fn current_selective_composite_is_bounded_against_all_detailed_output() { + const COMPOSITE_RESOLUTION: usize = 24; + const CHARACTERIZATION_TILE_EDGE: usize = 4; + assert_scheduler_contract_is_current(); + let random = pseudo_random_sequence(48_000, 0xa11d); + let periodic = (0..48_000) + .map(|index| b"ACGTTGCAAGTC"[index % 12]) + .collect::>(); + let mut aggregate_refined_tiles = 0_usize; + let mut aggregate_all_detail_tiles = 0_usize; + let mut aggregate_last_visible_change = 0_usize; + for (left_bases, right_bases) in [ + (random.clone(), mutate_at_rate(&random, 0.01, 0xc001)), + (random.clone(), mutate_at_rate(&random, 0.10, 0xc002)), + (periodic.clone(), mutate_at_rate(&periodic, 0.10, 0xc003)), + ] { + let left = PackedSequence::from_ascii("composite-left", &left_bases); + let right = PackedSequence::from_ascii("composite-right", &right_bases); + let (_, _, preview) = production_tile(&left, &right, COMPOSITE_RESOLUTION, 256); + let (left_detailed, right_detailed, detailed) = + production_tile(&left, &right, COMPOSITE_RESOLUTION, 1_024); + let exact = exact_matrix(&left, &right, COMPOSITE_RESOLUTION); + assert_first_coherent_structure(&preview, &exact); + let (composite, refined_tiles, last_visible_change) = + selective_composite(&preview, &detailed, CHARACTERIZATION_TILE_EDGE); + assert_composite(&composite, &detailed, &exact); + let total_tiles = COMPOSITE_RESOLUTION + .div_ceil(CHARACTERIZATION_TILE_EDGE) + .pow(2); + aggregate_refined_tiles += refined_tiles; + aggregate_all_detail_tiles += total_tiles; + aggregate_last_visible_change += last_visible_change; + assert!( + last_visible_change < refined_tiles, + "scheduler must finish visible corrections before its final selected tile" + ); + + // Keep the complete detailed axes live through the comparison so the test + // exercises the same frozen production representation as the browser. + assert_eq!(left_detailed.resolution(), COMPOSITE_RESOLUTION); + assert_eq!(right_detailed.resolution(), COMPOSITE_RESOLUTION); + } + assert!( + aggregate_refined_tiles * 100 <= aggregate_all_detail_tiles * 80, + "scheduler refined {aggregate_refined_tiles}/{aggregate_all_detail_tiles} tiles" + ); + eprintln!( + "scheduler_refined_tiles={aggregate_refined_tiles}\tall_detail_tiles={aggregate_all_detail_tiles}\twork_fraction={:.6}\tlast_visible_change_sum={aggregate_last_visible_change}", + count_as_f64(aggregate_refined_tiles) / count_as_f64(aggregate_all_detail_tiles), + ); +} + +#[test] +#[cfg(feature = "validation")] +#[ignore = "diagnostic for an explicit sparse-policy decision"] +fn report_sparse_correction_effect_on_repeat_ani_c() { + let periodic = (0..48_000) + .map(|index| b"ACGTTGCAAGTC"[index % 12]) + .collect::>(); + let mutated = mutate_at_rate(&periodic, 0.10, 23); + let left = PackedSequence::from_ascii("periodic-left", &periodic); + let right = PackedSequence::from_ascii("periodic-right", &mutated); + let exact = exact_matrix(&left, &right, RESOLUTION); + for registers in [256, 1_024] { + let config = ScientificConfig::production_default(K, registers, true).unwrap(); + let domain = left.len().max(right.len()); + let left_axis = build_axis_overview(&left, domain, RESOLUTION, config) + .unwrap() + .freeze(); + let right_axis = build_axis_overview(&right, domain, RESOLUTION, config) + .unwrap() + .freeze(); + let tile = compute_frozen_tile_adaptive( + &left_axis, + &right_axis, + &left, + &right, + config.hash_algorithm(), + config.hash_seed(), + TileRequest { + x_start: 0, + y_start: 0, + width: RESOLUTION, + height: RESOLUTION, + }, + SparseCorrectionPolicy { + ani_floor: config.ani_floor(), + minimum_detection_probability: config.minimum_detection_probability(), + }, + ) + .unwrap(); + let mut ani_c_errors = Vec::new(); + for (index, exact_score) in exact.iter().enumerate() { + let Some((_, exact_ani_c)) = exact_score else { + continue; + }; + if *exact_ani_c < 0.80 || tile.identity[index] == u16::MAX { + continue; + } + let estimated_ani_c = f64::from(tile.identity[index]) / 10_000.0; + ani_c_errors.push((estimated_ani_c - exact_ani_c).abs()); + } + ani_c_errors.sort_by(f64::total_cmp); + eprintln!( + "sparse=true registers={registers} mean_abs_ani_c={:.6} p95_abs_ani_c={:.6}", + mean(&ani_c_errors), + percentile(&ani_c_errors, 95), + ); + } +} + +#[test] +#[cfg(feature = "validation")] +#[ignore = "explicit sparse-policy runtime benchmark"] +fn report_sparse_correction_runtime_cost() { + const LENGTH: usize = 2_000_000; + const BENCHMARK_RESOLUTION: usize = 128; + let random = pseudo_random_sequence(LENGTH, 0x5eed); + let periodic = (0..LENGTH) + .map(|index| b"ACGTTGCAAGTC"[index % 12]) + .collect::>(); + for (label, left_bases, mutation_seed) in + [("dense", random, 0xd35e), ("periodic", periodic, 0x7eae)] + { + let right_bases = mutate_at_rate(&left_bases, 0.10, mutation_seed); + let left = PackedSequence::from_ascii(format!("{label}-left"), &left_bases); + let right = PackedSequence::from_ascii(format!("{label}-right"), &right_bases); + for registers in [256, 1_024] { + for sparse in [false, true] { + let mut build_times = Vec::new(); + let mut matrix_times = Vec::new(); + let mut retained_bytes = 0; + for _ in 0..5 { + let config = + ScientificConfig::production_default(K, registers, sparse).unwrap(); + let started = Instant::now(); + let left_axis = build_axis_overview( + &left, + left.len().max(right.len()), + BENCHMARK_RESOLUTION, + config, + ) + .unwrap() + .freeze(); + let right_axis = build_axis_overview( + &right, + left.len().max(right.len()), + BENCHMARK_RESOLUTION, + config, + ) + .unwrap() + .freeze(); + build_times.push(started.elapsed().as_secs_f64() * 1_000.0); + retained_bytes = left_axis + .estimated_heap_bytes() + .saturating_add(right_axis.estimated_heap_bytes()); + + let request = TileRequest { + x_start: 0, + y_start: 0, + width: BENCHMARK_RESOLUTION, + height: BENCHMARK_RESOLUTION, + }; + let started = Instant::now(); + let tile = if sparse { + compute_frozen_tile_adaptive( + &left_axis, + &right_axis, + &left, + &right, + config.hash_algorithm(), + config.hash_seed(), + request, + SparseCorrectionPolicy { + ani_floor: config.ani_floor(), + minimum_detection_probability: config + .minimum_detection_probability(), + }, + ) + .unwrap() + } else { + compute_frozen_tile(&left_axis, &right_axis, request).unwrap() + }; + matrix_times.push(started.elapsed().as_secs_f64() * 1_000.0); + std::hint::black_box(tile.identity[tile.identity.len() / 2]); + } + build_times.sort_by(f64::total_cmp); + matrix_times.sort_by(f64::total_cmp); + eprintln!( + "case={label} registers={registers} sparse={sparse} build_median_ms={:.3} matrix_median_ms={:.3} retained_mib={:.3}", + build_times[build_times.len() / 2], + matrix_times[matrix_times.len() / 2], + count_as_f64(retained_bytes) / 1024.0 / 1024.0, + ); + } + } + } +} + +fn evaluate_fixture( + left: &PackedSequence, + right: &PackedSequence, + resolution: usize, + registers: usize, + metrics: &mut AcceptanceMetrics, +) { + let starting_ani_c_errors = metrics.ani_c_errors.len(); + let (left_axis, right_axis, tile) = production_tile(left, right, resolution, registers); + let exact = exact_matrix(left, right, resolution); + for row in 0..resolution { + for column in 0..resolution { + let index = row * resolution + column; + let estimated = approximate_max_containment(&left_axis, &right_axis, column, row); + let exact_score = exact[index]; + metrics.record(exact_score, estimated); + match estimated { + Some((_, ani_c)) => { + assert_eq!(tile.identity[index], encode_ani_c(ani_c)); + } + None => assert_eq!(tile.identity[index], u16::MAX), + } + } + } + let ani_c_errors = &metrics.ani_c_errors[starting_ani_c_errors..]; + if !ani_c_errors.is_empty() { + eprintln!( + "fixture={} registers={registers} relevant={} mean_abs_ani_c={:.6}", + left.name(), + ani_c_errors.len(), + mean(ani_c_errors), + ); + } +} + +fn production_tile( + left: &PackedSequence, + right: &PackedSequence, + resolution: usize, + registers: usize, +) -> (FrozenAxis, FrozenAxis, MatrixTile) { + let domain = left.len().max(right.len()); + let config = ScientificConfig::production_default(K, registers, false).unwrap(); + assert_eq!(config.hll_precision(), 10); + assert_eq!(config.sparse_core_cap(), 0); + let left_axis = build_axis_overview(left, domain, resolution, config) + .unwrap() + .freeze(); + let right_axis = build_axis_overview(right, domain, resolution, config) + .unwrap() + .freeze(); + let tile = compute_frozen_tile( + &left_axis, + &right_axis, + TileRequest { + x_start: 0, + y_start: 0, + width: resolution, + height: resolution, + }, + ) + .unwrap(); + (left_axis, right_axis, tile) +} + +fn approximate_max_containment( + left: &FrozenAxis, + right: &FrozenAxis, + column: usize, + row: usize, +) -> Option<(f64, f64)> { + if left.core_encoded()[column].observations() == 0 + || right.core_encoded()[row].observations() == 0 + { + return None; + } + let registers = left.core_encoded()[column].register_count(); + let left_score = estimate_containment_bit_sliced( + &left.core_encoded()[column], + &right.expanded_encoded()[row], + K, + DEFAULT_B_BITS, + registers, + ); + let right_score = estimate_containment_bit_sliced( + &right.core_encoded()[row], + &left.expanded_encoded()[column], + K, + DEFAULT_B_BITS, + registers, + ); + match (left_score, right_score) { + (Some(left), Some(right)) => { + let selected = if left.containment >= right.containment { + left + } else { + right + }; + Some((selected.containment, selected.ani)) + } + (Some(value), None) | (None, Some(value)) => Some((value.containment, value.ani)), + (None, None) => None, + } +} + +fn exact_matrix( + left: &PackedSequence, + right: &PackedSequence, + resolution: usize, +) -> Vec> { + let domain = left.len().max(right.len()); + let left_exact = exact_axis(left, domain, resolution); + let right_exact = exact_axis(right, domain, resolution); + right_exact + .iter() + .flat_map(|right_sets| { + left_exact.iter().map(move |left_sets| { + if left_sets.0.is_empty() || right_sets.0.is_empty() { + return None; + } + exact_moddotplot_score(&left_sets.0, &left_sets.1, &right_sets.0, &right_sets.1, K) + .map(|score| (score.containment, score.ani)) + }) + }) + .collect() +} + +fn exact_axis( + sequence: &PackedSequence, + domain: u64, + resolution: usize, +) -> Vec<(ExactKmerSet, ExactKmerSet)> { + (0..resolution) + .map(|bin| { + let start = boundary(bin * 2, resolution * 2, domain).min(sequence.len()); + let end = boundary(bin * 2 + 2, resolution * 2, domain).min(sequence.len()); + let expanded_start = + boundary((bin * 2).saturating_sub(1), resolution * 2, domain).min(sequence.len()); + let expanded_end = boundary((bin * 2 + 3).min(resolution * 2), resolution * 2, domain) + .min(sequence.len()); + ( + ExactKmerSet::from_interval(sequence, start, end, K), + ExactKmerSet::from_interval(sequence, expanded_start, expanded_end, K), + ) + }) + .collect() +} + +fn selective_composite( + preview: &MatrixTile, + detailed: &MatrixTile, + tile_edge: usize, +) -> (MatrixTile, usize, usize) { + assert_eq!( + (preview.width, preview.height), + (detailed.width, detailed.height) + ); + let mut composite = MatrixTile { + width: preview.width, + height: preview.height, + identity: preview.identity.clone(), + direction: preview.direction.clone(), + direction_support: preview.direction_support.clone(), + }; + let tile_columns = preview.width.div_ceil(tile_edge); + let selected = scheduler_order(preview, tile_edge); + let mut refined_tiles = 0; + let mut last_visible_change = 0; + for selected_index in selected { + let tile_y = selected_index / tile_columns; + let tile_x = selected_index % tile_columns; + refined_tiles += 1; + let y = tile_y * tile_edge; + let x = tile_x * tile_edge; + let width = tile_edge.min(preview.width - x); + let height = tile_edge.min(preview.height - y); + let visible_change = (0..height).any(|row| { + (0..width).any(|column| { + let index = (y + row) * preview.width + x + column; + (preview.identity[index] >= 8_500) != (detailed.identity[index] >= 8_500) + }) + }); + if visible_change { + last_visible_change = refined_tiles; + } + for row in 0..height { + let start = (y + row) * preview.width + x; + let end = start + width; + composite.identity[start..end].copy_from_slice(&detailed.identity[start..end]); + composite.direction[start..end].copy_from_slice(&detailed.direction[start..end]); + composite.direction_support[start..end] + .copy_from_slice(&detailed.direction_support[start..end]); + } + } + (composite, refined_tiles, last_visible_change) +} + +fn scheduler_order(preview: &MatrixTile, tile_edge: usize) -> Vec { + let tile_columns = preview.width.div_ceil(tile_edge); + let tile_rows = preview.height.div_ceil(tile_edge); + let mut scores = vec![0.0_f64; tile_columns * tile_rows]; + for tile_y in 0..tile_rows { + for tile_x in 0..tile_columns { + let y = tile_y * tile_edge; + let x = tile_x * tile_edge; + let width = tile_edge.min(preview.width - x); + let height = tile_edge.min(preview.height - y); + let mut non_missing = 0_usize; + let mut missing = 0_usize; + let mut maximum_identity = 0_u16; + let mut identity_histogram = [0_usize; 101]; + let mut low_support_histogram = [0_usize; 101]; + for row in 0..height { + for column in 0..width { + let index = (y + row) * preview.width + x + column; + let identity = preview.identity[index]; + if identity == u16::MAX { + missing += 1; + continue; + } + non_missing += 1; + maximum_identity = maximum_identity.max(identity); + let bin = usize::from(identity / 100).min(100); + identity_histogram[bin] += 1; + if preview.direction_support[index] < 8 { + low_support_histogram[bin] += 1; + } + } + } + if non_missing > 0 { + let decision_cells = identity_histogram[80..=90].iter().sum::(); + let low_support_cells = low_support_histogram[80..=100].iter().sum::(); + let decision_score = if decision_cells >= 2.max(non_missing.div_ceil(100)) { + 1.0 + count_as_f64(decision_cells) / count_as_f64(non_missing) + } else { + 0.0 + }; + let support_score = + if low_support_cells >= 2.max((non_missing * 25).div_ceil(10_000)) { + 0.5 + count_as_f64(low_support_cells) / count_as_f64(non_missing) + } else { + 0.0 + }; + let missing_score = if maximum_identity >= 8_000 { + count_as_f64(missing) / count_as_f64(non_missing + missing) * 0.25 + } else { + 0.0 + }; + scores[tile_y * tile_columns + tile_x] = + decision_score + support_score + missing_score; + } + } + } + let direct = scores.clone(); + for tile_y in 0..tile_rows { + for tile_x in 0..tile_columns { + let direct_score = direct[tile_y * tile_columns + tile_x]; + if direct_score <= 0.0 { + continue; + } + for neighbor_y in tile_y.saturating_sub(1)..=(tile_y + 1).min(tile_rows - 1) { + for neighbor_x in tile_x.saturating_sub(1)..=(tile_x + 1).min(tile_columns - 1) { + let index = neighbor_y * tile_columns + neighbor_x; + scores[index] = scores[index].max(direct_score * 0.25); + } + } + } + } + for tile_y in 0..tile_rows { + for tile_x in 0..tile_columns { + if (tile_x + 3 * tile_y + preview.width).is_multiple_of(16) { + let index = tile_y * tile_columns + tile_x; + scores[index] = scores[index].max(0.05); + } + } + } + let mut selected = scores + .iter() + .enumerate() + .filter_map(|(index, score)| (*score > 0.0).then_some(index)) + .collect::>(); + selected.sort_by(|left, right| { + scores[*right] + .total_cmp(&scores[*left]) + .then_with(|| (left / tile_columns).cmp(&(right / tile_columns))) + .then_with(|| (left % tile_columns).cmp(&(right % tile_columns))) + }); + selected +} + +fn assert_scheduler_contract_is_current() { + let implementation = include_str!("../../../web/src/refinement.ts"); + for declaration in [ + "export const REFINEMENT_POLICY_VERSION = 2;", + "export const REFINEMENT_DISPLAY_MARGIN = 500;", + "export const REFINEMENT_MIN_SUPPORT = 8;", + "export const REFINEMENT_EXPLORATION_MODULUS = 16;", + ] { + assert!(implementation.contains(declaration)); + } +} + +fn assert_first_coherent_structure(preview: &MatrixTile, exact: &[Option<(f64, f64)>]) { + let mut exact_visible = 0; + let mut preview_visible = 0; + for (index, score) in exact.iter().enumerate() { + if score.is_some_and(|(_, ani_c)| ani_c >= 0.85) { + exact_visible += 1; + preview_visible += usize::from(preview.identity[index] >= 8_000); + } + } + assert!(exact_visible > 0); + assert!(preview_visible * 100 >= exact_visible * 80); +} + +fn assert_composite(composite: &MatrixTile, detailed: &MatrixTile, exact: &[Option<(f64, f64)>]) { + let mut differences = Vec::new(); + let mut valid = 0_usize; + let mut visibility_crossings = 0; + for (index, exact_score) in exact.iter().enumerate() { + let composite_value = composite.identity[index]; + let detailed_value = detailed.identity[index]; + assert_eq!(composite_value == u16::MAX, detailed_value == u16::MAX); + if let Some((_, exact_ani_c)) = exact_score { + valid += 1; + if *exact_ani_c >= THRESHOLDS[0] { + differences.push( + (f64::from(composite_value) - f64::from(detailed_value)).abs() / 10_000.0, + ); + } + let visibility_floor = THRESHOLDS[0] * 10_000.0; + visibility_crossings += usize::from( + (f64::from(detailed_value) >= visibility_floor) + != (f64::from(composite_value) >= visibility_floor), + ); + } + } + differences.sort_by(f64::total_cmp); + assert!(mean(&differences) <= 0.030); + assert!(percentile(&differences, 95) <= 0.100); + let crossing_limit = (valid * 2).div_ceil(100); + assert!(visibility_crossings <= crossing_limit); +} + +fn fixtures() -> Vec<(PackedSequence, PackedSequence)> { + let mut fixtures = Vec::new(); + for realization in [1, 0x22, 0x33, 0x44] { + let random = pseudo_random_sequence(48_000, realization); + for (label, rate, mutation_seed) in [("99", 0.01, 11), ("90", 0.10, 13), ("80", 0.20, 19)] { + fixtures.push(pair( + &format!("point-{realization:x}-{label}"), + &random, + &mutate_at_rate(&random, rate, mutation_seed ^ realization), + )); + } + } + let random = pseudo_random_sequence(48_000, 1); + let periodic = (0..48_000) + .map(|index| b"ACGTTGCAAGTC"[index % 12]) + .collect::>(); + let tandem_motif = pseudo_random_sequence(257, 0x7a6d); + let tandem = (0..48_000) + .map(|index| tandem_motif[index % tandem_motif.len()]) + .collect::>(); + let mut ambiguous = mutate_at_rate(&random, 0.05, 0xa6b1); + for index in (97..ambiguous.len()).step_by(97) { + ambiguous[index] = b'N'; + } + let mut deletion = random.clone(); + deletion.drain(16_000..20_000); + let mut insertion = random[..24_000].to_vec(); + insertion.extend(pseudo_random_sequence(4_000, 0x1a5e)); + insertion.extend_from_slice(&random[24_000..]); + let mut inversion = random.clone(); + inversion[16_000..32_000].copy_from_slice(&reverse_complement(&random[16_000..32_000])); + let mut offset = pseudo_random_sequence(4_000, 0x000f_f5e7); + offset.extend_from_slice(&random); + fixtures.extend([ + pair("point-85", &random, &mutate_at_rate(&random, 0.15, 17)), + pair( + "periodic-90", + &periodic, + &mutate_at_rate(&periodic, 0.10, 23), + ), + pair("tandem-90", &tandem, &mutate_at_rate(&tandem, 0.10, 29)), + pair("ambiguity", &random, &ambiguous), + pair("large-deletion", &random, &deletion), + pair("large-insertion", &random, &insertion), + pair("segment-inversion", &random, &inversion), + pair("coordinate-offset", &random, &offset), + ( + PackedSequence::from_ascii("cardinality-long", &random), + PackedSequence::from_ascii("cardinality-quarter", &random[..12_000]), + ), + ]); + fixtures +} + +fn pair(name: &str, left: &[u8], right: &[u8]) -> (PackedSequence, PackedSequence) { + ( + PackedSequence::from_ascii(format!("{name}-left"), left), + PackedSequence::from_ascii(format!("{name}-right"), right), + ) +} + +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] +fn encode_ani_c(value: f64) -> u16 { + (value.clamp(0.0, 1.0) * 10_000.0).round() as u16 +} + +fn mean(values: &[f64]) -> f64 { + values.iter().sum::() / count_as_f64(values.len()) +} + +fn percentile(values: &[f64], percentile: usize) -> f64 { + values[(values.len().saturating_sub(1) * percentile) / 100] +} + +fn count_as_f64(count: usize) -> f64 { + f64::from(u32::try_from(count).expect("bounded validation count fits u32")) +} + +fn tile_digest(tile: &MatrixTile) -> u64 { + let mut digest = 0xcbf2_9ce4_8422_2325_u64; + for byte in tile + .identity + .iter() + .flat_map(|value| value.to_le_bytes()) + .chain(tile.direction.iter().flat_map(|value| value.to_le_bytes())) + .chain( + tile.direction_support + .iter() + .flat_map(|value| value.to_le_bytes()), + ) + { + digest ^= u64::from(byte); + digest = digest.wrapping_mul(0x0000_0100_0000_01b3); + } + digest +} + +fn boundary(index: usize, count: usize, domain: u64) -> u64 { + u64::try_from(index as u128 * u128::from(domain) / count as u128).unwrap() +} + +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] +fn mutate_at_rate(sequence: &[u8], rate: f64, seed: u64) -> Vec { + let threshold = (rate * f64::from(u32::MAX)) as u32; + sequence + .iter() + .enumerate() + .map(|(index, &base)| { + let random = splitmix64(seed ^ index as u64); + let low = u32::from_le_bytes(random.to_le_bytes()[..4].try_into().unwrap()); + if low <= threshold { mutate(base) } else { base } + }) + .collect() +} + +fn pseudo_random_sequence(length: usize, seed: u64) -> Vec { + (0..length) + .map(|index| b"ACGT"[(splitmix64(seed ^ index as u64) & 3) as usize]) + .collect() +} + +fn splitmix64(mut value: u64) -> u64 { + value = value.wrapping_add(0x9e37_79b9_7f4a_7c15); + value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + value ^ (value >> 31) +} + +fn mutate(base: u8) -> u8 { + match base { + b'A' => b'C', + b'C' => b'G', + b'G' => b'T', + _ => b'A', + } +} + +fn reverse_complement(sequence: &[u8]) -> Vec { + sequence + .iter() + .rev() + .map(|base| match base { + b'A' => b'T', + b'C' => b'G', + b'G' => b'C', + _ => b'A', + }) + .collect() +} diff --git a/crates/core/tests/statistical_accuracy.rs b/crates/core/tests/statistical_accuracy.rs new file mode 100644 index 0000000..9e6d7b6 --- /dev/null +++ b/crates/core/tests/statistical_accuracy.rs @@ -0,0 +1,111 @@ +//! Statistical acceptance tests against exact k-mer-set calculations. + +use moddotplot_core::validation::{SparseCorrectionPolicy, compute_tile_adaptive}; +use moddotplot_core::{ + ExactKmerSet, PackedSequence, ScientificConfig, TileRequest, build_axis_overview, + exact_moddotplot_score, +}; + +#[test] +fn bbit_overview_tracks_exact_high_identity_windows() { + const LENGTH: usize = 160_000; + const RESOLUTION: usize = 20; + const K: u8 = 21; + let left_bases = pseudo_random_sequence(LENGTH); + let mut right_bases = left_bases.clone(); + for index in (101..LENGTH).step_by(97) { + right_bases[index] = mutate(right_bases[index]); + } + + let left = PackedSequence::from_ascii("left", &left_bases); + let right = PackedSequence::from_ascii("right", &right_bases); + let config = ScientificConfig::production_default(K, 2_048, true).unwrap(); + let left_axis = build_axis_overview(&left, left.len(), RESOLUTION, config).unwrap(); + let right_axis = build_axis_overview(&right, right.len(), RESOLUTION, config).unwrap(); + let tile = compute_tile_adaptive( + &left_axis, + &right_axis, + &left, + &right, + config.hash_algorithm(), + config.hash_seed(), + TileRequest { + x_start: 0, + y_start: 0, + width: RESOLUTION, + height: RESOLUTION, + }, + SparseCorrectionPolicy { + ani_floor: config.ani_floor(), + minimum_detection_probability: config.minimum_detection_probability(), + }, + ) + .unwrap(); + + let left_exact = exact_axis(&left, RESOLUTION, K); + let right_exact = exact_axis(&right, RESOLUTION, K); + let mut errors = Vec::new(); + for index in 0..RESOLUTION { + let exact = exact_moddotplot_score( + &left_exact[index].0, + &left_exact[index].1, + &right_exact[index].0, + &right_exact[index].1, + K, + ) + .unwrap(); + let estimated = f64::from(tile.identity[index * RESOLUTION + index]) / 10_000.0; + errors.push((estimated - exact.ani).abs()); + } + errors.sort_by(f64::total_cmp); + let error_count = u32::try_from(errors.len()).unwrap(); + let mean = errors.iter().sum::() / f64::from(error_count); + let p95 = errors[(errors.len() * 95 / 100).min(errors.len() - 1)]; + assert!(mean < 0.015, "mean ANI error was {mean:.5}"); + assert!(p95 < 0.035, "95th percentile ANI error was {p95:.5}"); +} + +fn exact_axis( + sequence: &PackedSequence, + resolution: usize, + k: u8, +) -> Vec<(ExactKmerSet, ExactKmerSet)> { + let domain = sequence.len(); + (0..resolution) + .map(|bin| { + let start = boundary(bin * 2, resolution * 2, domain); + let end = boundary(bin * 2 + 2, resolution * 2, domain); + let expanded_start = boundary((bin * 2).saturating_sub(1), resolution * 2, domain); + let expanded_end = boundary((bin * 2 + 3).min(resolution * 2), resolution * 2, domain); + ( + ExactKmerSet::from_interval(sequence, start, end, k), + ExactKmerSet::from_interval(sequence, expanded_start, expanded_end, k), + ) + }) + .collect() +} + +fn boundary(index: usize, count: usize, domain: u64) -> u64 { + u64::try_from(index as u128 * u128::from(domain) / count as u128).unwrap() +} + +fn mutate(base: u8) -> u8 { + match base { + b'A' => b'C', + b'C' => b'G', + b'G' => b'T', + _ => b'A', + } +} + +fn pseudo_random_sequence(length: usize) -> Vec { + let mut state = 0x1319_8a2e_0370_7344_u64; + let mut sequence = Vec::with_capacity(length); + for _ in 0..length { + state = state + .wrapping_mul(2_862_933_555_777_941_757) + .wrapping_add(3_037_000_493); + sequence.push(b"ACGT"[((state >> 61) & 3) as usize]); + } + sequence +} diff --git a/crates/wasm/Cargo.toml b/crates/wasm/Cargo.toml new file mode 100644 index 0000000..787c881 --- /dev/null +++ b/crates/wasm/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "moddotplot-wasm" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "WebAssembly boundary for moddotplot-interactive" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +console_error_panic_hook = "0.1" +moddotplot-core = { path = "../core" } +serde = { version = "1", features = ["derive"] } +serde-wasm-bindgen = "0.6" +wasm-bindgen = "0.2" + +[lints] +workspace = true diff --git a/crates/wasm/LICENSE b/crates/wasm/LICENSE new file mode 100644 index 0000000..0690d5e --- /dev/null +++ b/crates/wasm/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright 2026 moddotplot-interactive contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/crates/wasm/src/cache.rs b/crates/wasm/src/cache.rs new file mode 100644 index 0000000..3eee836 --- /dev/null +++ b/crates/wasm/src/cache.rs @@ -0,0 +1,29 @@ +//! Scientific axis-cache identity and retained entries. + +use super::{CoreScientificConfig, FrozenAxis, Rc, ScientificConfigIdentity}; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub(super) struct AxisCacheKey { + pub(super) sequence_index: usize, + pub(super) resolution: usize, + pub(super) start: usize, + pub(super) count: usize, + pub(super) scientific_identity: ScientificConfigIdentity, +} + +pub(super) struct AxisCacheEntry { + pub(super) axis: Rc, + pub(super) bytes: usize, + pub(super) last_used: u64, +} + +pub(super) fn validate_axis_cache_identity( + key: AxisCacheKey, + config: CoreScientificConfig, +) -> Result<(), &'static str> { + if key.scientific_identity == config.identity() { + Ok(()) + } else { + Err("zoom-axis cache key does not match its scientific configuration") + } +} diff --git a/crates/wasm/src/composition.rs b/crates/wasm/src/composition.rs new file mode 100644 index 0000000..26f675c --- /dev/null +++ b/crates/wasm/src/composition.rs @@ -0,0 +1,140 @@ +//! Owned GC and `CpG` preparation state. + +use super::{ + CompositionIndex, CompositionIndexBuilder, DEFAULT_COMPOSITION_BLOCK_SIZE, HashMap, JsError, + PackedSequence, +}; + +#[derive(Default)] +pub(super) struct CompositionState { + indices: HashMap, + builders: HashMap, +} + +impl CompositionState { + pub(super) fn estimated_heap_bytes(&self) -> usize { + self.indices + .capacity() + .saturating_mul(std::mem::size_of::<(usize, CompositionIndex)>()) + .saturating_add( + self.indices + .values() + .map(CompositionIndex::estimated_heap_bytes) + .sum::(), + ) + .saturating_add( + self.builders + .capacity() + .saturating_mul(std::mem::size_of::<(usize, CompositionIndexBuilder)>()), + ) + .saturating_add( + self.builders + .values() + .map(CompositionIndexBuilder::estimated_heap_bytes) + .sum::(), + ) + } + + pub(super) fn begin( + &mut self, + sequences: &[PackedSequence], + sequence_index: usize, + ) -> Result { + if self.indices.contains_key(&sequence_index) { + return Ok(true); + } + let sequence = sequences + .get(sequence_index) + .ok_or_else(|| JsError::new("GC sequence index is out of range"))?; + self.builders.entry(sequence_index).or_insert_with(|| { + CompositionIndexBuilder::new(sequence, DEFAULT_COMPOSITION_BLOCK_SIZE) + }); + Ok(false) + } + + pub(super) fn advance( + &mut self, + sequences: &[PackedSequence], + sequence_index: usize, + maximum_blocks: usize, + ) -> Result { + if maximum_blocks == 0 { + return Err(JsError::new("GC preparation chunk size must be positive")); + } + let sequence = sequences + .get(sequence_index) + .ok_or_else(|| JsError::new("GC sequence index is out of range"))?; + let builder = self + .builders + .get_mut(&sequence_index) + .ok_or_else(|| JsError::new("begin_composition_index must be called first"))?; + let progress = builder.advance(sequence, maximum_blocks); + if builder.is_complete() { + let builder = self + .builders + .remove(&sequence_index) + .ok_or_else(|| JsError::new("completed GC builder was not retained"))?; + let complete = builder + .finish() + .ok_or_else(|| JsError::new("GC index completion was inconsistent"))?; + self.indices.insert(sequence_index, complete); + } + Ok(progress) + } + + pub(super) fn gc_bins( + &self, + sequences: &[PackedSequence], + sequence_index: usize, + start: u64, + end: u64, + bins: usize, + ) -> Result, JsError> { + let (sequence, index) = self.query(sequences, sequence_index, start, end, bins, "GC")?; + Ok(index.sample_gc(sequence, start, end, bins)) + } + + pub(super) fn cpg_bins( + &self, + sequences: &[PackedSequence], + sequence_index: usize, + start: u64, + end: u64, + bins: usize, + ) -> Result, JsError> { + let (sequence, index) = self.query(sequences, sequence_index, start, end, bins, "CpG")?; + Ok(index.sample_cpg_observed_expected(sequence, start, end, bins)) + } + + fn query<'a>( + &'a self, + sequences: &'a [PackedSequence], + sequence_index: usize, + start: u64, + end: u64, + bins: usize, + label: &str, + ) -> Result<(&'a PackedSequence, &'a CompositionIndex), JsError> { + if bins == 0 { + return Err(JsError::new(&format!("{label} bin count must be positive"))); + } + let sequence = sequences + .get(sequence_index) + .ok_or_else(|| JsError::new(&format!("{label} sequence index is out of range")))?; + if start > end || end > sequence.len() { + return Err(JsError::new(&format!( + "{label} sequence range is out of bounds" + ))); + } + let index = self + .indices + .get(&sequence_index) + .ok_or_else(|| JsError::new("composition index is not ready"))?; + Ok((sequence, index)) + } + + pub(super) fn clear(&mut self) { + self.indices.clear(); + self.builders.clear(); + } +} diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs new file mode 100644 index 0000000..258d931 --- /dev/null +++ b/crates/wasm/src/lib.rs @@ -0,0 +1,1604 @@ +//! Narrow, worker-oriented WebAssembly API for moddotplot-interactive. + +mod cache; +mod composition; +mod preparation; +mod resource; +mod session; + +use cache::{AxisCacheEntry, AxisCacheKey, validate_axis_cache_identity}; +use composition::CompositionState; +use preparation::{ + ComparisonPreparation, PendingPreparationTile, PreparedComparison, join_axis_parts, + progressive_tiles_for_part, +}; +use resource::{ + MAX_CACHED_AXIS_BYTES, MAX_PENDING_PREPARATION_TILE_BYTES, MAX_TILE_EDGE, + MAX_WASM_SESSION_BYTES, estimated_frozen_axis_bytes, estimated_sequence_bytes, + matrix_tile_heap_bytes, validate_prepared_axis_admission, +}; +use session::ComparisonContext; + +#[cfg(test)] +use moddotplot_core::hash::HashAlgorithm; +use moddotplot_core::{ + CompositionIndex, CompositionIndexBuilder, DEFAULT_COMPOSITION_BLOCK_SIZE, FastaError, + FastaParser, FrozenAxis, KmerTileGeometry, KmerTileRequest, MatrixTile, PackedSequence, + ScientificConfig as CoreScientificConfig, ScientificConfigIdentity, TileBuildError, + TileRequest, build_axis_range, compute_frozen_tile, compute_kmer_tile, +}; +use serde::Serialize; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::rc::Rc; +use wasm_bindgen::prelude::*; + +/// Core-owned immutable configuration used by browser preparation and tile requests. +#[wasm_bindgen(js_name = ScientificConfig)] +pub struct BrowserScientificConfig { + inner: CoreScientificConfig, +} + +#[wasm_bindgen(js_class = ScientificConfig)] +impl BrowserScientificConfig { + /// Constructs one production quality tier from user-controlled values. + /// + /// # Errors + /// + /// Returns a JavaScript error if the k-mer length or register count is invalid. + #[wasm_bindgen(constructor)] + pub fn new(k: u8, register_count: usize) -> Result { + let inner = CoreScientificConfig::production_default(k, register_count, false) + .map_err(|error| JsError::new(&error.to_string()))?; + Ok(Self { inner }) + } + + /// Stable hexadecimal digest covering every scientific field. + #[wasm_bindgen(getter)] + pub fn digest(&self) -> String { + format!("{:016x}", self.inner.digest()) + } + + /// Canonical full scientific configuration identity encoded as lowercase hex. + /// + /// The shorter digest is useful provenance, while this full field encoding is + /// the compatibility key used to validate persisted browser artifacts. + #[wasm_bindgen(getter)] + pub fn identity(&self) -> String { + let bytes = self.inner.identity(); + let mut encoded = String::with_capacity(bytes.as_bytes().len() * 2); + for byte in bytes.as_bytes() { + use std::fmt::Write as _; + write!(&mut encoded, "{byte:02x}").expect("writing to a String cannot fail"); + } + encoded + } + + /// Scientific-contract schema version. + #[wasm_bindgen(getter)] + pub fn version(&self) -> u16 { + self.inner.version() + } + + /// Canonical k-mer length. + #[wasm_bindgen(getter)] + pub fn k(&self) -> u8 { + self.inner.k() + } + + /// OPH register count. + #[wasm_bindgen(getter)] + pub fn register_count(&self) -> usize { + self.inner.register_count() + } + + /// HLL precision selected by the core release configuration. + #[wasm_bindgen(getter)] + pub fn hll_precision(&self) -> u8 { + self.inner.hll_precision() + } + + /// Low-bit candidate width. + #[wasm_bindgen(getter)] + pub fn b_bits(&self) -> u8 { + self.inner.b_bits() + } + + /// Winner verification width. + #[wasm_bindgen(getter)] + pub fn verification_bits(&self) -> u8 { + self.inner.verification_bits() + } + + /// Stable production hash identifier. + #[wasm_bindgen(getter)] + pub fn hash_algorithm(&self) -> String { + self.inner.hash_algorithm().as_str().to_owned() + } + + /// Public deterministic hash seed, encoded losslessly for JavaScript. + #[wasm_bindgen(getter)] + pub fn hash_seed(&self) -> String { + self.inner.hash_seed().to_string() + } + + /// OPH resemblance estimator identifier. + #[wasm_bindgen(getter)] + pub fn estimator(&self) -> String { + self.inner.oph_estimator().as_str().to_owned() + } + + /// Fixed-point scale of the identity channel. + #[wasm_bindgen(getter)] + pub fn identity_scale(&self) -> u16 { + self.inner.identity_scale() + } + + /// Missing-value sentinel of the identity channel. + #[wasm_bindgen(getter)] + pub fn missing_identity(&self) -> u16 { + self.inner.missing_identity() + } +} + +#[derive(Clone, Debug, Serialize)] +struct SequenceMetadata { + index: usize, + name: String, + description: String, + length: u64, +} + +/// Stateful compute session owned by one dedicated browser worker. +#[wasm_bindgen] +pub struct ComputeSession { + parser: Option, + fasta_sequence_checkpoint: Option, + sequences: Vec, + sequence_heap_bytes: usize, + comparison: Option, + prepared_levels: BTreeMap, + preparation: Option, + pending_preparation_tiles: VecDeque, + pending_preparation_tile_bytes: usize, + axis_cache: HashMap, + cache_recency: BTreeMap, + cache_clock: u64, + axis_cache_bytes: usize, + composition: CompositionState, +} + +#[wasm_bindgen] +impl ComputeSession { + /// Creates an empty session. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + console_error_panic_hook::set_once(); + Self { + parser: None, + fasta_sequence_checkpoint: None, + sequences: Vec::new(), + sequence_heap_bytes: 0, + comparison: None, + prepared_levels: BTreeMap::new(), + preparation: None, + pending_preparation_tiles: VecDeque::new(), + pending_preparation_tile_bytes: 0, + axis_cache: HashMap::new(), + cache_recency: BTreeMap::new(), + cache_clock: 0, + axis_cache_bytes: 0, + composition: CompositionState::default(), + } + } + + /// Starts one FASTA file. Multiple files can be appended to the same session. + /// + /// # Errors + /// + /// Returns an error if another FASTA stream is already active. + pub fn begin_fasta(&mut self) -> Result<(), JsError> { + if self.parser.is_some() || self.fasta_sequence_checkpoint.is_some() { + return Err(JsError::new("a FASTA stream is already active")); + } + self.fasta_sequence_checkpoint = Some(self.sequences.len()); + self.parser = Some(FastaParser::new()); + Ok(()) + } + + /// Abandons the active FASTA stream without changing committed sequences. + /// + /// Returns `true` when an in-progress parser was discarded and `false` when no + /// stream was active. A new stream may begin immediately after this call. + pub fn abort_fasta(&mut self) -> bool { + let parser_was_active = self.parser.take().is_some(); + let transaction_was_active = self.rollback_fasta_transaction(); + parser_was_active || transaction_was_active + } + + /// Pushes one raw FASTA byte chunk and returns metadata for records completed by it. + /// + /// # Errors + /// + /// Returns an error if no stream is active, the FASTA is malformed, or JavaScript + /// metadata serialization fails. + pub fn push_fasta_chunk(&mut self, chunk: &[u8]) -> Result { + if self.parser.is_none() { + return Err(JsError::new("begin_fasta must be called first")); + } + let completed = self + .parse_fasta_chunk(chunk) + .map_err(|error| JsError::new(&error.to_string()))?; + self.append_records(completed) + } + + /// Finishes the active FASTA file and returns final record metadata. + /// + /// # Errors + /// + /// Returns an error if no stream is active, the final FASTA record is invalid, or + /// JavaScript metadata serialization fails. + pub fn finish_fasta(&mut self) -> Result { + let completed = match self.finish_fasta_records() { + Ok(completed) => completed, + Err(error) => { + self.rollback_fasta_transaction(); + return Err(error); + } + }; + match self.append_records(completed) { + Ok(metadata) => { + self.fasta_sequence_checkpoint = None; + Ok(metadata) + } + Err(error) => { + self.rollback_fasta_transaction(); + Err(error) + } + } + } + + /// Finishes one lazily loaded indexed record and commits it only when it exactly + /// matches the name and length declared by the FAI index. + /// + /// # Errors + /// + /// Returns an error and rolls the whole FASTA transaction back when parsing, + /// serialization, record count, name, or length validation fails. + pub fn finish_indexed_fasta( + &mut self, + expected_name: &str, + expected_length: u64, + ) -> Result { + let checkpoint = self + .fasta_sequence_checkpoint + .ok_or_else(|| JsError::new("begin_fasta must be called first"))?; + let completed = match self.finish_fasta_records() { + Ok(completed) => completed, + Err(error) => { + self.rollback_fasta_transaction(); + return Err(error); + } + }; + let metadata = match self.append_records(completed) { + Ok(metadata) => metadata, + Err(error) => { + self.rollback_fasta_transaction(); + return Err(error); + } + }; + let matches_index = self.sequences.len() == checkpoint.saturating_add(1) + && self.sequences.get(checkpoint).is_some_and(|sequence| { + sequence.name() == expected_name && sequence.len() == expected_length + }); + if !matches_index { + self.rollback_fasta_transaction(); + return Err(JsError::new( + "Indexed FASTA record does not match its FAI name and length.", + )); + } + self.fasta_sequence_checkpoint = None; + Ok(metadata) + } + + fn parse_fasta_chunk(&mut self, chunk: &[u8]) -> Result, FastaError> { + self.parser + .as_mut() + .expect("begin_fasta establishes a parser before internal ingestion") + .push_chunk(chunk) + } + + fn finish_fasta_records(&mut self) -> Result, JsError> { + let parser = self + .parser + .take() + .ok_or_else(|| JsError::new("no FASTA stream is active"))?; + parser + .finish() + .map_err(|error| JsError::new(&error.to_string())) + } + + /// Returns metadata for every loaded record. + /// + /// # Errors + /// + /// Returns an error if JavaScript metadata serialization fails. + pub fn sequences(&self) -> Result { + let metadata = self.metadata_for_range(0, self.sequences.len()); + serde_wasm_bindgen::to_value(&metadata).map_err(|error| JsError::new(&error.to_string())) + } + + /// Estimates application-owned Rust/Wasm bytes currently retained by the session. + /// + /// Allocator metadata, temporary peak allocations, JavaScript buffers, and GPU + /// textures are intentionally excluded. + pub fn estimated_memory_bytes(&self) -> usize { + let mut bytes = + estimated_sequence_bytes(self.sequence_heap_bytes, self.sequences.capacity()); + for prepared in self.prepared_levels.values() { + bytes = bytes + .saturating_add(std::mem::size_of::()) + .saturating_add(prepared.x.estimated_heap_bytes()); + if let Some(y) = &prepared.y { + bytes = bytes.saturating_add(y.estimated_heap_bytes()); + } + } + if let Some(preparation) = &self.preparation { + bytes = bytes.saturating_add(std::mem::size_of::()); + if let Some(origins) = &preparation.progressive_tile_origins { + bytes = bytes.saturating_add( + origins + .capacity() + .saturating_mul(std::mem::size_of::<(usize, usize)>()) + .saturating_mul(2), + ); + } + for part in preparation.x_parts.iter().chain(&preparation.y_parts) { + bytes = bytes + .saturating_add(std::mem::size_of::()) + .saturating_add(part.estimated_heap_bytes()); + } + } + bytes = bytes + .saturating_add( + self.pending_preparation_tiles + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add(self.pending_preparation_tile_bytes); + bytes = bytes + .saturating_add( + self.axis_cache + .capacity() + .saturating_mul(std::mem::size_of::<(AxisCacheKey, AxisCacheEntry)>()), + ) + .saturating_add(self.axis_cache_bytes) + .saturating_add( + self.cache_recency + .len() + .saturating_mul(std::mem::size_of::<(u64, AxisCacheKey)>()), + ); + bytes = bytes.saturating_add(self.composition.estimated_heap_bytes()); + bytes + } + + /// Configured upper bound for application-owned Wasm session memory. + pub fn memory_budget_bytes(&self) -> usize { + MAX_WASM_SESSION_BYTES + } + + /// Starts chunked overview construction from one validated scientific configuration. + /// + /// # Errors + /// + /// Returns an error for missing sequence indexes or invalid sketch parameters. + pub fn begin_prepare_scientific( + &mut self, + x_index: usize, + y_index: usize, + resolution: usize, + config: &BrowserScientificConfig, + progressive_tiles: bool, + ) -> Result<(), JsError> { + if resolution == 0 { + return Err(JsError::new("resolution must be positive")); + } + let config = config.inner; + let x_sequence = self + .sequences + .get(x_index) + .ok_or_else(|| JsError::new("x sequence index is out of range"))?; + let y_sequence = self + .sequences + .get(y_index) + .ok_or_else(|| JsError::new("y sequence index is out of range"))?; + let domain_length = x_sequence.len().max(y_sequence.len()).max(1); + let same_comparison = self + .prepared_levels + .values() + .next() + .is_some_and(|prepared| { + prepared.x_index == x_index + && prepared.y_index == y_index + && prepared.x.resolution() == resolution + && prepared.config.k() == config.k() + && prepared.config.hash_algorithm() == config.hash_algorithm() + && prepared.config.hash_seed() == config.hash_seed() + }); + let axis_count = if x_index == y_index { 1 } else { 2 }; + let retained_bytes = if same_comparison { + self.estimated_memory_bytes() + } else { + estimated_sequence_bytes(self.sequence_heap_bytes, self.sequences.capacity()) + }; + validate_prepared_axis_admission(resolution, config, axis_count, retained_bytes) + .map_err(JsError::new)?; + if !same_comparison { + self.prepared_levels.clear(); + self.clear_axis_cache(); + } + self.comparison = Some(ComparisonContext { + x_index, + y_index, + domain_length, + k: config.k(), + }); + self.preparation = Some(ComparisonPreparation { + x_index, + y_index, + domain_length, + resolution, + config, + axis: 0, + x_next_bin: 0, + y_next_bin: 0, + progressive_tiles, + progressive_tile_origins: None, + x_parts: Vec::new(), + y_parts: Vec::new(), + }); + self.clear_pending_preparation_tiles(); + Ok(()) + } + + /// Restricts progressive preparation tiles to the supplied `(x,y)` origins. + /// + /// Axis construction is unchanged; the filter prevents unselected matrix blocks + /// from being compared merely to make selected detailed blocks visible early. + /// + /// # Errors + /// + /// Returns an error for an odd coordinate array or an origin outside the current + /// preparation resolution. + pub fn set_preparation_tile_filter(&mut self, coordinates: &[u32]) -> Result<(), JsError> { + if !coordinates.len().is_multiple_of(2) { + return Err(JsError::new( + "preparation tile coordinates must be x/y pairs", + )); + } + let preparation = self + .preparation + .as_mut() + .ok_or_else(|| JsError::new("begin_prepare_comparison must be called first"))?; + let mut origins = HashSet::with_capacity(coordinates.len() / 2); + let (pairs, remainder) = coordinates.as_chunks::<2>(); + debug_assert!(remainder.is_empty()); + for &[x_coordinate, y_coordinate] in pairs { + let x = usize::try_from(x_coordinate) + .map_err(|_| JsError::new("preparation tile x origin is unsupported"))?; + let y = usize::try_from(y_coordinate) + .map_err(|_| JsError::new("preparation tile y origin is unsupported"))?; + if x >= preparation.resolution || y >= preparation.resolution { + return Err(JsError::new( + "preparation tile origin lies outside its resolution", + )); + } + origins.insert((x, y)); + } + preparation.progressive_tile_origins = Some(origins); + Ok(()) + } + + /// Selects an axis pair without constructing sketches, for an initially exact view. + /// + /// # Errors + /// + /// Returns an error for an invalid k-mer length or missing sequence index. + pub fn select_exact_comparison( + &mut self, + x_index: usize, + y_index: usize, + k: u8, + ) -> Result<(), JsError> { + let config = CoreScientificConfig::production_default(k, 2, false) + .map_err(|error| JsError::new(&error.to_string()))?; + let x_length = self + .sequences + .get(x_index) + .ok_or_else(|| JsError::new("x sequence index is out of range"))? + .len(); + let y_length = self + .sequences + .get(y_index) + .ok_or_else(|| JsError::new("y sequence index is out of range"))? + .len(); + self.preparation = None; + self.clear_pending_preparation_tiles(); + self.prepared_levels.clear(); + self.clear_axis_cache(); + self.comparison = Some(ComparisonContext { + x_index, + y_index, + domain_length: x_length.max(y_length).max(1), + k: config.k(), + }); + Ok(()) + } + + /// Abandons an in-progress overview refinement while retaining completed levels. + pub fn cancel_preparation(&mut self) { + self.preparation = None; + self.clear_pending_preparation_tiles(); + } + + /// Reports whether the requested overview register level is ready. + pub fn has_prepared_level(&self, register_count: usize) -> bool { + self.prepared_levels.contains_key(®ister_count) + } + + /// Retains only the two register levels selected for the next browser generation. + /// + /// Zoom-axis ranges are view-specific, so they are released when a new plot + /// generation begins even when its overview sketches remain reusable. + pub fn retain_prepared_levels(&mut self, preview_registers: usize, detailed_registers: usize) { + self.prepared_levels.retain(|registers, _| { + *registers == preview_registers || *registers == detailed_registers + }); + self.clear_axis_cache(); + } + + /// Builds the next contiguous group of overview bins and returns progress in `[0,1]`. + /// + /// # Errors + /// + /// Returns an error if preparation has not begun or `maximum_bins` is zero. + pub fn prepare_comparison_chunk(&mut self, maximum_bins: usize) -> Result { + if maximum_bins == 0 { + return Err(JsError::new("preparation chunk size must be positive")); + } + let mut state = self + .preparation + .take() + .ok_or_else(|| JsError::new("begin_prepare_comparison must be called first"))?; + let building_axis = state.axis; + let sequence_index = if building_axis == 0 { + state.x_index + } else { + state.y_index + }; + let sequence = self + .sequences + .get(sequence_index) + .ok_or_else(|| JsError::new("preparation sequence index is out of range"))?; + let next_bin = if building_axis == 0 { + state.x_next_bin + } else { + state.y_next_bin + }; + let count = maximum_bins.min(state.resolution - next_bin); + let part = build_axis_range( + sequence, + state.domain_length, + state.resolution, + next_bin, + count, + state.config, + ) + .map_err(|error| JsError::new(&error.to_string()))? + .freeze(); + if building_axis == 0 { + state.x_parts.push(part); + state.x_next_bin += count; + } else { + state.y_parts.push(part); + state.y_next_bin += count; + } + if state.progressive_tiles && count <= MAX_TILE_EDGE { + for tile in progressive_tiles_for_part(&state, building_axis) { + self.push_pending_preparation_tile(tile); + } + } + let complete = state.x_next_bin == state.resolution + && (state.x_index == state.y_index || state.y_next_bin == state.resolution); + if complete { + let x = join_axis_parts(std::mem::take(&mut state.x_parts)); + let y = if state.x_index == state.y_index { + None + } else { + Some(join_axis_parts(std::mem::take(&mut state.y_parts))) + }; + self.prepared_levels.insert( + state.config.register_count(), + PreparedComparison { + x, + y, + x_index: state.x_index, + y_index: state.y_index, + config: state.config, + }, + ); + return Ok(1.0); + } + if state.x_index != state.y_index { + state.axis = if state.progressive_tiles { + u8::from(building_axis == 0) + } else { + u8::from(state.x_next_bin >= state.resolution) + }; + } + let axis_count = if state.x_index == state.y_index { 1 } else { 2 }; + let completed = state.x_next_bin.saturating_add(state.y_next_bin); + let completed = u32::try_from(completed) + .map_err(|_| JsError::new("overview resolution is too large for progress reporting"))?; + let total = u32::try_from(axis_count * state.resolution) + .map_err(|_| JsError::new("overview resolution is too large for progress reporting"))?; + let progress = f64::from(completed) / f64::from(total); + self.preparation = Some(state); + Ok(progress) + } + + /// Removes the oldest tile made available during chunked axis preparation. + /// + /// The browser drains this queue after every preparation chunk so diagonal and + /// comparison blocks can paint before the complete overview axis is available. + pub fn take_preparation_tile(&mut self) -> Option { + let tile = self.pending_preparation_tiles.pop_front()?; + self.pending_preparation_tile_bytes = self + .pending_preparation_tile_bytes + .saturating_sub(matrix_tile_heap_bytes(&tile.inner)); + Some(WasmPreparationTile { + x: tile.x, + y: tile.y, + inner: tile.inner, + }) + } + + /// Computes one clipped numeric tile from the prepared comparison. + /// + /// # Errors + /// + /// Returns an error if no comparison is prepared or `b_bits` is outside `1..=32`. + pub fn compute_tile_scientific( + &self, + x_start: usize, + y_start: usize, + width: usize, + height: usize, + config: &BrowserScientificConfig, + ) -> Result { + let config = config.inner; + let prepared = self + .prepared_levels + .get(&config.register_count()) + .ok_or_else(|| JsError::new("the requested overview register level is not prepared"))?; + if prepared.config.digest() != config.digest() { + return Err(JsError::new( + "prepared level scientific configuration does not match", + )); + } + validate_tile_request(prepared.x.resolution(), x_start, y_start, width, height)?; + let y = prepared.y.as_ref().unwrap_or(&prepared.x); + let request = TileRequest { + x_start, + y_start, + width, + height, + }; + let inner = compute_scientific_tile( + &prepared.x, + y, + &self.sequences[prepared.x_index], + &self.sequences[prepared.y_index], + config, + request, + ) + .map_err(|error| JsError::new(&error.to_string()))?; + Ok(WasmTile { inner }) + } + + /// Computes one tile at a demand-selected zoom resolution. + /// + /// Axis sketches are constructed only for the requested x and y ranges, then + /// retained in a bounded session cache so adjacent matrix tiles reuse them. + /// + /// # Errors + /// + /// Returns an error when no sequence pair is selected, the selected k-mer length + /// disagrees with the configuration, coordinates are invalid, or b-bit/register + /// parameters are invalid. + pub fn compute_zoom_tile_scientific( + &mut self, + resolution: usize, + x_start: usize, + y_start: usize, + width: usize, + height: usize, + config: &BrowserScientificConfig, + ) -> Result { + let config = config.inner; + validate_tile_request(resolution, x_start, y_start, width, height)?; + + let comparison = self + .comparison + .ok_or_else(|| JsError::new("a comparison must be selected first"))?; + if comparison.k != config.k() { + return Err(JsError::new( + "selected comparison k-mer length does not match the scientific configuration", + )); + } + let x_index = comparison.x_index; + let y_index = comparison.y_index; + let domain_length = comparison.domain_length; + let x_count = width.min(resolution - x_start); + let y_count = height.min(resolution - y_start); + let x_key = AxisCacheKey { + sequence_index: x_index, + resolution, + start: x_start, + count: x_count, + scientific_identity: config.identity(), + }; + let y_key = AxisCacheKey { + sequence_index: y_index, + resolution, + start: y_start, + count: y_count, + scientific_identity: config.identity(), + }; + let x = self.axis_range(x_key, domain_length, config)?; + let y = if x_key == y_key { + Rc::clone(&x) + } else { + self.axis_range(y_key, domain_length, config)? + }; + + let request = TileRequest { + x_start, + y_start, + width, + height, + }; + let inner = compute_scientific_tile( + &x, + &y, + &self.sequences[x_index], + &self.sequences[y_index], + config, + request, + ) + .map_err(|error| JsError::new(&error.to_string()))?; + Ok(WasmTile { inner }) + } + + /// Computes an exact canonical k-mer tile for the prepared sequence pair. + /// + /// # Errors + /// + /// Returns an error when no comparison is prepared or a coordinate cannot be + /// represented in the shared domain. + pub fn compute_kmer_tile( + &self, + x_start: u64, + y_start: u64, + width: usize, + height: usize, + anchors: bool, + ) -> Result { + let comparison = self + .comparison + .ok_or_else(|| JsError::new("select a comparison before requesting exact tiles"))?; + if x_start >= comparison.domain_length || y_start >= comparison.domain_length { + return Err(JsError::new( + "base tile origin lies outside the sequence domain", + )); + } + if width == 0 || height == 0 || width > MAX_TILE_EDGE || height > MAX_TILE_EDGE { + return Err(JsError::new( + "exact tile dimensions must be in 1..=256 before allocation", + )); + } + let x = self + .sequences + .get(comparison.x_index) + .ok_or_else(|| JsError::new("x sequence index is out of range"))?; + let y = self + .sequences + .get(comparison.y_index) + .ok_or_else(|| JsError::new("y sequence index is out of range"))?; + let inner = compute_kmer_tile( + x, + y, + KmerTileRequest { + domain_length: comparison.domain_length, + x_start, + y_start, + width, + height, + k: comparison.k, + geometry: if anchors { + KmerTileGeometry::Anchors + } else { + KmerTileGeometry::Footprints + }, + }, + ) + .map_err(|error| JsError::new(&error.to_string()))?; + Ok(WasmTile { inner }) + } + + /// Copies a bounded sequence range as ASCII for close-zoom rendering. + /// + /// # Errors + /// + /// Returns an error for a missing sequence index or an invalid range. + pub fn sequence_ascii( + &self, + sequence_index: usize, + start: u64, + end: u64, + ) -> Result, JsError> { + let sequence = self + .sequences + .get(sequence_index) + .ok_or_else(|| JsError::new("sequence index is out of range"))?; + if start > end || end > sequence.len() { + return Err(JsError::new("sequence label range is out of bounds")); + } + let mut output = Vec::with_capacity( + usize::try_from(end - start) + .map_err(|_| JsError::new("sequence range is too large"))?, + ); + for position in start..end { + let (code, valid) = sequence + .get(position) + .ok_or_else(|| JsError::new("sequence position is out of range"))?; + output.push(if valid { + b"ACGT"[usize::from(code)] + } else { + b'N' + }); + } + Ok(output) + } + + /// Starts or resumes an on-demand GC index for one loaded sequence. + /// + /// Returns true when the index was already complete. + /// + /// # Errors + /// + /// Returns an error for a missing sequence index. + pub fn begin_composition_index(&mut self, sequence_index: usize) -> Result { + self.composition.begin(&self.sequences, sequence_index) + } + + /// Builds the next GC index chunk and returns completion in `[0, 1]`. + /// + /// # Errors + /// + /// Returns an error if indexing was not started or `maximum_blocks` is zero. + pub fn prepare_composition_index_chunk( + &mut self, + sequence_index: usize, + maximum_blocks: usize, + ) -> Result { + self.composition + .advance(&self.sequences, sequence_index, maximum_blocks) + } + + /// Samples exact GC fractions for equal windows in a visible sequence range. + /// + /// # Errors + /// + /// Returns an error for an unavailable index, invalid range, or zero bins. + pub fn gc_bins( + &self, + sequence_index: usize, + start: u64, + end: u64, + bins: usize, + ) -> Result, JsError> { + self.composition + .gc_bins(&self.sequences, sequence_index, start, end, bins) + } + + /// Samples `CpG` observed/expected ratios for equal windows in a visible range. + /// + /// # Errors + /// + /// Returns an error for an unavailable index, invalid range, or zero bins. + pub fn cpg_observed_expected_bins( + &self, + sequence_index: usize, + start: u64, + end: u64, + bins: usize, + ) -> Result, JsError> { + self.composition + .cpg_bins(&self.sequences, sequence_index, start, end, bins) + } + + /// Releases all sequence and comparison memory. + pub fn clear(&mut self) { + self.parser = None; + self.fasta_sequence_checkpoint = None; + self.comparison = None; + self.prepared_levels.clear(); + self.preparation = None; + self.clear_pending_preparation_tiles(); + self.sequences.clear(); + self.sequence_heap_bytes = 0; + self.clear_axis_cache(); + self.composition.clear(); + } + + fn append_records(&mut self, records: Vec) -> Result { + let previous_sequence_heap_bytes = self.sequence_heap_bytes; + let first = self.extend_sequence_storage(records); + if self.estimated_memory_bytes() > MAX_WASM_SESSION_BYTES { + self.sequences.truncate(first); + self.sequence_heap_bytes = previous_sequence_heap_bytes; + return Err(JsError::new( + "Loaded sequences exceed the 3 GiB Wasm session budget.", + )); + } + let metadata = self.metadata_for_range(first, self.sequences.len()); + serde_wasm_bindgen::to_value(&metadata).map_err(|error| JsError::new(&error.to_string())) + } + + fn extend_sequence_storage(&mut self, records: Vec) -> usize { + let first = self.sequences.len(); + let added_heap_bytes = records.iter().fold(0_usize, |total, sequence| { + total.saturating_add(sequence.estimated_heap_bytes()) + }); + self.sequence_heap_bytes = self.sequence_heap_bytes.saturating_add(added_heap_bytes); + self.sequences.extend(records); + first + } + + fn rollback_fasta_transaction(&mut self) -> bool { + let Some(checkpoint) = self.fasta_sequence_checkpoint.take() else { + return false; + }; + let removed_heap_bytes = self.sequences[checkpoint..] + .iter() + .fold(0_usize, |total, sequence| { + total.saturating_add(sequence.estimated_heap_bytes()) + }); + self.sequences.truncate(checkpoint); + self.sequence_heap_bytes = self.sequence_heap_bytes.saturating_sub(removed_heap_bytes); + true + } + + fn metadata_for_range(&self, start: usize, end: usize) -> Vec { + self.sequences[start..end] + .iter() + .enumerate() + .map(|(offset, sequence)| SequenceMetadata { + index: start + offset, + name: sequence.name().to_owned(), + description: sequence.description().to_owned(), + length: sequence.len(), + }) + .collect() + } + + fn axis_range( + &mut self, + key: AxisCacheKey, + domain_length: u64, + config: CoreScientificConfig, + ) -> Result, JsError> { + validate_axis_cache_identity(key, config).map_err(JsError::new)?; + if let Some(entry) = self.axis_cache.get(&key) { + let axis = Rc::clone(&entry.axis); + self.touch_axis_cache(key); + return Ok(axis); + } + let sequence = self + .sequences + .get(key.sequence_index) + .ok_or_else(|| JsError::new("cached axis sequence index is out of range"))?; + let admitted = estimated_frozen_axis_bytes(key.count, config); + if admitted > MAX_CACHED_AXIS_BYTES { + return Err(JsError::new( + "A requested zoom axis exceeds the cache budget; reduce detailed accuracy.", + )); + } + while self.axis_cache_bytes.saturating_add(admitted) > MAX_CACHED_AXIS_BYTES { + let Some((_, expired)) = self.cache_recency.pop_first() else { + break; + }; + if let Some(entry) = self.axis_cache.remove(&expired) { + self.axis_cache_bytes = self.axis_cache_bytes.saturating_sub(entry.bytes); + } + } + if self.estimated_memory_bytes().saturating_add(admitted) > MAX_WASM_SESSION_BYTES { + return Err(JsError::new( + "The requested zoom level exceeds the Wasm session memory budget.", + )); + } + let axis = Rc::new( + build_axis_range( + sequence, + domain_length, + key.resolution, + key.start, + key.count, + config, + ) + .map_err(|error| JsError::new(&error.to_string()))? + .freeze(), + ); + + let bytes = std::mem::size_of::().saturating_add(axis.estimated_heap_bytes()); + let last_used = self.next_cache_tick(); + self.axis_cache.insert( + key, + AxisCacheEntry { + axis: Rc::clone(&axis), + bytes, + last_used, + }, + ); + self.axis_cache_bytes = self.axis_cache_bytes.saturating_add(bytes); + self.cache_recency.insert(last_used, key); + self.prune_axis_cache(); + Ok(axis) + } + + fn touch_axis_cache(&mut self, key: AxisCacheKey) { + let next = self.next_cache_tick(); + if let Some(entry) = self.axis_cache.get_mut(&key) { + self.cache_recency.remove(&entry.last_used); + entry.last_used = next; + self.cache_recency.insert(next, key); + } + } + + fn next_cache_tick(&mut self) -> u64 { + self.cache_clock = self.cache_clock.wrapping_add(1); + if self.cache_clock == 0 { + let mut entries = self + .axis_cache + .iter() + .map(|(key, entry)| (entry.last_used, *key)) + .collect::>(); + entries.sort_unstable(); + self.cache_recency.clear(); + for (index, (_, key)) in entries.into_iter().enumerate() { + let tick = u64::try_from(index + 1).expect("cache entry count fits u64"); + if let Some(entry) = self.axis_cache.get_mut(&key) { + entry.last_used = tick; + } + self.cache_recency.insert(tick, key); + } + self.cache_clock = u64::try_from(self.axis_cache.len()).expect("cache size fits u64"); + self.cache_clock += 1; + } + self.cache_clock + } + + fn prune_axis_cache(&mut self) { + while self.axis_cache_bytes > MAX_CACHED_AXIS_BYTES && self.axis_cache.len() > 1 { + let Some((_, expired)) = self.cache_recency.pop_first() else { + break; + }; + if let Some(entry) = self.axis_cache.remove(&expired) { + self.axis_cache_bytes = self.axis_cache_bytes.saturating_sub(entry.bytes); + } + } + } + + fn clear_axis_cache(&mut self) { + self.axis_cache.clear(); + self.cache_recency.clear(); + self.cache_clock = 0; + self.axis_cache_bytes = 0; + } + + fn push_pending_preparation_tile(&mut self, tile: PendingPreparationTile) { + let bytes = matrix_tile_heap_bytes(&tile.inner); + if self.pending_preparation_tile_bytes.saturating_add(bytes) + > MAX_PENDING_PREPARATION_TILE_BYTES + { + return; + } + self.pending_preparation_tile_bytes = + self.pending_preparation_tile_bytes.saturating_add(bytes); + self.pending_preparation_tiles.push_back(tile); + } + + fn clear_pending_preparation_tiles(&mut self) { + self.pending_preparation_tiles.clear(); + self.pending_preparation_tile_bytes = 0; + } +} + +fn validate_tile_request( + resolution: usize, + x_start: usize, + y_start: usize, + width: usize, + height: usize, +) -> Result<(), JsError> { + if resolution == 0 || width == 0 || height == 0 { + return Err(JsError::new( + "resolution and tile dimensions must be positive", + )); + } + if width > MAX_TILE_EDGE || height > MAX_TILE_EDGE { + return Err(JsError::new( + "tile dimensions must be at most 256 before allocation", + )); + } + if x_start >= resolution || y_start >= resolution { + return Err(JsError::new("tile origin lies outside its resolution")); + } + Ok(()) +} + +fn compute_scientific_tile( + x: &FrozenAxis, + y: &FrozenAxis, + x_sequence: &PackedSequence, + y_sequence: &PackedSequence, + config: CoreScientificConfig, + request: TileRequest, +) -> Result { + let _ = (x_sequence, y_sequence, config); + compute_frozen_tile(x, y, request) +} + +impl Default for ComputeSession { + fn default() -> Self { + Self::new() + } +} + +/// Owned numeric tile copied to JavaScript typed arrays. +#[wasm_bindgen] +pub struct WasmTile { + inner: MatrixTile, +} + +/// Owned numeric tile produced while overview axes are still being prepared. +#[wasm_bindgen] +pub struct WasmPreparationTile { + x: usize, + y: usize, + inner: MatrixTile, +} + +#[wasm_bindgen] +impl WasmPreparationTile { + /// Global x-axis cell origin. + #[wasm_bindgen(getter)] + pub fn x(&self) -> usize { + self.x + } + + /// Global y-axis cell origin. + #[wasm_bindgen(getter)] + pub fn y(&self) -> usize { + self.y + } + + /// Tile width. + #[wasm_bindgen(getter)] + pub fn width(&self) -> usize { + self.inner.width + } + + /// Tile height. + #[wasm_bindgen(getter)] + pub fn height(&self) -> usize { + self.inner.height + } + + /// ANI values multiplied by 10,000; `65535` is missing data. + pub fn take_identity(&mut self) -> Vec { + std::mem::take(&mut self.inner.identity) + } + + /// Signed direction values in `[-32767, 32767]`. + pub fn take_direction(&mut self) -> Vec { + std::mem::take(&mut self.inner.direction) + } + + /// Informative full-hash direction support. + pub fn take_direction_support(&mut self) -> Vec { + std::mem::take(&mut self.inner.direction_support) + } +} + +#[wasm_bindgen] +impl WasmTile { + /// Tile width. + #[wasm_bindgen(getter)] + pub fn width(&self) -> usize { + self.inner.width + } + + /// Tile height. + #[wasm_bindgen(getter)] + pub fn height(&self) -> usize { + self.inner.height + } + + /// ANI values multiplied by 10,000; `65535` is missing data. + pub fn take_identity(&mut self) -> Vec { + std::mem::take(&mut self.inner.identity) + } + + /// Signed direction values in `[-32767, 32767]`. + pub fn take_direction(&mut self) -> Vec { + std::mem::take(&mut self.inner.direction) + } + + /// Informative full-hash direction support. + pub fn take_direction_support(&mut self) -> Vec { + std::mem::take(&mut self.inner.direction_support) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn aborting_partial_fasta_preserves_committed_records_and_allows_restart() { + let committed = PackedSequence::from_ascii("committed", b"ACGT"); + let committed_identity = committed.identity(); + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![committed]); + + session.begin_fasta().unwrap(); + let provisional = session + .parse_fasta_chunk(b">cancelled\nACGTAC\n>unfinished\nTG") + .unwrap(); + assert_eq!(provisional.len(), 1); + session.extend_sequence_storage(provisional); + assert_eq!(session.sequences.len(), 2); + assert!(session.abort_fasta()); + assert!(!session.abort_fasta()); + assert_eq!(session.sequences.len(), 1); + assert_eq!(session.sequences[0].name(), "committed"); + assert_eq!(session.sequences[0].identity(), committed_identity); + + session.begin_fasta().unwrap(); + assert!( + session + .parse_fasta_chunk(b">replacement\nTGCA\n") + .unwrap() + .is_empty() + ); + let completed = session.finish_fasta_records().unwrap(); + session.extend_sequence_storage(completed); + assert_eq!(session.sequences.len(), 2); + assert_eq!(session.sequences[1].name(), "replacement"); + assert_eq!(session.sequences[1].len(), 4); + } + + #[test] + fn aborting_invalid_fasta_preserves_committed_records_and_allows_restart() { + let committed = PackedSequence::from_ascii("committed", b"ACGT"); + let committed_identity = committed.identity(); + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![committed]); + + session.begin_fasta().unwrap(); + assert!(session.parse_fasta_chunk(b"missing-header\n").is_err()); + assert!(session.abort_fasta()); + assert_eq!(session.sequences.len(), 1); + assert_eq!(session.sequences[0].identity(), committed_identity); + + session.begin_fasta().unwrap(); + assert!( + session + .parse_fasta_chunk(b">valid-after-error\nAAAA\n") + .unwrap() + .is_empty() + ); + let completed = session.finish_fasta_records().unwrap(); + session.extend_sequence_storage(completed); + assert_eq!(session.sequences.len(), 2); + assert_eq!(session.sequences[1].name(), "valid-after-error"); + assert_eq!(session.sequences[1].len(), 4); + } + + #[test] + fn preview_and_detailed_overviews_remain_independent_and_bounded() { + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![PackedSequence::from_ascii( + "sequence", + b"ACGTACGTACGTACGT", + )]); + + let preview = BrowserScientificConfig { + inner: CoreScientificConfig::production(3, 2, 4, false).unwrap(), + }; + let detailed = BrowserScientificConfig { + inner: CoreScientificConfig::production(3, 4, 4, false).unwrap(), + }; + session + .begin_prepare_scientific(0, 0, 4, &preview, true) + .expect("preview preparation starts"); + while session + .prepare_comparison_chunk(4) + .expect("preview preparation advances") + < 1.0 + {} + let mut progressive = session + .take_preparation_tile() + .expect("the completed self block is immediately available"); + assert_eq!((progressive.x(), progressive.y()), (0, 0)); + assert_eq!((progressive.width(), progressive.height()), (4, 4)); + let identity = progressive.take_identity(); + let direction = progressive.take_direction(); + let support = progressive.take_direction_support(); + assert_eq!(identity[0], 10_000); + assert_eq!(identity[5], 10_000); + let mut completed = session + .compute_tile_scientific(0, 0, 4, 4, &preview) + .expect("completed level remains available"); + assert_eq!(completed.take_identity(), identity); + assert_eq!(completed.take_direction(), direction); + assert_eq!(completed.take_direction_support(), support); + assert!(session.take_preparation_tile().is_none()); + session + .begin_prepare_scientific(0, 0, 4, &detailed, true) + .expect("detailed preparation starts"); + while session + .prepare_comparison_chunk(4) + .expect("detailed preparation advances") + < 1.0 + {} + let detailed_progressive = session + .take_preparation_tile() + .expect("a detailed tile is available as soon as its axis block completes"); + assert_eq!((detailed_progressive.x(), detailed_progressive.y()), (0, 0)); + + assert!(session.has_prepared_level(2)); + assert!(session.has_prepared_level(4)); + assert_eq!( + session.prepared_levels[&2].x.core_encoded()[0].register_count(), + 2 + ); + assert_eq!( + session.prepared_levels[&4].x.core_encoded()[0].register_count(), + 4 + ); + + session.retain_prepared_levels(2, 2); + assert!(session.has_prepared_level(2)); + assert!(!session.has_prepared_level(4)); + } + + #[test] + fn demand_zoom_can_start_from_a_selected_pair_without_an_overview_axis() { + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![PackedSequence::from_ascii( + "sequence", + b"ACGTACGTACGTACGT", + )]); + let config = BrowserScientificConfig { + inner: CoreScientificConfig::production(3, 4, 4, false).unwrap(), + }; + + session + .select_exact_comparison(0, 0, 3) + .expect("the sequence pair is selected without preparing overview signatures"); + assert!(session.prepared_levels.is_empty()); + let mut tile = session + .compute_zoom_tile_scientific(8, 0, 0, 8, 8, &config) + .expect("a demand zoom tile can build only its requested axis ranges"); + let identity = tile.take_identity(); + assert_eq!(identity.len(), 64); + assert_eq!(identity[0], 10_000); + assert_eq!(identity[9], 10_000); + } + + #[test] + fn pairwise_preparation_interleaves_axes_and_publishes_compatible_blocks() { + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![ + PackedSequence::from_ascii("x", b"ACGTACGTACGTACGTACGTACGT"), + PackedSequence::from_ascii("y", b"ACGTACGTTCGTACGTACGTACGT"), + ]); + let preview = BrowserScientificConfig { + inner: CoreScientificConfig::production(3, 4, 4, false).unwrap(), + }; + session + .begin_prepare_scientific(0, 1, 6, &preview, true) + .unwrap(); + + assert!((session.prepare_comparison_chunk(3).unwrap() - 0.25).abs() < f64::EPSILON); + assert!(session.take_preparation_tile().is_none()); + assert!((session.prepare_comparison_chunk(3).unwrap() - 0.5).abs() < f64::EPSILON); + let first = session + .take_preparation_tile() + .expect("the first compatible x/y block is available halfway through preparation"); + assert_eq!( + (first.x(), first.y(), first.width(), first.height()), + (0, 0, 3, 3) + ); + + assert!((session.prepare_comparison_chunk(3).unwrap() - 0.75).abs() < f64::EPSILON); + let second = session.take_preparation_tile().unwrap(); + assert_eq!((second.x(), second.y()), (3, 0)); + assert!((session.prepare_comparison_chunk(3).unwrap() - 1.0).abs() < f64::EPSILON); + let remaining = std::iter::from_fn(|| session.take_preparation_tile()) + .map(|tile| (tile.x(), tile.y())) + .collect::>(); + assert_eq!(remaining, [(0, 3), (3, 3)]); + } + + #[test] + fn progressive_preparation_filter_skips_unselected_matrix_blocks() { + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![ + PackedSequence::from_ascii("x", b"ACGTACGTACGTACGTACGTACGT"), + PackedSequence::from_ascii("y", b"ACGTACGTTCGTACGTACGTACGT"), + ]); + let config = BrowserScientificConfig { + inner: CoreScientificConfig::production(3, 4, 4, false).unwrap(), + }; + session + .begin_prepare_scientific(0, 1, 6, &config, true) + .unwrap(); + session.set_preparation_tile_filter(&[3, 0]).unwrap(); + + session.prepare_comparison_chunk(3).unwrap(); + session.prepare_comparison_chunk(3).unwrap(); + assert!(session.take_preparation_tile().is_none()); + session.prepare_comparison_chunk(3).unwrap(); + let selected = session.take_preparation_tile().unwrap(); + assert_eq!((selected.x(), selected.y()), (3, 0)); + session.prepare_comparison_chunk(3).unwrap(); + assert!(session.take_preparation_tile().is_none()); + } + + #[test] + fn gc_index_builds_incrementally_and_is_reused() { + let sequence = b"ACGT".repeat(600); + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![PackedSequence::from_ascii("gc", &sequence)]); + + assert!(!session.begin_composition_index(0).unwrap()); + assert!(session.prepare_composition_index_chunk(0, 1).unwrap() < 1.0); + while session.prepare_composition_index_chunk(0, 1).unwrap() < 1.0 {} + assert!(session.begin_composition_index(0).unwrap()); + let values = session.gc_bins(0, 0, 2_400, 4).unwrap(); + assert!( + values + .iter() + .all(|value| (value - 0.5).abs() < f64::EPSILON) + ); + } + + #[test] + fn over_budget_pairwise_axis_is_rejected_before_preparation() { + let maximum = CoreScientificConfig::production(21, 4_096, 10, false).unwrap(); + let error = validate_prepared_axis_admission(10_000, maximum, 2, 0) + .expect_err("oversized pairwise axes must fail admission"); + + assert!(error.contains("prepared-axis budget")); + } + + #[test] + fn retained_sequence_bytes_are_accounted_incrementally_and_cleared() { + let first = PackedSequence::from_ascii("first", &vec![b'A'; 4_097]); + let first_heap_bytes = first.estimated_heap_bytes(); + let second = PackedSequence::with_description("second", "metadata"); + let second_heap_bytes = second.estimated_heap_bytes(); + let mut session = ComputeSession::new(); + + assert_eq!(session.extend_sequence_storage(vec![first]), 0); + assert_eq!(session.sequence_heap_bytes, first_heap_bytes); + assert_eq!(session.extend_sequence_storage(vec![second]), 1); + assert_eq!( + session.sequence_heap_bytes, + first_heap_bytes.saturating_add(second_heap_bytes) + ); + assert_eq!( + session.estimated_memory_bytes(), + estimated_sequence_bytes(session.sequence_heap_bytes, session.sequences.capacity()) + ); + + session.clear(); + assert_eq!(session.sequence_heap_bytes, 0); + assert_eq!( + session.estimated_memory_bytes(), + estimated_sequence_bytes(0, session.sequences.capacity()) + ); + } + + #[test] + fn predicted_axis_memory_bounds_observed_retained_bytes() { + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![PackedSequence::from_ascii( + "memory", + &vec![b'A'; 100_000], + )]); + let config = BrowserScientificConfig { + inner: CoreScientificConfig::production(21, 1_024, 10, false).unwrap(), + }; + session + .begin_prepare_scientific(0, 0, 128, &config, false) + .unwrap(); + while session.prepare_comparison_chunk(32).unwrap() < 1.0 {} + let axis = &session.prepared_levels[&1_024].x; + let observed = + std::mem::size_of::().saturating_add(axis.estimated_heap_bytes()); + let predicted = estimated_frozen_axis_bytes(128, config.inner); + assert!( + observed <= predicted, + "observed {observed} bytes exceeded predicted {predicted} bytes" + ); + } + + #[test] + fn zoom_cache_uses_exact_scientific_identity_and_rejects_key_drift() { + let mut session = ComputeSession::new(); + session.extend_sequence_storage(vec![PackedSequence::from_ascii( + "cache", + b"ACGTACGTACGTACGTACGTACGTACGTACGT", + )]); + let base = CoreScientificConfig::new( + 3, + HashAlgorithm::NtHash2, + 0, + 4, + 2, + 32, + moddotplot_core::OphJaccardEstimator::VerifiedWinners, + 4, + 0, + 0.80, + 0.99, + ) + .unwrap(); + let changed_seed = CoreScientificConfig::new( + 3, + HashAlgorithm::NtHash2, + 1, + 4, + 2, + 32, + moddotplot_core::OphJaccardEstimator::VerifiedWinners, + 4, + 0, + 0.80, + 0.99, + ) + .unwrap(); + let base_key = AxisCacheKey { + sequence_index: 0, + resolution: 4, + start: 0, + count: 4, + scientific_identity: base.identity(), + }; + session.axis_range(base_key, 32, base).unwrap(); + assert_eq!(session.axis_cache.len(), 1); + + let changed_key = AxisCacheKey { + scientific_identity: changed_seed.identity(), + ..base_key + }; + session.axis_range(changed_key, 32, changed_seed).unwrap(); + assert_eq!(session.axis_cache.len(), 2); + + assert!(validate_axis_cache_identity(base_key, changed_seed).is_err()); + } +} diff --git a/crates/wasm/src/preparation.rs b/crates/wasm/src/preparation.rs new file mode 100644 index 0000000..f0681de --- /dev/null +++ b/crates/wasm/src/preparation.rs @@ -0,0 +1,103 @@ +//! Comparison preparation state and progressive tile composition. + +use super::{ + CoreScientificConfig, FrozenAxis, HashSet, MatrixTile, TileRequest, compute_frozen_tile, +}; + +pub(super) struct PreparedComparison { + pub(super) x: FrozenAxis, + pub(super) y: Option, + pub(super) x_index: usize, + pub(super) y_index: usize, + pub(super) config: CoreScientificConfig, +} + +pub(super) struct ComparisonPreparation { + pub(super) x_index: usize, + pub(super) y_index: usize, + pub(super) domain_length: u64, + pub(super) resolution: usize, + pub(super) config: CoreScientificConfig, + pub(super) axis: u8, + pub(super) x_next_bin: usize, + pub(super) y_next_bin: usize, + pub(super) progressive_tiles: bool, + pub(super) progressive_tile_origins: Option>, + pub(super) x_parts: Vec, + pub(super) y_parts: Vec, +} + +pub(super) struct PendingPreparationTile { + pub(super) x: usize, + pub(super) y: usize, + pub(super) inner: MatrixTile, +} + +pub(super) fn join_axis_parts(parts: Vec) -> FrozenAxis { + FrozenAxis::join_contiguous(parts) + .expect("prepared axis parts must be compatible and contiguous") +} + +pub(super) fn progressive_tiles_for_part( + state: &ComparisonPreparation, + building_axis: u8, +) -> Vec { + if state.x_index == state.y_index { + let Some(current) = state.x_parts.last() else { + return Vec::new(); + }; + return state + .x_parts + .iter() + .filter_map(|previous| selected_pending_tile(state, current, previous)) + .collect(); + } + if building_axis == 0 { + let Some(current) = state.x_parts.last() else { + return Vec::new(); + }; + state + .y_parts + .iter() + .filter_map(|y_part| selected_pending_tile(state, current, y_part)) + .collect() + } else { + let Some(current) = state.y_parts.last() else { + return Vec::new(); + }; + state + .x_parts + .iter() + .filter_map(|x_part| selected_pending_tile(state, x_part, current)) + .collect() + } +} + +fn selected_pending_tile( + state: &ComparisonPreparation, + x: &FrozenAxis, + y: &FrozenAxis, +) -> Option { + if state + .progressive_tile_origins + .as_ref() + .is_some_and(|origins| !origins.contains(&(x.offset(), y.offset()))) + { + return None; + } + Some(PendingPreparationTile { + x: x.offset(), + y: y.offset(), + inner: compute_frozen_tile( + x, + y, + TileRequest { + x_start: x.offset(), + y_start: y.offset(), + width: x.core_encoded().len(), + height: y.core_encoded().len(), + }, + ) + .ok()?, + }) +} diff --git a/crates/wasm/src/resource.rs b/crates/wasm/src/resource.rs new file mode 100644 index 0000000..32e6431 --- /dev/null +++ b/crates/wasm/src/resource.rs @@ -0,0 +1,73 @@ +//! Wasm retained-memory estimates and pre-allocation admission. + +use super::{ComputeSession, CoreScientificConfig, MatrixTile, PackedSequence}; + +pub(super) const MAX_CACHED_AXIS_BYTES: usize = 192 * 1024 * 1024; +pub(super) const MAX_PREPARED_AXIS_BYTES: usize = 384 * 1024 * 1024; +pub(super) const MAX_WASM_SESSION_BYTES: usize = 3 * 1024 * 1024 * 1024; +pub(super) const MAX_PENDING_PREPARATION_TILE_BYTES: usize = 16 * 1024 * 1024; +pub(super) const MAX_TILE_EDGE: usize = 256; + +pub(super) fn estimated_frozen_axis_bytes( + resolution: usize, + config: CoreScientificConfig, +) -> usize { + let words = config.register_count().div_ceil(64); + let signature_words = usize::from(config.verification_bits()).saturating_add(3); + let one_signature = signature_words + .saturating_mul(words) + .saturating_mul(std::mem::size_of::()) + .saturating_add(std::mem::size_of::()); + resolution + .saturating_mul( + one_signature + .saturating_mul(2) + .saturating_add(std::mem::size_of::<(u64, u64)>()), + ) + .saturating_add(std::mem::size_of::()) + .saturating_add(1_024) +} + +pub(super) fn estimated_sequence_bytes(sequence_heap_bytes: usize, capacity: usize) -> usize { + std::mem::size_of::() + .saturating_add(capacity.saturating_mul(std::mem::size_of::())) + .saturating_add(sequence_heap_bytes) +} + +pub(super) fn validate_prepared_axis_admission( + resolution: usize, + config: CoreScientificConfig, + axis_count: usize, + current_session_bytes: usize, +) -> Result { + let admitted = estimated_frozen_axis_bytes(resolution, config) + .checked_mul(axis_count) + .ok_or("prepared-axis estimate overflowed")?; + if admitted > MAX_PREPARED_AXIS_BYTES { + return Err( + "This quality tier exceeds the 384 MiB prepared-axis budget; lower plot resolution or sketch accuracy.", + ); + } + if current_session_bytes.saturating_add(admitted) > MAX_WASM_SESSION_BYTES { + return Err( + "This comparison exceeds the Wasm session memory budget; lower plot resolution or sketch accuracy.", + ); + } + Ok(admitted) +} + +pub(super) fn matrix_tile_heap_bytes(tile: &MatrixTile) -> usize { + tile.identity + .capacity() + .saturating_mul(std::mem::size_of::()) + .saturating_add( + tile.direction + .capacity() + .saturating_mul(std::mem::size_of::()), + ) + .saturating_add( + tile.direction_support + .capacity() + .saturating_mul(std::mem::size_of::()), + ) +} diff --git a/crates/wasm/src/session.rs b/crates/wasm/src/session.rs new file mode 100644 index 0000000..81149db --- /dev/null +++ b/crates/wasm/src/session.rs @@ -0,0 +1,9 @@ +//! Immutable session selection context. + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) struct ComparisonContext { + pub(super) x_index: usize, + pub(super) y_index: usize, + pub(super) domain_length: u64, + pub(super) k: u8, +} diff --git a/docs/CITATIONS.md b/docs/CITATIONS.md new file mode 100644 index 0000000..60978a0 --- /dev/null +++ b/docs/CITATIONS.md @@ -0,0 +1,93 @@ +# Citations + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +## Preferred citation + +Users of ModDotPlot Browser are requested to cite the original ModDotPlot paper: + +Sweeten AP, Schatz MC, Phillippy AM. ModDotPlot-rapid and interactive visualization of +tandem repeats. *Bioinformatics*. 2024;40(8):btae493. +[doi:10.1093/bioinformatics/btae493](https://doi.org/10.1093/bioinformatics/btae493). +PMID: 39110522; PMCID: PMC11321072. + +The structured version of this request is the `preferred-citation` entry in +[`CITATION.cff`](../CITATION.cff). + +## Scientific methods and reference implementations + +ModDotPlot Browser relies on or was validated against the following methods and reference +implementations. Cite the relevant works when publishing analyses that depend on those +components. + +### MinHash, containment, and genomic sketching + +* Broder AZ. On the resemblance and containment of documents. *Compression and + Complexity of Sequences 1997*. 1997:21-29. + [doi:10.1109/SEQUEN.1997.666900](https://doi.org/10.1109/SEQUEN.1997.666900). +* Ondov BD, Treangen TJ, Melsted P, Mallonee AB, Bergman NH, Koren S, Phillippy AM. + Mash: fast genome and metagenome distance estimation using MinHash. *Genome Biology*. + 2016;17:132. + [doi:10.1186/s13059-016-0997-x](https://doi.org/10.1186/s13059-016-0997-x). + +### Historical MashMap and minmer design references + +* Jain C, Dilthey A, Koren S, Aluru S, Phillippy AM. A fast approximate algorithm for + mapping long reads to large reference databases. *Journal of Computational Biology*. + 2018;25(7):766-779. + [doi:10.1089/cmb.2018.0036](https://doi.org/10.1089/cmb.2018.0036). +* Kille B, Garrison E, Treangen TJ, Phillippy AM. Minmers are a generalization of + minimizers that enable unbiased local Jaccard estimation. *Bioinformatics*. + 2023;39(9):btad512. + [doi:10.1093/bioinformatics/btad512](https://doi.org/10.1093/bioinformatics/btad512). + +MashMap/minmers informed the original design and Phase 0 evaluation, but are not part +of the current executable or scientific oracle. Their former reference code is +recoverable at Git tag `v0.1.0`; exact distinct canonical k-mer containment is now the +sole scientific oracle. + +### Hashing and sketch representation + +* Kazemi P, Wong J, Nikolić V, Mohamadi H, Warren RL, Birol I. ntHash2: recursive + spaced seed hashing for nucleotide sequences. *Bioinformatics*. + 2022;38(20):4812-4813. + [doi:10.1093/bioinformatics/btac564](https://doi.org/10.1093/bioinformatics/btac564). +* Li P, König AC. b-Bit minwise hashing. *Proceedings of the 19th International + Conference on World Wide Web*. 2010:671-680. + [doi:10.1145/1772690.1772759](https://doi.org/10.1145/1772690.1772759). +* Li P, Owen AB, Zhang C-H. One permutation hashing. *Advances in Neural Information + Processing Systems 25*. 2012:3122-3130. + [Proceedings record](https://papers.nips.cc/paper_files/paper/2012/hash/eaa32c96f620053cf442ad32258076b9-Abstract.html). +* Flajolet P, Fusy É, Gandouet O, Meunier F. HyperLogLog: the analysis of a near-optimal + cardinality estimation algorithm. *Discrete Mathematics and Theoretical Computer + Science Proceedings, Analysis of Algorithms 2007*. 2007;AH:127-146. + [Official paper](https://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf). + +### Historical MurmurHash3 compatibility reference + +The removed Phase 0 MashMap path was checked against Austin Appleby's public-domain +[MurmurHash3 reference implementation](https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp) +and a pinned MashMap fixture. The current release compiles only ntHash2. + +## Reference software + +The structured metadata also credits the original +[ModDotPlot](https://github.com/marbl/moddotplot) implementation, +[MashMap](https://github.com/marbl/MashMap) commit `1a07d0e`, the +[ntHash](https://github.com/bcgsc/ntHash) source commit pinned in the test fixtures, +and the historically consulted SMHasher/MurmurHash3 reference implementation. + +Similarity color schemes use [ColorBrewer 2](https://colorbrewer2.org/), created by +Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. Viridis color +data are adapted from +[d3-scale-chromatic 3.1.0](https://github.com/d3/d3-scale-chromatic/blob/main/src/sequential-multi/viridis.js), +Copyright 2010-2024 Mike Bostock. The application stores these color tables locally and +makes no runtime request to either source. + +## Source-level licensing + +The Rust ntHash2 implementation is a clean implementation of the published method and +constants. Its upstream license, along with the ColorBrewer and d3-scale-chromatic +notices, is included in [`THIRD_PARTY_LICENSES.txt`](../THIRD_PARTY_LICENSES.txt). +ModDotPlot Browser itself is released under MIT-0. The complete locked dependency +inventory is generated as [`SBOM.cdx.json`](../SBOM.cdx.json). diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..4e61a34 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,495 @@ +# Local installation and HPC deployment + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +`ModDotPlot Browser` is a static Rust/WebAssembly web application. After it has been +built, it does not require an application server or database. A web server only +delivers the static files; FASTA parsing, dotplot computation, and rendering happen +inside the user's browser. + +## Requirements + +The current build uses: + +- Git +- Node.js 25.9.0 +- npm +- Rust 1.98.0 +- The Rust `wasm32-unknown-unknown` target +- `wasm-pack` 0.15.0 +- A modern browser supporting WebAssembly, Web Workers, WebGL2, and + `DecompressionStream` + +The required Node.js and Rust versions are also recorded in `.node-version` and +`rust-toolchain.toml`. + +--- + +## Local installation + +### 1. Download the source code + +The recommended method is to clone the `main` branch: + +```bash +git clone https://github.com/marbl/ModDotPlot-Browser.git +cd ModDotPlot-Browser +``` + +Alternatively, download the source without Git: + +```bash +curl -L \ + https://github.com/marbl/ModDotPlot-Browser/archive/refs/heads/main.zip \ + -o ModDotPlot-Browser-main.zip + +unzip ModDotPlot-Browser-main.zip +cd ModDotPlot-Browser-main +``` + +If you intend to customize the application, fork the repository and clone your fork +instead. + +### 2. Install Node.js + +If `nvm` is already installed, use the version recorded by the repository: + +```bash +source "${NVM_DIR:-$HOME/.nvm}/nvm.sh" +nvm install "$(cat .node-version)" +nvm use "$(cat .node-version)" +``` + +Confirm the versions: + +```bash +node --version +npm --version +``` + +Node.js should report version `25.9.0`. + +### 3. Install Rust + +If Rust is not already installed: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --profile minimal + +source "$HOME/.cargo/env" +``` + +Install the pinned Rust toolchain and WebAssembly target: + +```bash +rustup toolchain install 1.98.0 \ + --profile minimal \ + --component rustfmt \ + --component clippy \ + --target wasm32-unknown-unknown +``` + +Install the pinned version of `wasm-pack`: + +```bash +cargo install wasm-pack --version 0.15.0 --locked +``` + +Verify the installation: + +```bash +rustc --version +cargo --version +wasm-pack --version +``` + +### 4. Install dependencies and build + +Install the exact JavaScript dependencies recorded in `package-lock.json`: + +```bash +npm --prefix web ci +``` + +Build the Rust/WebAssembly code and static website: + +```bash +npm --prefix web run build +``` + +The deployable website is written to: + +```text +web/dist/ +``` + +The build also runs the TypeScript tests, prepares the bundled *Arabidopsis thaliana* +example, generates the WebAssembly bindings, checks the documentation contracts, and +updates the software dependency inventory. + +### 5. Launch locally + +Start the included preview server: + +```bash +npm --prefix web run preview -- \ + --host 127.0.0.1 \ + --port 4178 \ + --strictPort +``` + +Open the following address in a browser: + +```text +http://127.0.0.1:4178 +``` + +Keep the terminal running while using the application. Stop the server with +`Control-C`. + +If port `4178` is already in use, choose another port: + +```bash +npm --prefix web run preview -- \ + --host 127.0.0.1 \ + --port 4180 \ + --strictPort +``` + +Then open: + +```text +http://127.0.0.1:4180 +``` + +--- + +## HPC deployment + +There are two common ways to use `ModDotPlot Browser` on an HPC system: + +1. Build and run the static server on the HPC system, then reach it through an SSH + tunnel. +2. Install `web/dist/` on an institutional Apache, Nginx, or Open OnDemand web server. + +Please note that each HPC environment has its own policies regarding software installation, network access, and resource usage. Before installing or running `ModDotPlot Browser`, consult your cluster’s documentation and follow local guidelines for job submission and the use of login and compute nodes. Contact your HPC administrators if you are unsure what is permitted or need help setting up the software. + +### Important data-location behavior + +The application runs wherever the browser runs. + +If `ModDotPlot Browser` is served by an HPC system but opened in a browser on a laptop: + +- Computation occurs on the laptop. +- Browser memory on the laptop determines the practical dataset limit. +- The file picker sees files on the laptop, not arbitrary paths on the HPC filesystem. +- Allocating more HPC CPUs or GPUs does not accelerate the browser on the laptop. +- FASTA and annotation files selected through the browser are not uploaded to the web + server. + +To open files stored on an HPC filesystem, use one of the following approaches: + +- Copy the files to the local workstation with `scp`, SFTP, or Globus. +- Mount the HPC filesystem locally with SSHFS. +- Run the browser inside an Open OnDemand or VNC remote desktop. +- Ask an HPC administrator to provide an authenticated data-serving integration. + +**Do not place private genomic data in a publicly accessible web directory!** + +### Option 1: Build directly on the HPC system + +Clone the repository: + +```bash +git clone --branch main https://github.com/marbl/ModDotPlot-Browser.git +cd ModDotPlot-Browser +``` + +Many HPC systems provide Node.js and Rust through environment modules. Available module +names vary by cluster: + +```bash +module spider node +module spider rust +``` + +Load suitable versions if available: + +```bash +module load nodejs/25.9.0 +module load rust/1.98.0 +``` + +If the cluster does not provide these modules, install Node.js and Rust in the user +account using the local installation instructions above. + +Ensure that the WebAssembly target and `wasm-pack` are available: + +```bash +rustup target add wasm32-unknown-unknown \ + --toolchain 1.98.0 + +cargo install wasm-pack --version 0.15.0 --locked +``` + +Build the application: + +```bash +npm --prefix web ci +npm --prefix web run build +``` + +If compute nodes do not have outbound internet access, perform dependency installation +on a login or designated build node. Follow the local HPC policy regarding compilation +on login nodes. + +### Option 2: Build locally and copy the static site to the HPC system + +This option avoids installing Rust on the HPC system. + +Build the application on a local computer: + +```bash +cd ModDotPlot-Browser + +npm --prefix web ci +npm --prefix web run build +``` + +Package the generated static site: + +```bash +tar -C web/dist \ + -czf moddotplot-browser-v1.0.0-dist.tar.gz \ + . +``` + +Copy it to the HPC system: + +```bash +scp moddotplot-browser-v1.0.0-dist.tar.gz \ + USER@login.cluster.example.edu:/path/to/destination/ +``` + +On the HPC system: + +```bash +mkdir -p /path/to/moddotplot-browser-v1.0.0 + +tar -xzf /path/to/destination/moddotplot-browser-v1.0.0-dist.tar.gz \ + -C /path/to/moddotplot-browser-v1.0.0 +``` + +The extracted directory is the complete static website. It can be published through +Apache, Nginx, Open OnDemand, or another static server. + +### Temporary personal HPC session + +For a temporary personal session, use the repository's preview server. This method +requires Node.js and the installed `web/node_modules` dependencies on the HPC system. + +From the repository checkout: + +```bash +npm --prefix web ci +``` + +If `web/dist/` has not already been built: + +```bash +npm --prefix web run build +``` + +Start the server: + +```bash +npm --prefix web run preview -- \ + --host 127.0.0.1 \ + --port 4178 \ + --strictPort +``` + +Leave this process running. + +#### Server running on the login node + +On the local workstation, open a second terminal and create an SSH tunnel: + +```bash +ssh -N \ + -L 4178:127.0.0.1:4178 \ + USER@login.cluster.example.edu +``` + +Open: + +```text +http://127.0.0.1:4178 +``` + +Do not run a long-lived server on a login node unless cluster policy permits it. + +#### Server running on a Slurm compute node + +Request an interactive allocation: + +```bash +srun \ + --pty \ + --time=04:00:00 \ + --cpus-per-task=1 \ + --mem=2G \ + bash +``` + +On the allocated node, record its hostname: + +```bash +hostname -f +``` + +Start `ModDotPlot Browser` from the repository checkout: + +```bash +cd /path/to/ModDotPlot-Browser + +npm --prefix web run preview -- \ + --host 127.0.0.1 \ + --port 4178 \ + --strictPort +``` + +On the local workstation, tunnel through the login node to the allocated compute node: + +```bash +ssh -N \ + -J USER@login.cluster.example.edu \ + -L 4178:127.0.0.1:4178 \ + USER@COMPUTE_NODE +``` + +Replace: + +- `USER` with the HPC username. +- `login.cluster.example.edu` with the login host. +- `COMPUTE_NODE` with the hostname returned by `hostname -f`. + +Open: + +```text +http://127.0.0.1:4178 +``` + +Some clusters do not permit direct SSH connections to compute nodes. In that case, use +the cluster's documented multi-hop tunneling procedure or Open OnDemand. + +### Persistent shared HPC deployment + +For a shared deployment, publish `web/dist/` through an institutional static web server. + +For example: + +```bash +mkdir -p /path/to/web/root/moddotplot +cp -a web/dist/. /path/to/web/root/moddotplot/ +``` + +If the application is served from the root of a domain: + +```text +https://moddotplot.example.edu/ +``` + +the default build is sufficient. + +If it is served below a URL prefix: + +```text +https://tools.example.edu/moddotplot/ +``` + +rebuild with the prefix, including the leading and trailing slashes: + +```bash +MODDOTPLOT_BASE_PATH=/moddotplot/ \ + npm --prefix web run build +``` + +Then deploy the new contents of `web/dist/`. + +The static server should: + +- Serve `.wasm` files using the MIME type `application/wasm`. +- Support HTTP byte-range requests. +- Preserve `Range`, `Content-Range`, and `Content-Length` headers. +- Serve bundled `.gz` files as binary files without automatically adding a + `Content-Encoding` header. +- Permit JavaScript module workers. +- Use HTTPS for a remotely shared deployment. + +Byte-range support is important for indexed access to the bundled example FASTA. + +### Open OnDemand + +Open OnDemand is often the easiest way to use files directly from an HPC filesystem. + +Two possible configurations are: + +1. Publish `web/dist/` as a static institutional application. In this arrangement, the + browser still runs on the user's workstation and cannot directly select arbitrary + HPC paths. +2. Launch an Open OnDemand interactive desktop and open `ModDotPlot Browser` inside the + remote desktop's browser. The browser file picker can then access files visible on + the HPC filesystem. + +The second configuration is recommended when large FASTA files should remain on the +HPC system. + +--- + +## Customizing a deployment + +Edit source files rather than editing `web/dist/`, because every build replaces +`web/dist/`. + +Common customization locations include: + +```text +web/index.html Landing-page content +web/src/styles.css Application styling +web/src/ TypeScript application code +web/public/moddotplot-browser.gif Animated landing logo +web/public/browser_1.png Social-link preview +web/public/ Other static files +web/package.json Application version +``` + +After making changes: + +```bash +npm --prefix web run build +``` + +Restart the preview server or copy the updated `web/dist/` directory to the persistent +web-server location. + +--- + +## Updating an installation + +For an unmodified checkout: + +```bash +cd ModDotPlot-Browser +git switch main +git pull --ff-only + +npm --prefix web ci +npm --prefix web run build +``` + +Restart the preview server or redeploy `web/dist/`. + +Custom installations should be maintained in a separate Git branch or fork. Merge +updates from the upstream `main` branch rather than overwriting local changes. diff --git a/docs/METHODS.md b/docs/METHODS.md new file mode 100644 index 0000000..c3573de --- /dev/null +++ b/docs/METHODS.md @@ -0,0 +1,210 @@ +# Methods + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +## How the algorithm works + +ModDotPlot Browser preserves the central idea introduced in the original +[ModDotPlot publication](https://doi.org/10.1093/bioinformatics/btae493): construct an +alignment-free dot plot by comparing sketches of canonical *k*-mers in genomic intervals. +However, the browser uses a new sketching and rendering implementation designed for +bounded memory, progressive computation, WebAssembly, and interactive zooming. + +The browser is therefore not a line-for-line port of the published Python ModDotPlot +algorithm. It retains the containment-based scoring model and window-expansion strategy, +but replaces hierarchical modimizers with one-permutation hashing and cardinality +estimation. + +*For this reason, we recommend using `ModDotPlot Browser` only for exploratory analysis.* + +### 1. One-permutation sketches + +The published ModDotPlot algorithm uses hierarchical modimizers. ModDotPlot Browser +instead summarizes each interval with an undensified one-permutation hash sketch, +abbreviated **OPH**. + +For a sketch containing `m` registers: + +1. The upper hash bits assign each canonical *k*-mer to one register. +2. The smallest complete hash assigned to a register becomes that register's winner. +3. Empty registers remain empty; they are not filled or densified. +4. A 32-bit winner fingerprint is retained for scientific comparison. +5. Orientation bits record whether the winning canonical *k*-mer was observed on the + forward strand, reverse strand, or both. + +The default sketch sizes are: + +```text +Preview: 256 registers +Detailed: 1,024 registers +``` + +The 256-register sketch is intended to paint a fast provisional overview. Selected +tiles are subsequently recomputed with 1,024 registers when the preview has weak +support or lies close to a display threshold. + +### 2. Estimating Jaccard similarity + +For two interval sketches, let: + +```text +U = registers occupied in at least one sketch +M = occupied registers whose verified 32-bit winners match +``` + +The estimated Jaccard similarity is: + +```text +J_hat = M / U +``` + +This is the matched-bin OPH estimator. A smaller 14-bit comparison is used as an +execution prefilter, but a scientific match is accepted only after the 32-bit winner +fingerprints agree. + +### 3. Estimating set cardinalities and containment + +Each interval also maintains a HyperLogLog cardinality estimate using precision 10. +This estimates the number of distinct canonical *k*-mers in each interval. + +Given estimated Jaccard similarity `J` and estimated cardinalities `|A|` and `|B|`, the +intersection is estimated as: + +```text +I_hat = J × (|A| + |B|) / (1 + J) +``` + +Directed containment is then: + +```text +C_hat(A in B) = clamp(I_hat / |A|, 0, 1) +``` + +Containment is directional: a short repeat may be almost completely contained in a +larger interval even when the larger interval is not contained in the shorter one. + +### 4. Computing the displayed score + +For core intervals `X` and `Y`, and expanded intervals `X+` and `Y+`, the browser +computes containment in both directions: + +```text +C_x = containment(X, Y+) +C_y = containment(Y, X+) +C = max(C_x, C_y) +``` + +The displayed score is: + +```text +ANI_c = C^(1/k) +``` + +The maximum directed containment is used so self-comparison matrices remain symmetric +and so a strong match can be detected when either core interval is contained within the +expanded interval on the opposite axis. + +The value is stored as: + +```text +round(ANI_c × 10,000) +``` + +A stored value of `10,000` represents 100%, while `65,535` is reserved for missing data. + +### 5. Orientation and direction coloring + +When matching canonical sketch winners have the same unambiguous source orientation, +they contribute a forward vote. Opposite source orientations contribute a reverse +vote. + +Direction is summarized as: + +```text +direction = (forward - reverse) / (forward + reverse) +``` + +Positive values indicate forward-oriented similarity and negative values indicate +reverse-complement similarity. Palindromic or otherwise ambiguous observations do not +force either direction. + +Direction coloring changes the presentation of a tile but does not change its +containment or `ANI_c` score. + +### 6. Progressive computation + +The browser does not precompute every zoom level before displaying a plot. + +Instead: + +1. A 256-register overview is computed and painted incrementally. +2. Completed blocks can appear before the complete axes have finished preparing. +3. Tiles with weak evidence or values near display thresholds are selected for + 1,024-register refinement. +4. Detailed tiles replace compatible preview tiles as they become available. +5. Panning or zooming cancels obsolete work. +6. Previously computed tiles are retained in a bounded browser cache. +7. Zoomed regions are recomputed on demand at the required genomic scale. + +This produces a responsive first view without requiring all possible zoom matrices to +be held in memory. + +### 7. Exact terminal zoom + +When the displayed interval reaches one matrix position per base, the approximate +sketch estimator is no longer used. The browser switches to exact canonical *k*-mer +comparison. + +At this resolution: + +- Equal valid canonical *k*-mers match exactly. +- Unequal valid *k*-mers are mismatches. +- *K*-mers overlapping ambiguous bases are missing. +- A match is drawn across its complete k-base footprint. +- Forward matches produce forward diagonals. +- Reverse-complement matches produce reverse diagonals. +- Reverse-complement-palindromic *k*-mers have neutral direction evidence. + +The default exact view colors matches by the corresponding base from the X sequence. +An identity-colored exact view is also available. Changing exact-view colors does not +change the underlying exact match matrix. + +## How this differs from the published ModDotPlot algorithm + +The browser retains the published method's high-level containment model but changes its +sketch representation, execution strategy, and interactive rendering architecture. + +| Component | Published ModDotPlot | ModDotPlot Browser | +| --- | --- | --- | +| Primary implementation | Python with Plotly, Dash, Flask, NumPy, and Plotnine | Rust compiled to WebAssembly with TypeScript and WebGL2 | +| Execution location | Python process on a workstation, server, or HPC node | Locally inside the user's browser | +| *K*-mer hash | MurmurHash3 | Rolling canonical ntHash2 | +| Sketch type | Hierarchical modulo sketches called modimizers | Undensified one-permutation hashing | +| Sketch size | Approximately 1,000 modimizers per window | 256 preview or 1,024 detailed OPH registers | +| Repetitive-window correction | Adaptively reduces modimizer sparsity when too few modimizers are selected | Not used in the production browser | +| Set comparison | Direct intersection of selected modimizer sets | Verified OPH winner matches divided by union-occupied registers | +| Cardinality handling | Modimizer-set sizes and a fractional-minhash containment correction | HyperLogLog precision 10 plus OPH-estimated Jaccard | +| Window-offset correction | Expands the target window by half a window on each side | Preserved using reusable half-window summaries | +| Interactive hierarchy | Precomputes full matrices for multiple hierarchical modimizer levels | Computes an overview, selectively refines uncertain tiles, and builds zoom tiles on demand | +| Zoom storage | Multiple complete similarity matrices | Bounded prepared-axis and tile caches | +| Terminal zoom | Limited by the minimum precomputed window size | Switches to exact canonical *k*-mer comparison at one base per cell | +| Orientation display | Primarily identity heatmaps | Identity or support-aware forward/reverse orientation coloring | +| Multiple sequences | Self and pairwise matrices generated by the CLI | Self, pairwise, and interactive grids of up to six sequences | +| Annotations | Not part of the core paper algorithm | Local BED, GFF3, GTF, GC%, and CpG tracks | +| Output | Static plots, interactive Dash plots, and compressed matrix data | Interactive WebGL plots, images, BEDPE, and an official-CLI configuration package | + +### Why the sketch method changed + +Hierarchical modimizers are effective in the command-line implementation because they +form nested sketches that can be reused across precomputed zoom levels. In a browser, +however, retaining complete modimizer sets and several full matrices can require +substantial memory for chromosome- or genome-scale input. + +The browser's OPH representation provides a fixed-size summary for each interval. +HyperLogLog supplies a compact estimate of the corresponding distinct *k*-mer +cardinality. Together, these allow containment to be estimated without retaining the +complete *k*-mer set or a full hierarchy of precomputed matrices. + +The browser also separates fast first paint from detailed computation. A small sketch +can paint an overview quickly, while more expensive sketches are reserved for cells +where additional evidence is likely to affect the display. diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md new file mode 100644 index 0000000..874ba77 --- /dev/null +++ b/docs/PRIVACY.md @@ -0,0 +1,52 @@ +# Privacy and local data handling + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +ModDotPlot Browser is designed to process genomic data locally in the user's browser. + +## Application behavior + +The current application: + +* reads FASTA, FAI, GZI, BED, GFF3, and GTF files, including gzip- and BGZF-compressed + sequence and annotation versions where supported, through browser file APIs; +* parses and sketches sequences in a dedicated Web Worker and WebAssembly instance; +* renders numeric results through WebGL2; +* makes no application API calls; +* includes no analytics, advertising, telemetry, CDN assets, or runtime package + downloads; and +* does not intentionally upload sequence, annotation, sketch, or plot data. + +The built application is a set of static files. It still requires HTTP or HTTPS +hosting because browsers restrict module workers and WebAssembly on `file://` pages. +The hosting provider can observe normal requests for the application files, including +IP address and browser metadata according to that provider's policy. Loaded genomic +files are not part of those requests. + +## Memory and lifetime + +Loaded data and calculated results are held in browser memory for the current tab. +ModDotPlot Browser does not persist sessions in IndexedDB, local storage, or OPFS. +Selecting **Clear** removes live application records and cache entries. +Reloading or closing the page destroys the worker and Wasm instance. + +For indexed FASTA, the worker retains browser `File` references and validated index +metadata so it can read a selected record later. This is local random access to the +user-selected file, not an upload or a persistent copy. Records already requested by a +plot remain packed in the current Wasm session for reuse until Clear, replacement, or +page close. + +WebAssembly linear memory can remain at a high-water allocation until the page is +reloaded, even after the application has released the objects that occupied it. The +memory is locally reusable and is not evidence of a network transfer. + +## Boundaries of this statement + +Browser extensions, modified browsers, operating-system services, developer tools, +and the policies of the static host are outside ModDotPlot Browser's control. Users +handling sensitive or controlled genomic data should use a trusted browser profile, +review the deployed build and hosting environment, and apply their institution's data +governance requirements. + +Future persistence, remote collaboration, or telemetry features must be opt-in, +documented here, and reviewed as a change to the application's privacy model. diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..3dad150 --- /dev/null +++ b/docs/RELEASE_CHECKLIST.md @@ -0,0 +1,106 @@ +# Release checklist + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +This checklist applies to the v1.0.0 release. The browser remains intended for +exploratory use; analyses intended for publication should use the official ModDotPlot CLI. + +## Source and identity + +- [x] Baseline release is committed and annotated as `v0.1.0`. +- [x] Product name is **ModDotPlot Browser**; technical packages and archives retain + `moddotplot-interactive`; license is MIT-0 in the root and packaged crates. +- [x] Canonical authorship is recorded in `CITATION.cff`; generated prose uses the + standard file note. +- [x] `CITATION.cff` requests the original ModDotPlot citation and credits method and + historical reference implementations. +- [x] The static application processes user-selected data locally without uploading it. + +## Scientific gate + +- [x] Production configuration is immutable, validated, versioned, and digestible. +- [x] Exact distinct canonical k-mers are the independent containment oracle. +- [x] The browser-default preview, detailed, and selective-composite production paths + have assertion-backed exact-containment acceptance tests. +- [x] Verified-winner OPH and HLL p10 are supported by the documented sweep. +- [x] Initial terminal views enter exact mode before their first tile. +- [x] `U/u` normalization and coordinate behavior are documented and tested. +- [x] Rejected estimator and fused-projection executable paths have been removed from + default artifacts or isolated in validation-only code. +- [x] Freeze the release configuration version and digests in executable tests. +- [x] Run the bounded release statistical report and attach its complete machine-readable + output, command, hardware, commit, and pass/fail interpretation. + +## Correctness and browser gate + +Run from a clean checkout: + +```sh +cargo fmt --all --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +cargo clippy --workspace --all-targets --features validation -- -D warnings +cargo test --workspace --features validation +cargo check -p moddotplot-wasm --target wasm32-unknown-unknown +npm --prefix web ci +npm --prefix web run verify +``` + +- [x] Chromium, Firefox, and WebKit integration covers rapid replacement load, clear, + exact entry, unexpected egress, keyboard navigation, and context restoration. +- [x] The same suite passes in installed Google Chrome; WebKit 26.5 provides automated + Safari-engine coverage. Actual Safari WebDriver requires the user's disabled global + “Allow remote automation” setting and was not enabled by the release process. +- [x] Inject worker crash and prepared-axis allocation rejection and verify useful + recovery paths. +- [x] Confirm self/pairwise plots, forward/reverse mode, reset, rapid zoom, cache + eviction policy, resize, gzip, ambiguity, and short inputs. + +## Performance and resource gate + +- [x] Frozen axes and pre-allocation admission bound the main Wasm structures. +- [x] Tile/axis caches are byte bounded and use coordinated eviction. +- [x] Fused projection was benchmarked and rejected for first-paint latency. +- [x] Record current time to first coherent structure, stable viewport, Wasm/JS/GPU + memory, and cancellation latency for representative self and pairwise chromosomes. +- [x] Confirm no monotonic live-cache growth during a fixed pan/zoom trace. +- [x] Clearly retain the 1 Gb input value as an upper bound rather than a laptop + performance guarantee until sharding/spill validation is complete. +- [x] Run the generated million-record annotation parser/publication gate and enforce + explicit per-file byte and matching-record admission limits. + +## Reproducibility and distribution gate + +- [x] Rust, Node/npm, dependencies, and CI actions are pinned. +- [x] One `npm --prefix web run build` command generates and tests the static release. +- [x] `SBOM.cdx.json` and `THIRD_PARTY_LICENSES.txt` are generated from locked packages + plus checked-in license assets for embedded third-party data such as ColorBrewer and + d3-scale-chromatic Viridis. +- [ ] Generate separate source and binary archives in a local packaging preflight; + regenerate them from the annotated release commit as the final step. +- [ ] Attach SHA-256 checksums and build provenance: commit, dirty-state check, OS, + architecture, Rust, wasm-pack, wasm-bindgen/wasm-opt, Node, and npm versions. +- [ ] Rebuild twice from clean checkouts; confirm byte identity or document the exact + nondeterministic fields. +- [ ] Verify the binary archive contains `LICENSE`, notices, license bundle, SBOM, + citation metadata, user guide, and deployable static assets. + +## Privacy and local-static-artifact gate + +- [x] Source and automated fixture processing show no application data egress. +- [x] A restrictive CSP is present in the static document. +- [x] Keep the restrictive document CSP in the static archive. HTTP response headers + remain the responsibility of any future host and are not claimed by this local build. +- [x] Confirm no analytics, CDN, source maps, or unexpected external URLs are present + in the production artifact. Development-only loopback WebSocket permissions are + explicit in the document CSP. +- [x] Document that `file://` is unsupported and HTTP/HTTPS serving is required. + +## Local release finalization + +- [x] Update versions consistently in Cargo, npm, CFF, and the UI. +- [ ] Commit generated SBOM/licenses and release evidence. +- [ ] Create and verify an annotated tag from a clean tree as the final source action. +- [ ] Produce source/binary archives, checksums, provenance, release notes, and known + limitations under the local ignored `release-artifacts/` directory. +- [ ] Push the verified v1.0.0 release commit to the `develop` branch. diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md new file mode 100644 index 0000000..4e03daf --- /dev/null +++ b/docs/USER_GUIDE.md @@ -0,0 +1,150 @@ +# ModDotPlot Browser User Guide & Tutorial + +> **Note:** This project was generated by ChatGPT 5.6 Sol under the direction of Adam Phillippy and Alex Sweeten. For analyses intended for publication, please use the official [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) and cite the [ModDotPlot paper](https://doi.org/10.1093/bioinformatics/btae493). + +## Table of contents + +- [Getting started](#getting-started) +- [Plot Navigation](#plot-navigation) +- [Plot Resolution](#plot-resolution) +- [Plot Customization](#plot-customization) +- [Exporting Data to ModDotPlot CLI](#exporting-data-to-moddotplot-cli) + +## Getting started + +`ModDotPlot Browser` has been tested and works on current versions of Google Chrome, Microsoft Edge, Mozilla Firefox, and Safari. Web browsers must have WebAssembly, Web Workers, and WebGL2 enabled. Mobile browsers appear to be compatible, although navigating plots with touchscreen controls may be more challenging. + +There is no fixed input-size limit because files are processed locally rather than uploaded. The practical limit depends on available system memory and browser WebAssembly constraints. As a reference, the approximately 6 GB HG002 diploid human genome uses about 830 MB of browser memory at the default settings. + +![](../web/public/tutorial_images/landing_page.png) + +To get started, simply drop one or more plain, gzip-compressed, or BGZF FASTA assembly files on the large FASTA target, or use its file chooser. BGZF is detected from its header even when the suffix is `.gz`. For indexed loading, include `sample.fa.fai` with `sample.fa`, or include both `sample.fa.gz.fai` and `sample.fa.gz.gzi` with a BGZF `sample.fa.gz`: + +![](../web/public/tutorial_images/accepted_files.png) + +ModDotPlot will index and validate the provided assembly and, upon completion, create an `Explore` button that launches the interactive browser. You can optionally add BED, GFF, or GTF annotation files on the smaller annotation target. Make sure their sequence identifiers match the corresponding FASTA headers. + +![](../web/public/tutorial_images/explore.png) + +The following tutorial will use the provided *Arabidopsis thaliana* genome assembly from [Naish *et al.* 2021](https://doi.org/10.1126/science.abi7489). + +![](../web/public/tutorial_images/self-similarity_level1.png) + +## Plot Navigation + +To navigate an interactive plot: + +- Scroll with a mouse wheel or trackpad to zoom around the pointer. +- Click and drag to pan across the plot. +- Select `Reset view` to return to the complete plot, and `Clear` to return back to the landing page. + +Zooming is limited to a minimum visible interval of approximately 100 bp on each eligible axis. At the closest zoom levels, the browser switches to exact *k*-mer rendering. The default exact view colors matching cells by nucleotide base, while the `Identity` option applies the selected identity palette. + +![](../web/public/tutorial_images/kmer_coloring.png) + +Hovering over a plot displays the X and Y sequence names, genomic intervals, and estimated identity for the cell beneath the pointer. Cursor guide lines can be configured or disabled in `Advanced options`. + +`ModDotPlot Browser` supports three plot modes when multiple sequences are available: + +- **Self plot** compares one selected sequence against itself. ![](../web/public/tutorial_images/self-similarity-final.png) +- **Pairwise plot** compares two different sequences. Use the X and Y selectors to control which sequence appears on each axis. ![](../web/public/tutorial_images/pairwise.png) +- **Grid plot** displays between two and six selected sequences. Self-comparisons appear along the diagonal running from the bottom left to the top right, while the remaining cells contain pairwise comparisons. ![](../web/public/tutorial_images/grid.png) + +In Grid plot mode, select any cell to open it as a full interactive Self or Pairwise plot. Use the `Back to grid` button to return to the completed grid. Grid order can be changed by dragging a sequence label on either axis; changing one axis updates the other so that corresponding self-comparisons remain on the diagonal. + +## Plot Resolution + +Each pixel along an axis corresponds to a genomic interval of a given length, called the *window size* ($w$). Let $n$ be the chromosome length in base pairs and $r$ be the number of pixels spanning the chromosome along that axis. The window size is computed through the following formula: + +$$ +w = \frac{n}{r} +$$ + +A higher display resolution therefore corresponds to a smaller window size, with fewer base pairs represented by each pixel. `ModDotPlot Browser` has a default resolution of 1,000 × 1,000. While the window size cannot be modified directly, the resolution can be changed in the advanced settings panel: + +![](../web/public/tutorial_images/resolution.png) + +Rendering at a higher resolution requires more computation and memory. Because the number of cells grows with the square of the resolution, doubling both plot dimensions creates approximately four times as many cells. High and Ultra settings should therefore be used cautiously with large sequences, large grids, or computers with limited memory. + +- $r = 500$ (250,000 cells, approximately 420 MB of memory) ![](../web/public/tutorial_images/500.png) +- $r = 4,000$ (16 million cells, approximately 1.16 GB of memory) ![](../web/public/tutorial_images/4000.png) + +Additional non-aesthetic settings are available under `Advanced options`: + +- **Quick-view accuracy** controls the sketch size used to generate the first responsive preview. +- **Detailed accuracy** controls the sketch size used for the refined result. +- ***K*-mer length** changes the *k*-mer size used in similarity estimation. Only odd values are allowed to prevent palindromes. + +Increasing Quick-view or Detailed accuracy can improve estimator stability but requires more processing time and memory. Changing the *k*-mer length changes the scientific comparison itself and should be done deliberately. + +## Plot Customization + +The controls beside the plot can be used to change its appearance without reloading the input assembly. + +The `Color` control provides two display modes: + +- **Similarity** colors cells by estimated sequence identity (ANI). +- **Direction** distinguishes forward (blue) and reverse-orientation (pink) matches. Color intensity reflects the estimated identity. + +![](../web/public/tutorial_images/direction.png) + +The plot background can be switched between white and black. Under `Identity colors`, the palette selector contains supported ColorBrewer palettes grouped by palette class, together with Spectral and Viridis options. Spectral is the default. + +The identity color controls can be used to: + +- Select the number of distinct palette colors. +- Reverse the palette with the `Flip` button. +- Adjust the minimum, midpoint, and maximum displayed identity values. +- Select an individual color swatch and replace it using a color picker or hexadecimal color code. + +![](../web/public/tutorial_images/color-wheel.png) + +Adjusting these controls changes how the calculated values are displayed; it does not change the underlying similarity calculation. + +The `Feature tracks` panel can display the following computed tracks on the X axis, Y axis, or both: + +- **GC%:** average GC content in each visible genomic window. +- **CpG O/E:** observed-to-expected CpG ratio, displayed on a 0–2 scale. + +BED, GFF3, and GTF annotation files can also be added from the landing page or with the `Add track` button. Plain-text, gzip-compressed, and BGZF-compressed annotations are supported. A multi-sequence annotation file is represented by one logical track, and features are automatically matched to the sequence currently displayed on each axis. + +Use the `Track placement` controls to display X-axis tracks above or below the plot and Y-axis tracks to its left or right. The selected placement applies to computed and imported feature tracks. + +Select `Export image` to save a high-quality rendering of the current interactive view. *Please do not use plots generated this way for official publications; use the validated [ModDotPlot CLI](https://github.com/marbl/ModDotPlot) instead.* Depending on browser support and the selected filename extension, plots can be exported as PNG, SVG, or PDF. Image exports include the visible axes, sequence labels, feature tracks, and legend. SVG and PDF exports retain vector axes, labels, tracks, and legends around the rasterized heatmap. + +## Exporting Data to ModDotPlot CLI + +As a reminder, ***this browser is meant for exploratory analysis only!*** Once you have styled a plot to your liking, select `Export ModDotPlot command` to download files for use in the [ModDotPlot CLI](https://github.com/marbl/ModDotPlot). The browser will finish preparing detailed data for the visible view and download a ZIP archive. + +![](../web/public/tutorial_images/export_mdp.png) + +For a Self or Pairwise plot, the archive contains: + +```text +.bedpe +.config.json +``` + +The BEDPE file contains the non-empty similarity cells from the exported view. The JSON configuration records the corresponding ModDotPlot display settings and refers to the BEDPE file through its `load` field. + +Extract both files into the same directory: +```bash +unzip .zip +cd +``` + +The exported command can then be run with the official ModDotPlot CLI: +```bash +moddotplot -c .config.json -l .bedpe +``` + +![](../web/public/tutorial_images/Chr1_TRI.png) +![](../web/public/tutorial_images/Chr1_HIST.png) + +The actual exported filenames are written into the JSON metadata, so replace `` with the name of the downloaded package. + +For a Grid plot, the ZIP archive contains one BEDPE file for every selected self-comparison and every unique pairwise comparison, together with a single configuration file. Run the exported grid with: + +```bash +moddotplot --grid-only -c .config.json -l +``` diff --git a/docs/benchmarks/backend-adaptive-chm13-dense-1m-r2048.tsv b/docs/benchmarks/backend-adaptive-chm13-dense-1m-r2048.tsv new file mode 100644 index 0000000..21fa621 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-chm13-dense-1m-r2048.tsv @@ -0,0 +1,2 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 chm13_chr1_dense_1m_r2048 1048576 1048576 32 21 2048 14 8192 8.0 0.80 31.078 14.105 45.183 2947687 0 0 0 1024 0 0 888 0.00447253 0.01323364 0.05682089 0.00668400 0.01891293 0.03043052 0.00891064 0.02017534 0.74133713 0.01185450 0.02947993 0 0 0.99998474 0 true diff --git a/docs/benchmarks/backend-adaptive-chm13-dense-1m-r4096.tsv b/docs/benchmarks/backend-adaptive-chm13-dense-1m-r4096.tsv new file mode 100644 index 0000000..40d5353 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-chm13-dense-1m-r4096.tsv @@ -0,0 +1,2 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 chm13_chr1_dense_1m_r4096 1048576 1048576 32 21 4096 14 8192 8.0 0.80 100.987 17.950 118.937 11236558 0 0 0 1024 0 0 888 0.00347411 0.00906653 0.04784368 0.00506016 0.01311012 0.02234270 0.00564529 0.01432522 0.03416991 0.00956727 0.02717778 0 0 0.99967194 0 true diff --git a/docs/benchmarks/backend-adaptive-chm13-dense-1m.tsv b/docs/benchmarks/backend-adaptive-chm13-dense-1m.tsv new file mode 100644 index 0000000..42bd6b6 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-chm13-dense-1m.tsv @@ -0,0 +1,2 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 chm13_chr1_dense_1m_r1024 1048576 1048576 32 21 1024 14 8192 8.0 0.80 23.710 13.828 37.538 1612391 0 0 0 1024 0 0 888 0.00623364 0.02050163 0.04215150 0.01289777 0.02478471 0.80294140 0.01905023 0.02877117 0.80294140 0.02071125 0.03946372 0 0 1.00000000 0 false diff --git a/docs/benchmarks/backend-adaptive-chm13-full-native.tsv b/docs/benchmarks/backend-adaptive-chm13-full-native.tsv new file mode 100644 index 0000000..29b6bbe --- /dev/null +++ b/docs/benchmarks/backend-adaptive-chm13-full-native.tsv @@ -0,0 +1,2 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms correction_ms selection_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells +backend-v2-performance chm13_chr1_full 248387328 248387328 1000 21 2048 14 8192 8.0 0.80 2961.812 23029.439 2857.711 20171.724 25991.251 92914913 6 35866 11986 diff --git a/docs/benchmarks/backend-adaptive-chm13-hor.tsv b/docs/benchmarks/backend-adaptive-chm13-hor.tsv new file mode 100644 index 0000000..3f98987 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-chm13-hor.tsv @@ -0,0 +1,2 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 chm13_chr1_hor 262144 262144 32 21 2048 14 8192 8.0 0.80 15.739 19.810 35.550 4782697 32 74465 1520 1024 0 0 1024 0.00026818 0.00000000 0.02932887 0.00002103 0.00000000 0.00200009 0.00002103 0.00000000 0.00200009 0.00094822 0.00249293 0 0 0.67820740 0 true diff --git a/docs/benchmarks/backend-adaptive-chm13-mixed-1m.tsv b/docs/benchmarks/backend-adaptive-chm13-mixed-1m.tsv new file mode 100644 index 0000000..f5166ed --- /dev/null +++ b/docs/benchmarks/backend-adaptive-chm13-mixed-1m.tsv @@ -0,0 +1,2 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 chm13_chr1_mixed_1m 1048576 1048576 32 21 1024 14 8192 8.0 0.80 69.694 92.163 161.856 9073874 52 238116 1664 1024 0 0 652 0.00103335 0.00219463 0.05376283 0.00024012 0.00053950 0.01343246 0.00167698 0.00000000 0.74887782 0.14708760 0.76402481 0 0 0.98339844 0 true diff --git a/docs/benchmarks/backend-adaptive-native.tsv b/docs/benchmarks/backend-adaptive-native.tsv new file mode 100644 index 0000000..ad79392 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-native.tsv @@ -0,0 +1,361 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 identity 262144 262144 32 21 1024 14 512 8.0 0.80 21.954 17.399 39.353 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 512 12.0 0.80 21.986 17.409 39.395 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 512 16.0 0.80 21.504 17.242 38.746 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 512 24.0 0.80 22.013 16.852 38.865 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 1024 8.0 0.80 22.342 16.847 39.189 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 1024 12.0 0.80 23.135 16.682 39.816 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 1024 16.0 0.80 23.241 16.503 39.744 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 1024 24.0 0.80 22.086 17.940 40.025 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 2048 8.0 0.80 26.404 18.726 45.130 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 2048 12.0 0.80 27.548 17.679 45.227 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 2048 16.0 0.80 23.863 16.753 40.616 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 2048 24.0 0.80 23.765 16.867 40.632 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 4096 8.0 0.80 26.651 17.021 43.672 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 4096 12.0 0.80 26.449 16.586 43.035 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 4096 16.0 0.80 26.605 17.158 43.763 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 4096 24.0 0.80 26.826 17.091 43.918 3224596 0 0 0 1024 0 0 94 0.01848875 0.05732070 0.06810370 0.00150449 0.00425910 0.00590125 0.00013811 0.00095606 0.00590125 0.00015430 0.00132083 32 32 0.99969482 0 false +backend-v2 identity 262144 262144 32 21 1024 14 8192 8.0 0.80 33.488 130.246 163.734 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 8192 12.0 0.80 32.572 129.553 162.126 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 8192 16.0 0.80 32.583 132.714 165.297 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 8192 24.0 0.80 32.857 129.211 162.068 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 16384 8.0 0.80 32.478 129.420 161.898 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 16384 12.0 0.80 32.459 128.723 161.182 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 16384 16.0 0.80 32.847 129.874 162.721 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 identity 262144 262144 32 21 1024 14 16384 24.0 0.80 32.467 129.807 162.274 17904660 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.99969482 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 512 8.0 0.80 21.605 19.199 40.804 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 512 12.0 0.80 21.508 18.547 40.055 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 512 16.0 0.80 21.645 18.332 39.977 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 512 24.0 0.80 21.717 19.171 40.889 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 1024 8.0 0.80 24.083 19.967 44.050 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 1024 12.0 0.80 22.600 18.817 41.417 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 1024 16.0 0.80 22.056 18.502 40.557 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 1024 24.0 0.80 22.210 18.469 40.679 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 2048 8.0 0.80 23.614 18.673 42.287 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 2048 12.0 0.80 23.663 18.763 42.426 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 2048 16.0 0.80 23.595 18.334 41.929 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 2048 24.0 0.80 23.939 18.362 42.302 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 4096 8.0 0.80 26.635 18.666 45.300 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 4096 12.0 0.80 26.542 18.742 45.284 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 4096 16.0 0.80 26.528 18.861 45.389 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 4096 24.0 0.80 26.550 18.553 45.102 3224616 0 0 0 1024 0 0 94 0.02015411 0.05845826 0.07944413 0.00184057 0.00587549 0.00944168 0.00016896 0.00107301 0.00944168 0.00021152 0.00150742 32 32 0.99963379 0 false +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 8192 8.0 0.80 33.326 110.576 143.903 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 8192 12.0 0.80 32.434 109.466 141.900 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 8192 16.0 0.80 32.417 110.784 143.202 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 8192 24.0 0.80 32.365 111.833 144.198 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 16384 8.0 0.80 32.706 110.598 143.304 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 16384 12.0 0.80 32.738 111.709 144.447 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 16384 16.0 0.80 32.523 112.429 144.952 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_01pct 262144 262144 32 21 1024 14 16384 24.0 0.80 32.514 109.517 142.031 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.99963379 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 512 8.0 0.80 21.884 19.279 41.163 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 512 12.0 0.80 21.945 18.721 40.666 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 512 16.0 0.80 21.724 18.492 40.215 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 512 24.0 0.80 21.448 18.881 40.330 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 1024 8.0 0.80 22.189 18.880 41.069 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 1024 12.0 0.80 22.898 18.738 41.636 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 1024 16.0 0.80 22.178 18.708 40.886 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 1024 24.0 0.80 22.159 18.549 40.708 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 2048 8.0 0.80 23.575 18.446 42.021 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 2048 12.0 0.80 23.775 18.584 42.359 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 2048 16.0 0.80 23.770 18.659 42.429 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 2048 24.0 0.80 23.771 18.356 42.127 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 4096 8.0 0.80 26.595 18.898 45.493 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 4096 12.0 0.80 26.844 18.981 45.824 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 4096 16.0 0.80 26.846 19.358 46.204 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 4096 24.0 0.80 26.601 18.633 45.235 3224616 0 0 0 1024 0 0 94 0.01730150 0.04003666 0.07610008 0.00342390 0.00951323 0.01098410 0.00031430 0.00290455 0.01098410 0.00032638 0.00265582 32 32 0.99974060 0 false +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 8192 8.0 0.80 32.694 78.943 111.637 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 8192 12.0 0.80 32.563 80.089 112.652 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 8192 16.0 0.80 32.448 81.790 114.238 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 8192 24.0 0.80 32.443 80.967 113.410 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 16384 8.0 0.80 32.552 81.293 113.845 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 16384 12.0 0.80 32.410 81.237 113.646 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 16384 16.0 0.80 32.420 79.774 112.194 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_05pct 262144 262144 32 21 1024 14 16384 24.0 0.80 32.370 80.059 112.429 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.99974060 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 512 8.0 0.80 21.735 18.974 40.709 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 512 12.0 0.80 21.583 18.678 40.261 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 512 16.0 0.80 21.667 18.555 40.222 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 512 24.0 0.80 21.611 18.472 40.083 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 1024 8.0 0.80 22.027 18.891 40.918 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 1024 12.0 0.80 22.238 18.754 40.991 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 1024 16.0 0.80 22.337 18.240 40.578 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 1024 24.0 0.80 22.367 18.844 41.211 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 2048 8.0 0.80 23.878 18.444 42.322 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 2048 12.0 0.80 23.724 18.318 42.042 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 2048 16.0 0.80 23.514 18.351 41.865 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 2048 24.0 0.80 23.610 18.747 42.357 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 4096 8.0 0.80 26.598 18.111 44.709 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 4096 12.0 0.80 26.597 18.254 44.851 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 4096 16.0 0.80 26.714 18.369 45.082 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 4096 24.0 0.80 26.605 18.490 45.095 3224616 0 0 0 1024 0 0 94 0.01052168 0.02756640 0.03367192 0.00573807 0.01477841 0.01688296 0.00052674 0.00402513 0.01688296 0.00073435 0.00696861 32 32 0.99961853 0 false +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 8192 8.0 0.80 32.596 62.872 95.468 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 8192 12.0 0.80 32.541 63.052 95.593 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 8192 16.0 0.80 32.596 62.241 94.837 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 8192 24.0 0.80 32.530 69.008 101.539 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 16384 8.0 0.80 32.484 62.716 95.200 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 16384 12.0 0.80 32.574 62.121 94.695 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 16384 16.0 0.80 32.471 62.973 95.444 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 substitution_10pct 262144 262144 32 21 1024 14 16384 24.0 0.80 32.590 62.665 95.255 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.99961853 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 512 8.0 0.80 21.533 19.297 40.830 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 512 12.0 0.80 21.718 18.849 40.567 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 512 16.0 0.80 21.479 18.543 40.022 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 512 24.0 0.80 21.493 18.617 40.110 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 1024 8.0 0.80 22.500 18.423 40.923 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 1024 12.0 0.80 22.232 18.623 40.855 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 1024 16.0 0.80 22.116 18.449 40.565 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 1024 24.0 0.80 22.020 18.401 40.421 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 2048 8.0 0.80 23.505 18.347 41.852 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 2048 12.0 0.80 23.585 18.490 42.076 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 2048 16.0 0.80 23.485 18.361 41.846 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 2048 24.0 0.80 23.847 18.298 42.145 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 4096 8.0 0.80 26.615 18.577 45.193 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 4096 12.0 0.80 26.586 18.579 45.164 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 4096 16.0 0.80 26.584 18.762 45.345 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 4096 24.0 0.80 26.587 18.680 45.267 3224602 0 0 0 1024 0 0 86 0.00524332 0.01388268 0.02066798 0.01370600 0.02899229 0.04339278 0.00125724 0.00970653 0.04339278 0.00336415 0.01336639 32 32 0.99964905 0 false +backend-v2 indel_05pct 262144 262144 32 21 1024 14 8192 8.0 0.80 32.446 54.779 87.226 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 8192 12.0 0.80 32.637 55.972 88.609 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 8192 16.0 0.80 32.523 56.191 88.714 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 8192 24.0 0.80 32.318 55.578 87.896 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 16384 8.0 0.80 32.452 55.429 87.881 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 16384 12.0 0.80 34.047 56.091 90.138 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 16384 16.0 0.80 32.402 55.064 87.466 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 indel_05pct 262144 262144 32 21 1024 14 16384 24.0 0.80 32.507 56.521 89.028 17904666 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.99964905 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 512 8.0 0.80 21.581 18.441 40.022 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 512 12.0 0.80 21.659 18.350 40.009 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 512 16.0 0.80 21.443 17.967 39.410 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 512 24.0 0.80 21.551 17.898 39.450 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 1024 8.0 0.80 22.288 18.011 40.298 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 1024 12.0 0.80 22.531 17.811 40.342 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 1024 16.0 0.80 22.127 18.314 40.441 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 1024 24.0 0.80 22.126 17.898 40.024 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 2048 8.0 0.80 23.656 17.855 41.511 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 2048 12.0 0.80 23.778 18.016 41.794 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 2048 16.0 0.80 23.779 18.156 41.935 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 2048 24.0 0.80 23.593 17.996 41.590 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 4096 8.0 0.80 26.717 18.269 44.986 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 4096 12.0 0.80 26.670 18.072 44.743 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 4096 16.0 0.80 26.729 18.149 44.879 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 4096 24.0 0.80 26.689 18.204 44.893 3224600 0 0 0 1024 0 0 95 0.01650419 0.04965499 0.07911564 0.00149097 0.00509516 0.00810447 0.00013832 0.00087502 0.00810447 0.00015429 0.00099629 32 32 0.99971008 0 false +backend-v2 offset_1kb 262144 262144 32 21 1024 14 8192 8.0 0.80 32.437 129.149 161.587 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 8192 12.0 0.80 32.522 130.454 162.976 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 8192 16.0 0.80 32.538 130.530 163.069 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 8192 24.0 0.80 32.691 131.695 164.385 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 16384 8.0 0.80 32.511 129.997 162.508 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 16384 12.0 0.80 32.604 130.004 162.607 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 16384 16.0 0.80 32.488 130.653 163.142 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 offset_1kb 262144 262144 32 21 1024 14 16384 24.0 0.80 32.688 130.018 162.706 17904664 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 512 8.0 0.80 21.612 17.574 39.186 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 512 12.0 0.80 21.611 17.435 39.046 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 512 16.0 0.80 21.469 17.003 38.471 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 512 24.0 0.80 21.544 16.914 38.458 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 1024 8.0 0.80 22.268 17.146 39.414 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 1024 12.0 0.80 22.334 17.249 39.583 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 1024 16.0 0.80 22.414 17.017 39.430 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 1024 24.0 0.80 22.307 16.916 39.223 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 2048 8.0 0.80 23.824 17.542 41.366 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 2048 12.0 0.80 23.660 16.852 40.512 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 2048 16.0 0.80 23.649 16.825 40.473 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 2048 24.0 0.80 23.676 17.097 40.773 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 4096 8.0 0.80 26.539 17.510 44.049 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 4096 12.0 0.80 26.890 16.825 43.715 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 4096 16.0 0.80 26.840 17.514 44.354 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 4096 24.0 0.80 26.842 17.118 43.960 3224614 0 0 0 1024 0 0 98 0.01834391 0.05732070 0.06842178 0.00151611 0.00430277 0.00595318 0.00014510 0.00109346 0.00595318 0.00016420 0.00137443 0 0 0.99971008 0 false +backend-v2 central_inversion 262144 262144 32 21 1024 14 8192 8.0 0.80 32.538 129.859 162.397 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 8192 12.0 0.80 32.587 129.193 161.780 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 8192 16.0 0.80 32.675 129.390 162.065 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 8192 24.0 0.80 32.602 129.082 161.684 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 16384 8.0 0.80 32.592 129.032 161.623 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 16384 12.0 0.80 32.683 130.256 162.939 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 16384 16.0 0.80 32.492 130.448 162.939 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 central_inversion 262144 262144 32 21 1024 14 16384 24.0 0.80 32.571 129.199 161.770 17904678 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.99971008 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 512 8.0 0.80 21.645 17.630 39.275 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 512 12.0 0.80 21.669 17.335 39.004 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 512 16.0 0.80 21.732 17.271 39.003 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 512 24.0 0.80 21.860 17.255 39.115 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 1024 8.0 0.80 22.300 16.967 39.268 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 1024 12.0 0.80 22.422 17.117 39.538 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 1024 16.0 0.80 22.268 17.217 39.485 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 1024 24.0 0.80 22.372 16.912 39.284 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 2048 8.0 0.80 23.738 17.094 40.832 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 2048 12.0 0.80 23.719 17.445 41.164 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 2048 16.0 0.80 23.874 17.360 41.234 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 2048 24.0 0.80 23.922 17.040 40.962 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 4096 8.0 0.80 26.759 17.311 44.069 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 4096 12.0 0.80 26.790 17.005 43.795 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 4096 16.0 0.80 26.847 16.545 43.393 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 4096 24.0 0.80 26.756 16.816 43.572 3224616 0 0 0 1024 0 0 94 0.01835049 0.05586118 0.06842178 0.00150083 0.00446920 0.00595318 0.00013777 0.00090130 0.00595318 0.00015256 0.00129319 2 2 0.99969482 0 false +backend-v2 reverse_complement 262144 262144 32 21 1024 14 8192 8.0 0.80 32.615 129.239 161.854 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 8192 12.0 0.80 32.857 129.573 162.430 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 8192 16.0 0.80 32.686 128.157 160.842 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 8192 24.0 0.80 32.577 129.930 162.507 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 16384 8.0 0.80 32.635 129.295 161.931 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 16384 12.0 0.80 32.636 127.834 160.470 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 16384 16.0 0.80 32.565 128.726 161.291 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 reverse_complement 262144 262144 32 21 1024 14 16384 24.0 0.80 32.778 129.613 162.391 17904680 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.99969482 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 512 8.0 0.80 11.714 12.063 23.776 3252832 32 1328 1024 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 512 12.0 0.80 11.966 12.172 24.138 3252832 32 1328 1024 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 512 16.0 0.80 11.902 12.059 23.961 3252832 32 1328 1024 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 512 24.0 0.80 11.770 12.042 23.812 3252832 32 1328 1024 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 1024 8.0 0.80 12.308 16.660 28.969 3754592 60 22039 1920 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 1024 12.0 0.80 12.338 16.570 28.908 3754592 60 22039 1920 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 1024 16.0 0.80 12.354 16.590 28.944 3754592 60 22039 1920 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 1024 24.0 0.80 12.217 16.588 28.806 3754592 60 22039 1920 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 2048 8.0 0.80 12.343 16.622 28.965 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 2048 12.0 0.80 12.234 16.725 28.959 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 2048 16.0 0.80 12.356 16.666 29.022 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 2048 24.0 0.80 12.346 16.672 29.018 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 4096 8.0 0.80 12.283 16.700 28.983 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 4096 12.0 0.80 12.132 16.585 28.717 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 4096 16.0 0.80 12.230 16.748 28.978 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 4096 24.0 0.80 12.275 16.639 28.914 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 8192 8.0 0.80 12.280 16.653 28.933 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 8192 12.0 0.80 12.233 16.578 28.811 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 8192 16.0 0.80 12.257 16.635 28.892 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 8192 24.0 0.80 12.269 16.665 28.934 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 16384 8.0 0.80 12.262 16.618 28.879 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 16384 12.0 0.80 12.330 16.724 29.054 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 16384 16.0 0.80 12.149 16.600 28.749 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 low_complexity 262144 262144 32 21 1024 14 16384 24.0 0.80 12.260 16.632 28.893 3869280 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.28268433 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 512 8.0 0.80 8.416 15.465 23.881 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 512 12.0 0.80 7.969 15.365 23.334 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 512 16.0 0.80 7.975 15.503 23.478 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 512 24.0 0.80 8.178 15.383 23.560 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 1024 8.0 0.80 8.140 15.394 23.534 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 1024 12.0 0.80 8.077 15.414 23.491 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 1024 16.0 0.80 7.999 15.363 23.362 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 1024 24.0 0.80 7.992 15.418 23.410 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 2048 8.0 0.80 8.129 15.470 23.599 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 2048 12.0 0.80 8.058 15.534 23.592 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 2048 16.0 0.80 7.954 15.453 23.407 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 2048 24.0 0.80 8.110 15.465 23.575 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 4096 8.0 0.80 8.207 15.385 23.593 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 4096 12.0 0.80 8.224 15.500 23.724 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 4096 16.0 0.80 8.016 15.491 23.506 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 4096 24.0 0.80 7.999 15.470 23.469 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 8192 8.0 0.80 8.185 15.338 23.523 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 8192 12.0 0.80 8.219 15.475 23.694 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 8192 16.0 0.80 7.924 15.496 23.420 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 8192 24.0 0.80 7.905 15.439 23.343 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 16384 8.0 0.80 7.914 15.414 23.328 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 16384 12.0 0.80 8.197 15.442 23.639 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 16384 16.0 0.80 8.096 15.576 23.672 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 tandem_repeat 262144 262144 32 21 1024 14 16384 24.0 0.80 7.972 15.515 23.487 3253726 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.02761841 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 512 8.0 0.80 21.600 18.887 40.486 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 512 12.0 0.80 21.604 18.950 40.554 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 512 16.0 0.80 21.586 18.468 40.055 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 512 24.0 0.80 21.579 18.595 40.174 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 1024 8.0 0.80 22.263 18.397 40.660 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 1024 12.0 0.80 22.359 18.392 40.750 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 1024 16.0 0.80 22.369 18.912 41.281 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 1024 24.0 0.80 22.417 19.164 41.581 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 2048 8.0 0.80 23.770 18.728 42.498 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 2048 12.0 0.80 23.676 18.812 42.488 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 2048 16.0 0.80 23.634 18.802 42.436 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 2048 24.0 0.80 23.645 18.693 42.339 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 4096 8.0 0.80 26.653 18.816 45.469 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 4096 12.0 0.80 26.780 18.572 45.352 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 4096 16.0 0.80 26.841 18.659 45.500 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 4096 24.0 0.80 26.764 18.848 45.612 3224598 0 0 0 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 8192 8.0 0.80 32.532 53.912 86.443 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 8192 12.0 0.80 32.601 53.645 86.246 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 8192 16.0 0.80 32.549 54.261 86.810 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 8192 24.0 0.80 32.529 54.044 86.573 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 16384 8.0 0.80 32.560 54.006 86.566 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 16384 12.0 0.80 32.573 54.068 86.641 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 16384 16.0 0.80 32.540 54.392 86.932 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 unrelated 262144 262144 32 21 1024 14 16384 24.0 0.80 32.797 54.815 87.612 17904662 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.99955750 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 512 8.0 0.80 20.915 16.634 37.550 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 512 12.0 0.80 20.986 16.321 37.307 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 512 16.0 0.80 21.040 15.765 36.805 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 512 24.0 0.80 21.285 15.808 37.093 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 1024 8.0 0.80 21.660 16.037 37.697 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 1024 12.0 0.80 21.516 16.409 37.925 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 1024 16.0 0.80 21.729 15.833 37.562 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 1024 24.0 0.80 21.502 17.241 38.742 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 2048 8.0 0.80 23.028 16.402 39.430 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 2048 12.0 0.80 22.975 16.461 39.436 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 2048 16.0 0.80 22.903 15.922 38.824 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 2048 24.0 0.80 23.012 15.916 38.928 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 4096 8.0 0.80 25.943 16.466 42.409 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 4096 12.0 0.80 26.024 16.187 42.211 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 4096 16.0 0.80 26.007 16.150 42.157 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 4096 24.0 0.80 25.950 16.667 42.617 3224612 0 0 0 1024 0 0 94 0.01911311 0.04538929 0.06407958 0.00154962 0.00399050 0.00463812 0.00014225 0.00124271 0.00463812 0.00014108 0.00105474 32 32 0.99893188 0 false +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 8192 8.0 0.80 28.829 95.912 124.741 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 8192 12.0 0.80 28.750 96.054 124.804 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 8192 16.0 0.80 28.962 95.807 124.769 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 8192 24.0 0.80 28.807 97.997 126.803 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 16384 8.0 0.80 28.850 97.132 125.982 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 16384 12.0 0.80 28.799 96.366 125.165 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 16384 16.0 0.80 28.612 94.768 123.380 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 1024 14 16384 24.0 0.80 28.829 94.646 123.475 10564644 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.99893188 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 512 8.0 0.80 4.866 11.296 16.162 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 512 12.0 0.80 4.633 11.155 15.788 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 512 16.0 0.80 4.647 11.153 15.801 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 512 24.0 0.80 5.687 12.638 18.324 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 1024 8.0 0.80 4.647 11.195 15.842 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 1024 12.0 0.80 4.748 11.184 15.932 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 1024 16.0 0.80 4.713 11.211 15.924 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 1024 24.0 0.80 4.710 11.156 15.866 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 2048 8.0 0.80 4.621 11.195 15.816 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 2048 12.0 0.80 4.556 11.324 15.880 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 2048 16.0 0.80 4.564 11.124 15.688 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 2048 24.0 0.80 4.550 11.123 15.673 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 4096 8.0 0.80 4.553 11.238 15.791 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 4096 12.0 0.80 4.582 11.169 15.752 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 4096 16.0 0.80 4.641 11.136 15.777 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 4096 24.0 0.80 4.733 11.193 15.926 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 8192 8.0 0.80 4.619 11.133 15.751 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 8192 12.0 0.80 4.452 11.210 15.662 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 8192 16.0 0.80 4.495 11.109 15.604 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 8192 24.0 0.80 4.533 11.157 15.691 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 16384 8.0 0.80 4.628 11.174 15.802 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 16384 12.0 0.80 4.667 11.162 15.830 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 16384 16.0 0.80 4.735 11.145 15.880 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 1024 14 16384 24.0 0.80 4.675 11.104 15.779 3453996 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.19064331 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 512 8.0 0.80 18.081 19.935 38.016 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 512 12.0 0.80 18.141 19.809 37.950 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 512 16.0 0.80 18.099 19.823 37.922 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 512 24.0 0.80 18.170 19.857 38.028 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 1024 8.0 0.80 18.797 19.710 38.507 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 1024 12.0 0.80 18.779 19.977 38.757 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 1024 16.0 0.80 18.815 20.030 38.845 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 1024 24.0 0.80 18.735 19.957 38.692 3252394 16 1164 512 1024 0 0 784 0.00246983 0.01418235 0.04078311 0.00263476 0.01570893 0.02789988 0.11845375 0.70893629 0.78271102 0.05808352 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 2048 8.0 0.80 19.902 23.901 43.803 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 2048 12.0 0.80 19.936 23.804 43.740 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 2048 16.0 0.80 19.873 23.761 43.634 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 2048 24.0 0.80 19.903 23.854 43.757 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 4096 8.0 0.80 21.468 24.098 45.567 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 4096 12.0 0.80 21.421 23.843 45.264 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 4096 16.0 0.80 21.588 23.620 45.208 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 4096 24.0 0.80 21.460 24.060 45.520 3711146 32 25838 1024 1024 0 0 784 0.00060763 0.00000000 0.04078311 0.00004774 0.00000000 0.00301890 0.11647306 0.70893629 0.78271102 0.05815222 0.74710669 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 8192 8.0 0.80 22.015 37.112 59.127 7381162 64 161504 2032 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 8192 12.0 0.80 22.079 36.885 58.964 7381162 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 8192 16.0 0.80 22.100 36.826 58.926 7381162 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 8192 24.0 0.80 22.189 36.966 59.156 7381162 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 16384 8.0 0.80 21.978 36.639 58.617 7381162 64 161504 2032 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 16384 12.0 0.80 22.008 36.450 58.459 7381162 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 16384 16.0 0.80 22.095 36.795 58.890 7381162 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 alternating_context 262144 262144 32 21 1024 14 16384 24.0 0.80 21.891 37.337 59.228 7381162 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.70285034 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 512 8.0 0.80 18.911 13.769 32.680 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 512 12.0 0.80 19.078 13.060 32.138 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 512 16.0 0.80 18.995 12.553 31.547 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 512 24.0 0.80 18.884 12.734 31.618 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 1024 8.0 0.80 19.510 12.870 32.379 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 1024 12.0 0.80 19.673 12.893 32.566 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 1024 16.0 0.80 19.451 12.552 32.003 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 1024 24.0 0.80 19.509 12.510 32.019 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 2048 8.0 0.80 20.894 12.496 33.390 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 2048 12.0 0.80 20.860 12.746 33.606 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 2048 16.0 0.80 20.873 12.989 33.862 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 2048 24.0 0.80 21.578 12.462 34.040 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 4096 8.0 0.80 23.667 12.860 36.528 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 4096 12.0 0.80 23.428 12.662 36.090 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 4096 16.0 0.80 23.524 12.998 36.522 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 4096 24.0 0.80 23.429 12.898 36.328 3224624 0 0 0 1024 0 0 71 0.01867859 0.05995171 0.06810370 0.00151618 0.00523151 0.00590125 0.00014017 0.00084935 0.00590125 0.00017439 0.00142455 24 24 0.87469482 0 false +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 8192 8.0 0.80 28.905 96.578 125.483 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 8192 12.0 0.80 28.595 95.644 124.238 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 8192 16.0 0.80 28.557 96.262 124.819 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 8192 24.0 0.80 28.461 96.665 125.125 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 16384 8.0 0.80 28.402 97.060 125.462 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 16384 12.0 0.80 28.431 95.465 123.897 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 16384 16.0 0.80 28.468 95.255 123.723 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 1024 14 16384 24.0 0.80 28.464 95.734 124.198 16069680 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87469482 0 true diff --git a/docs/benchmarks/backend-adaptive-smoke.tsv b/docs/benchmarks/backend-adaptive-smoke.tsv new file mode 100644 index 0000000..bacd3da --- /dev/null +++ b/docs/benchmarks/backend-adaptive-smoke.tsv @@ -0,0 +1,16 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 identity 32768 32768 8 21 256 14 4096 12.0 0.80 3.786 9.057 12.843 2140436 16 65496 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061600 0.00444845 8 8 1.00000000 0 true +backend-v2 substitution_01pct 32768 32768 8 21 256 14 4096 12.0 0.80 3.464 9.098 12.562 2140456 16 65496 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00091954 0.00425159 8 8 1.00000000 0 true +backend-v2 substitution_05pct 32768 32768 8 21 256 14 4096 12.0 0.80 3.530 8.891 12.422 2140456 16 65496 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00145549 0.00759879 8 8 1.00000000 0 true +backend-v2 substitution_10pct 32768 32768 8 21 256 14 4096 12.0 0.80 3.540 8.895 12.435 2140456 16 65496 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00323099 0.01384519 8 8 1.00000000 0 true +backend-v2 indel_05pct 32768 32768 8 21 256 14 4096 12.0 0.80 3.464 8.771 12.235 2140442 16 65496 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00756169 0.03614775 8 8 1.00000000 0 true +backend-v2 offset_1kb 32768 32768 8 21 256 14 4096 12.0 0.80 3.483 9.398 12.881 2140440 16 65496 128 64 0 0 23 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00093350 0.00531350 8 8 1.00000000 0 true +backend-v2 central_inversion 32768 32768 8 21 256 14 4096 12.0 0.80 3.567 9.266 12.832 2140454 16 65496 128 64 0 0 26 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.01141145 0.00565316 0 0 1.00000000 0 true +backend-v2 reverse_complement 32768 32768 8 21 256 14 4096 12.0 0.80 3.487 9.024 12.511 2140456 16 65496 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00057929 0.00348358 2 2 1.00000000 0 true +backend-v2 low_complexity 32768 32768 8 21 256 14 4096 12.0 0.80 1.451 1.982 3.433 380480 16 3212 128 64 0 0 54 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.08680465 0.85955893 7 7 0.41430664 0 true +backend-v2 tandem_repeat 32768 32768 8 21 256 14 4096 12.0 0.80 1.099 1.625 2.725 313054 16 466 128 64 0 0 64 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.10937500 0 true +backend-v2 unrelated 32768 32768 8 21 256 14 4096 12.0 0.80 3.440 8.597 12.037 2140438 16 65496 128 64 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 1.00000000 0 true +backend-v2 ambiguity_blocks 32768 32768 8 21 256 14 4096 12.0 0.80 3.026 7.800 10.826 1222948 16 56984 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00083469 0.00526210 8 8 1.00000000 0 true +backend-v2 sparse_valid_islands 32768 32768 8 21 256 14 4096 12.0 0.80 0.517 1.525 2.042 334124 16 1728 128 64 0 0 22 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 8 8 0.34130859 0 true +backend-v2 alternating_context 32768 32768 8 21 256 14 4096 12.0 0.80 2.164 3.809 5.974 864330 16 20041 128 64 0 0 47 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.25953633 0.89407086 8 8 0.59863281 0 true +backend-v2 unequal_length_padding 32768 24576 8 21 256 14 4096 12.0 0.80 3.033 7.363 10.396 1911088 14 57304 96 64 0 0 17 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00073087 0.00444845 6 6 0.87500000 0 true diff --git a/docs/benchmarks/backend-adaptive-tier-r2048.tsv b/docs/benchmarks/backend-adaptive-tier-r2048.tsv new file mode 100644 index 0000000..11f4f46 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-tier-r2048.tsv @@ -0,0 +1,16 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 identity 262144 262144 32 21 2048 14 8192 8.0 0.80 47.023 132.910 179.934 20575252 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.98252869 0 true +backend-v2 substitution_01pct 262144 262144 32 21 2048 14 8192 8.0 0.80 46.281 110.240 156.521 20575272 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.98243713 0 true +backend-v2 substitution_05pct 262144 262144 32 21 2048 14 8192 8.0 0.80 45.064 80.775 125.839 20575272 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.98163605 0 true +backend-v2 substitution_10pct 262144 262144 32 21 2048 14 8192 8.0 0.80 45.475 63.228 108.703 20575272 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.98207855 0 true +backend-v2 indel_05pct 262144 262144 32 21 2048 14 8192 8.0 0.80 45.154 56.014 101.168 20575258 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.98190308 0 true +backend-v2 offset_1kb 262144 262144 32 21 2048 14 8192 8.0 0.80 45.268 131.634 176.902 20575256 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.98239136 0 true +backend-v2 central_inversion 262144 262144 32 21 2048 14 8192 8.0 0.80 45.257 132.274 177.531 20575270 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.98257446 0 true +backend-v2 reverse_complement 262144 262144 32 21 2048 14 8192 8.0 0.80 45.254 139.057 184.310 20575272 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.98261261 0 true +backend-v2 low_complexity 262144 262144 32 21 2048 14 8192 8.0 0.80 13.569 17.083 30.651 6539872 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.16634369 0 true +backend-v2 tandem_repeat 262144 262144 32 21 2048 14 8192 8.0 0.80 8.514 15.898 24.411 5924318 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.01380920 0 true +backend-v2 unrelated 262144 262144 32 21 2048 14 8192 8.0 0.80 45.213 54.924 100.137 20575254 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.98204041 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 2048 14 8192 8.0 0.80 41.840 99.185 141.025 13235236 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.96990967 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 2048 14 8192 8.0 0.80 5.163 11.686 16.849 6124588 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.10018921 0 true +backend-v2 alternating_context 262144 262144 32 21 2048 14 8192 8.0 0.80 29.840 38.019 67.859 10051754 64 161504 1488 1024 0 0 784 0.00006338 0.00000000 0.01683045 0.00000527 0.00000000 0.00148497 0.00770807 0.00566153 0.71869831 0.09434016 0.71416435 32 32 0.57888794 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 2048 14 8192 8.0 0.80 40.242 97.847 138.089 18740272 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.85975647 0 true diff --git a/docs/benchmarks/backend-adaptive-tier-r4096.tsv b/docs/benchmarks/backend-adaptive-tier-r4096.tsv new file mode 100644 index 0000000..31f0763 --- /dev/null +++ b/docs/benchmarks/backend-adaptive-tier-r4096.tsv @@ -0,0 +1,16 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 identity 262144 262144 32 21 4096 14 8192 16.0 0.80 70.136 85.916 156.052 25916436 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 0.86451721 0 true +backend-v2 substitution_01pct 262144 262144 32 21 4096 14 8192 16.0 0.80 70.123 85.843 155.966 25916456 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 0.86512756 0 true +backend-v2 substitution_05pct 262144 262144 32 21 4096 14 8192 16.0 0.80 68.359 83.657 152.016 25916456 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 0.86423492 0 true +backend-v2 substitution_10pct 262144 262144 32 21 4096 14 8192 16.0 0.80 68.230 77.309 145.539 25916456 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 0.86423111 0 true +backend-v2 indel_05pct 262144 262144 32 21 4096 14 8192 16.0 0.80 68.438 74.122 142.560 25916442 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 0.86439133 0 true +backend-v2 offset_1kb 262144 262144 32 21 4096 14 8192 16.0 0.80 68.112 86.436 154.548 25916440 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 0.86426163 0 true +backend-v2 central_inversion 262144 262144 32 21 4096 14 8192 16.0 0.80 68.517 87.051 155.568 25916454 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 0.86457443 0 true +backend-v2 reverse_complement 262144 262144 32 21 4096 14 8192 16.0 0.80 68.202 85.303 153.504 25916456 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 0.86457062 0 true +backend-v2 low_complexity 262144 262144 32 21 4096 14 8192 16.0 0.80 14.869 13.637 28.506 11881056 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.09045792 0 true +backend-v2 tandem_repeat 262144 262144 32 21 4096 14 8192 16.0 0.80 9.104 10.800 19.904 11265502 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.00690460 0 true +backend-v2 unrelated 262144 262144 32 21 4096 14 8192 16.0 0.80 68.200 72.284 140.484 25916438 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.86434555 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 4096 14 8192 16.0 0.80 63.564 71.817 135.381 18576420 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 0.82495880 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 4096 14 8192 16.0 0.80 6.024 10.782 16.806 11465772 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.05133057 0 true +backend-v2 alternating_context 262144 262144 32 21 4096 14 8192 16.0 0.80 39.413 32.848 72.261 15392938 64 161504 1520 1024 0 0 784 0.00006150 0.00000000 0.01683938 0.00000507 0.00000000 0.00135560 0.00062790 0.00065285 0.03856315 0.09558780 0.70891989 32 32 0.40557861 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 4096 14 8192 16.0 0.80 59.834 67.200 127.034 24081456 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.75695419 0 true diff --git a/docs/benchmarks/backend-adaptive-tier-r512.tsv b/docs/benchmarks/backend-adaptive-tier-r512.tsv new file mode 100644 index 0000000..2b56ffa --- /dev/null +++ b/docs/benchmarks/backend-adaptive-tier-r512.tsv @@ -0,0 +1,16 @@ +schema fixture x_bases y_bases resolution k registers b_bits sparse_cap min_expected_matches ani_floor build_ms matrix_median_ms first_view_ms bytes retained_sparse_cores retained_sparse_hashes corrected_directed_cells cells false_missing false_present relevant_cells relevant_mean_containment_error relevant_p95_containment_error relevant_max_containment_error relevant_mean_ani_error relevant_p95_ani_error relevant_max_ani_error all_mean_ani_error all_p95_ani_error all_max_ani_error mashmap_mean_ani_delta mashmap_p95_ani_delta direction_correct_cells direction_tested_cells mean_core_occupancy fingerprint32_collisions passed +backend-v2 identity 262144 262144 32 21 512 14 8192 8.0 0.80 27.610 128.678 156.288 16569364 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009423 0.00034866 32 32 1.00000000 0 true +backend-v2 substitution_01pct 262144 262144 32 21 512 14 8192 8.0 0.80 26.729 110.754 137.482 16569384 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00013753 0.00089027 32 32 1.00000000 0 true +backend-v2 substitution_05pct 262144 262144 32 21 512 14 8192 8.0 0.80 25.944 81.579 107.523 16569384 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00025720 0.00246535 32 32 1.00000000 0 true +backend-v2 substitution_10pct 262144 262144 32 21 512 14 8192 8.0 0.80 25.890 62.972 88.862 16569384 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00061101 0.00523025 32 32 1.00000000 0 true +backend-v2 indel_05pct 262144 262144 32 21 512 14 8192 8.0 0.80 25.906 55.727 81.633 16569370 64 524247 2048 1024 0 0 86 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00293850 0.01221082 32 32 1.00000000 0 true +backend-v2 offset_1kb 262144 262144 32 21 512 14 8192 8.0 0.80 26.008 132.537 158.545 16569368 64 524248 2048 1024 0 0 95 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00011464 0.00039909 32 32 1.00000000 0 true +backend-v2 central_inversion 262144 262144 32 21 512 14 8192 8.0 0.80 26.966 129.551 156.517 16569382 64 524248 2048 1024 0 0 98 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010314 0.00053746 0 0 1.00000000 0 true +backend-v2 reverse_complement 262144 262144 32 21 512 14 8192 8.0 0.80 25.826 128.228 154.054 16569384 64 524248 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009468 0.00044439 2 2 1.00000000 0 true +backend-v2 low_complexity 262144 262144 32 21 512 14 8192 8.0 0.80 10.905 16.238 27.143 2533984 64 26174 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00348331 0.00818059 32 32 0.42416382 0 true +backend-v2 tandem_repeat 262144 262144 32 21 512 14 8192 8.0 0.80 7.877 15.006 22.883 1918430 64 1810 2048 1024 0 0 1024 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0.05520630 0 true +backend-v2 unrelated 262144 262144 32 21 512 14 8192 8.0 0.80 25.966 54.235 80.201 16569366 64 524248 2048 1024 0 0 0 NaN NaN NaN NaN NaN NaN 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 1.00000000 0 true +backend-v2 ambiguity_blocks 262144 262144 32 21 512 14 8192 8.0 0.80 22.193 95.761 117.953 9229348 64 456152 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00009571 0.00046294 32 32 1.00000000 0 true +backend-v2 sparse_valid_islands 262144 262144 32 21 512 14 8192 8.0 0.80 4.205 10.768 14.973 2118700 64 13824 2048 1024 0 0 94 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 32 32 0.34741211 0 true +backend-v2 alternating_context 262144 262144 32 21 512 14 8192 8.0 0.80 17.155 36.555 53.710 6045866 64 161504 2048 1024 0 0 784 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.09551272 0.70891989 32 32 0.76989746 0 true +backend-v2 unequal_length_padding 262144 196608 32 21 512 14 8192 8.0 0.80 22.700 96.705 119.405 14734384 56 458712 1536 1024 0 0 71 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00010560 0.00074946 24 24 0.87500000 0 true diff --git a/docs/benchmarks/backend-chm13-chr1-hor.tsv b/docs/benchmarks/backend-chm13-chr1-hor.tsv new file mode 100644 index 0000000..b4faa34 --- /dev/null +++ b/docs/benchmarks/backend-chm13-chr1-hor.tsv @@ -0,0 +1,13 @@ +schema fixture hash x_bases y_bases resolution k registers b_bits hll_precision hash_mkmer_s hash_checksum build_ms matrix_median_ms bytes mean_core_occupancy fingerprint32_collisions cells false_missing false_present mean_abs_error p50_abs_error p95_abs_error max_abs_error max_overestimate worst_column worst_row worst_estimate worst_exact mashmap_mean_abs_error mashmap_p95_abs_error direction_correct_cells direction_tested_cells passed +backend-v1 chm13_chr1_hor packed_fmix64 262144 262144 32 21 512 14 12 242.665 cfc3cf955812f1da 12.993 0.251 1886418 0.98638916 0 1024 0 0 0.00119764 0.00102109 0.00312075 0.00469261 0.00469261 18 10 0.98080000 0.97610739 0.00139402 0.00371376 0 0 true +backend-v1 chm13_chr1_hor packed_fmix64 262144 262144 32 21 1024 14 12 242.665 cfc3cf955812f1da 18.404 0.619 3221714 0.89105225 0 1024 0 0 0.00085709 0.00073211 0.00207705 0.00409261 0.00409261 18 10 0.98020000 0.97610739 0.00114741 0.00306969 0 0 true +backend-v1 chm13_chr1_hor packed_fmix64 262144 262144 32 21 2048 14 12 242.665 cfc3cf955812f1da 25.665 1.002 5892306 0.67616272 0 1024 0 0 0.00064145 0.00050000 0.00165452 0.00249161 0.00249161 24 10 0.97710000 0.97460839 0.00117215 0.00317333 0 0 true +backend-v1 chm13_chr1_hor packed_fmix64 262144 262144 32 21 4096 14 12 242.665 cfc3cf955812f1da 32.303 2.241 11233490 0.42920685 0 1024 0 0 0.00046937 0.00037722 0.00126106 0.00191680 0.00191680 20 4 0.98060000 0.97868320 0.00112614 0.00305205 0 0 true +backend-v1 chm13_chr1_hor nthash2 262144 262144 32 21 512 14 12 227.464 442c38caea2b506e 12.543 0.234 1886418 0.98632812 0 1024 0 0 0.00128022 0.00098335 0.00370439 0.00564400 0.00486937 27 14 0.97190000 0.97754400 0.00162089 0.00452960 0 0 true +backend-v1 chm13_chr1_hor nthash2 262144 262144 32 21 1024 14 12 227.464 442c38caea2b506e 18.172 0.616 3221714 0.89465332 0 1024 0 0 0.00090382 0.00072807 0.00238959 0.00467497 0.00322382 16 5 0.96890000 0.97357497 0.00128907 0.00336472 0 0 true +backend-v1 chm13_chr1_hor nthash2 262144 262144 32 21 2048 14 12 227.464 442c38caea2b506e 25.260 0.992 5892306 0.67820740 0 1024 0 0 0.00084150 0.00070349 0.00213576 0.00311796 0.00141828 21 5 0.97030000 0.97341796 0.00107340 0.00283037 0 0 true +backend-v1 chm13_chr1_hor nthash2 262144 262144 32 21 4096 14 12 227.464 442c38caea2b506e 31.591 2.155 11233490 0.43370819 0 1024 0 0 0.00056449 0.00048332 0.00137703 0.00181828 0.00181828 17 4 0.97700000 0.97518172 0.00109975 0.00278541 0 0 true +backend-v1 chm13_chr1_hor mashmap_murmur3 262144 262144 32 21 512 14 12 27.785 14a6772490c81c8e 29.589 0.236 1886418 0.88348389 0 1024 0 0 0.00193632 0.00147383 0.00500739 0.00718298 0.00223044 25 16 0.97030000 0.97748298 0.00183759 0.00466666 0 0 true +backend-v1 chm13_chr1_hor mashmap_murmur3 262144 262144 32 21 1024 14 12 27.785 14a6772490c81c8e 34.114 0.638 3221714 0.77331543 0 1024 0 0 0.00113128 0.00089668 0.00293977 0.00498185 0.00293977 25 3 0.97310000 0.97808185 0.00132538 0.00345530 0 0 true +backend-v1 chm13_chr1_hor mashmap_murmur3 262144 262144 32 21 2048 14 12 27.785 14a6772490c81c8e 41.722 0.997 5892306 0.60136414 0 1024 0 0 0.00076539 0.00062605 0.00204548 0.00437482 0.00221050 31 16 0.96570000 0.97007482 0.00114319 0.00291103 0 0 true +backend-v1 chm13_chr1_hor mashmap_murmur3 262144 262144 32 21 4096 14 12 27.785 14a6772490c81c8e 47.470 2.231 11233490 0.39773560 0 1024 0 0 0.00059583 0.00048507 0.00147111 0.00233071 0.00136846 8 3 0.96680000 0.96913071 0.00101511 0.00265202 0 0 true diff --git a/docs/benchmarks/backend-native.tsv b/docs/benchmarks/backend-native.tsv new file mode 100644 index 0000000..827bf62 --- /dev/null +++ b/docs/benchmarks/backend-native.tsv @@ -0,0 +1,181 @@ +schema fixture hash x_bases y_bases resolution k registers b_bits hll_precision hash_mkmer_s hash_checksum build_ms matrix_median_ms bytes mean_core_occupancy fingerprint32_collisions cells false_missing false_present mean_abs_error p50_abs_error p95_abs_error max_abs_error max_overestimate worst_column worst_row worst_estimate worst_exact mashmap_mean_abs_error mashmap_p95_abs_error direction_correct_cells direction_tested_cells passed +backend-v1 identity packed_fmix64 262144 262144 32 21 512 14 12 157.450 b7e90671068693ca 14.649 0.191 1886228 1.00000000 0 1024 0 0 0.00016095 0.00000000 0.00086822 0.00626822 0.00626822 1 0 0.97380000 0.96753178 0.00016306 0.00121956 32 32 true +backend-v1 identity packed_fmix64 262144 262144 32 21 1024 14 12 157.450 b7e90671068693ca 21.378 0.547 3221524 0.99951172 0 1024 0 0 0.00011553 0.00000000 0.00066822 0.00556822 0.00556822 10 9 0.97310000 0.96753178 0.00011426 0.00061876 32 32 true +backend-v1 identity packed_fmix64 262144 262144 32 21 2048 14 12 157.450 b7e90671068693ca 34.326 0.964 5892116 0.98127747 0 1024 0 0 0.00010007 0.00000000 0.00070000 0.00356822 0.00356822 1 0 0.97110000 0.96753178 0.00009566 0.00070000 32 32 true +backend-v1 identity packed_fmix64 262144 262144 32 21 4096 14 12 157.450 b7e90671068693ca 57.200 2.119 11233300 0.86338043 0 1024 0 0 0.00007457 0.00000000 0.00046822 0.00296822 0.00296822 2 1 0.97050000 0.96753178 0.00008579 0.00070000 32 32 true +backend-v1 identity nthash2 262144 262144 32 21 512 14 12 235.199 fea5f06c4f5198ba 13.336 0.182 1886228 1.00000000 0 1024 0 0 0.00014753 0.00000000 0.00086822 0.00826822 0.00826822 16 15 0.97580000 0.96753178 0.00016802 0.00150000 32 32 true +backend-v1 identity nthash2 262144 262144 32 21 1024 14 12 235.199 fea5f06c4f5198ba 19.883 0.577 3221524 0.99969482 0 1024 0 0 0.00013823 0.00000000 0.00096822 0.00586822 0.00586822 20 19 0.97340000 0.96753178 0.00015470 0.00130000 32 32 true +backend-v1 identity nthash2 262144 262144 32 21 2048 14 12 235.199 fea5f06c4f5198ba 32.959 0.980 5892116 0.98252869 0 1024 0 0 0.00009042 0.00000000 0.00056822 0.00406822 0.00406822 20 19 0.97160000 0.96753178 0.00011648 0.00068851 32 32 true +backend-v1 identity nthash2 262144 262144 32 21 4096 14 12 235.199 fea5f06c4f5198ba 55.603 2.095 11233300 0.86451721 0 1024 0 0 0.00006858 0.00000000 0.00053178 0.00276822 0.00276822 20 19 0.97030000 0.96753178 0.00010555 0.00062204 32 32 true +backend-v1 identity mashmap_murmur3 262144 262144 32 21 512 14 12 9.195 3e0ee3a95528dc2e 67.466 0.176 1886228 0.97027588 0 1024 0 0 0.00019326 0.00000000 0.00153178 0.00686822 0.00686822 28 27 0.97440000 0.96753178 0.00018113 0.00150000 32 32 true +backend-v1 identity mashmap_murmur3 262144 262144 32 21 1024 14 12 9.195 3e0ee3a95528dc2e 73.808 0.506 3221524 0.93814087 0 1024 0 0 0.00013449 0.00000000 0.00085559 0.00866822 0.00866822 20 19 0.97620000 0.96753178 0.00014017 0.00114932 32 32 true +backend-v1 identity mashmap_murmur3 262144 262144 32 21 2048 14 12 9.195 3e0ee3a95528dc2e 85.372 0.961 5892116 0.87457275 0 1024 0 0 0.00007951 0.00000000 0.00036822 0.00476822 0.00476822 20 19 0.97230000 0.96753178 0.00010561 0.00100000 32 32 true +backend-v1 identity mashmap_murmur3 262144 262144 32 21 4096 14 12 9.195 3e0ee3a95528dc2e 104.810 2.127 11233300 0.75405121 0 1024 0 0 0.00007326 0.00000000 0.00046822 0.00296822 0.00296822 21 20 0.97050000 0.96753178 0.00009409 0.00073998 32 32 true +backend-v1 substitution_01pct packed_fmix64 262144 262144 32 21 512 14 12 152.771 348202827e831b36 14.538 0.182 1886248 1.00000000 0 1024 0 0 0.00022274 0.00000000 0.00176443 0.00982255 0.00982255 6 5 0.96450000 0.95467745 0.00022471 0.00156968 32 32 true +backend-v1 substitution_01pct packed_fmix64 262144 262144 32 21 1024 14 12 152.771 348202827e831b36 21.270 0.493 3221544 0.99960327 0 1024 0 0 0.00014413 0.00000000 0.00107759 0.00527810 0.00527810 1 0 0.96340000 0.95812190 0.00016593 0.00105328 32 32 true +backend-v1 substitution_01pct packed_fmix64 262144 262144 32 21 2048 14 12 152.771 348202827e831b36 33.920 0.960 5892136 0.98126221 0 1024 0 0 0.00011140 0.00000000 0.00106359 0.00387810 0.00387810 1 0 0.96200000 0.95812190 0.00014555 0.00102801 32 32 true +backend-v1 substitution_01pct packed_fmix64 262144 262144 32 21 4096 14 12 152.771 348202827e831b36 56.744 2.119 11233320 0.86408234 0 1024 0 0 0.00009095 0.00000000 0.00078708 0.00324380 0.00324380 15 14 0.96290000 0.95965620 0.00013785 0.00096812 32 32 true +backend-v1 substitution_01pct nthash2 262144 262144 32 21 512 14 12 233.970 5cf02a0a6c9e3cba 13.278 0.181 1886248 1.00000000 0 1024 0 0 0.00023196 0.00000000 0.00191407 0.00894673 0.00876150 23 22 0.95100000 0.95994673 0.00027211 0.00211287 32 32 true +backend-v1 substitution_01pct nthash2 262144 262144 32 21 1024 14 12 233.970 5cf02a0a6c9e3cba 20.014 0.581 3221544 0.99963379 0 1024 0 0 0.00016865 0.00000000 0.00103979 0.00944673 0.00726150 23 22 0.95050000 0.95994673 0.00021138 0.00150872 32 32 true +backend-v1 substitution_01pct nthash2 262144 262144 32 21 2048 14 12 233.970 5cf02a0a6c9e3cba 32.765 0.952 5892136 0.98243713 0 1024 0 0 0.00011278 0.00000000 0.00095620 0.00449179 0.00449179 20 20 0.99190000 0.98740821 0.00017594 0.00137469 32 32 true +backend-v1 substitution_01pct nthash2 262144 262144 32 21 4096 14 12 233.970 5cf02a0a6c9e3cba 55.544 2.139 11233320 0.86512756 0 1024 0 0 0.00009506 0.00000000 0.00080728 0.00334673 0.00279753 22 23 0.95660000 0.95994673 0.00017000 0.00125898 32 32 true +backend-v1 substitution_01pct mashmap_murmur3 262144 262144 32 21 512 14 12 9.173 13e83fc808e3767c 67.361 0.181 1886248 0.97091675 0 1024 0 0 0.00022514 0.00000000 0.00188708 0.00756150 0.00756150 20 19 0.96480000 0.95723850 0.00024169 0.00214439 32 32 true +backend-v1 substitution_01pct mashmap_murmur3 262144 262144 32 21 1024 14 12 9.173 13e83fc808e3767c 73.961 0.575 3221544 0.93885803 0 1024 0 0 0.00015867 0.00000000 0.00143502 0.00986150 0.00986150 20 19 0.96710000 0.95723850 0.00020439 0.00183013 32 32 true +backend-v1 substitution_01pct mashmap_murmur3 262144 262144 32 21 2048 14 12 9.173 13e83fc808e3767c 85.272 1.005 5892136 0.87517548 0 1024 0 0 0.00011746 0.00000000 0.00096186 0.00576150 0.00576150 20 19 0.96300000 0.95723850 0.00016094 0.00125925 32 32 true +backend-v1 substitution_01pct mashmap_murmur3 262144 262144 32 21 4096 14 12 9.173 13e83fc808e3767c 104.571 2.102 11233320 0.75450897 0 1024 0 0 0.00009523 0.00000000 0.00077404 0.00346150 0.00346150 20 19 0.96070000 0.95723850 0.00015907 0.00107244 32 32 true +backend-v1 substitution_05pct packed_fmix64 262144 262144 32 21 512 14 12 159.736 761ff557813bad25 14.822 0.181 1886248 1.00000000 0 1024 0 0 0.00039724 0.00000000 0.00287420 0.01238199 0.01238199 15 16 0.93510000 0.92271801 0.00042456 0.00362884 32 32 true +backend-v1 substitution_05pct packed_fmix64 262144 262144 32 21 1024 14 12 159.736 761ff557813bad25 21.248 0.572 3221544 0.99957275 0 1024 0 0 0.00034051 0.00000000 0.00287420 0.01211761 0.01211761 9 10 0.93140000 0.91928239 0.00038870 0.00278185 32 32 true +backend-v1 substitution_05pct packed_fmix64 262144 262144 32 21 2048 14 12 159.736 761ff557813bad25 33.990 0.945 5892136 0.98088837 0 1024 0 0 0.00021616 0.00000000 0.00179780 0.00706314 0.00706314 11 10 0.92270000 0.91563686 0.00029843 0.00204787 32 32 true +backend-v1 substitution_05pct packed_fmix64 262144 262144 32 21 4096 14 12 159.736 761ff557813bad25 56.566 2.113 11233320 0.86393356 0 1024 0 0 0.00015837 0.00000000 0.00130710 0.00500350 0.00500350 15 14 0.92500000 0.91999650 0.00026512 0.00229145 32 32 true +backend-v1 substitution_05pct nthash2 262144 262144 32 21 512 14 12 238.407 a3c371daadae2b23 13.282 0.183 1886248 1.00000000 0 1024 0 0 0.00040464 0.00000000 0.00346283 0.01525839 0.01525839 6 5 0.93130000 0.91604161 0.00039004 0.00353494 32 32 true +backend-v1 substitution_05pct nthash2 262144 262144 32 21 1024 14 12 238.407 a3c371daadae2b23 20.063 0.567 3221544 0.99974060 0 1024 0 0 0.00031393 0.00000000 0.00290350 0.01098835 0.01098835 19 20 0.93120000 0.92021165 0.00032613 0.00265889 32 32 true +backend-v1 substitution_05pct nthash2 262144 262144 32 21 2048 14 12 238.407 a3c371daadae2b23 32.940 0.949 5892136 0.98163605 0 1024 0 0 0.00018966 0.00000000 0.00163958 0.00853017 0.00853017 8 9 0.92230000 0.91376983 0.00029974 0.00299145 32 32 true +backend-v1 substitution_05pct nthash2 262144 262144 32 21 4096 14 12 238.407 a3c371daadae2b23 56.012 2.149 11233320 0.86423492 0 1024 0 0 0.00015426 0.00000000 0.00143686 0.00483017 0.00483017 8 9 0.91860000 0.91376983 0.00027540 0.00239791 32 32 true +backend-v1 substitution_05pct mashmap_murmur3 262144 262144 32 21 512 14 12 9.155 14bb15f3576e5884 67.590 0.179 1886248 0.97006226 0 1024 0 0 0.00041841 0.00000000 0.00317646 0.01615043 0.01615043 11 12 0.93790000 0.92174957 0.00043957 0.00359307 32 32 true +backend-v1 substitution_05pct mashmap_murmur3 262144 262144 32 21 1024 14 12 9.155 14bb15f3576e5884 73.839 0.554 3221544 0.93827820 0 1024 0 0 0.00027512 0.00000000 0.00243017 0.01005043 0.01005043 11 12 0.93180000 0.92174957 0.00037157 0.00331347 32 32 true +backend-v1 substitution_05pct mashmap_murmur3 262144 262144 32 21 2048 14 12 9.155 14bb15f3576e5884 85.427 0.947 5892136 0.87529755 0 1024 0 0 0.00019071 0.00000000 0.00156988 0.00655043 0.00655043 11 12 0.92830000 0.92174957 0.00031929 0.00290741 32 32 true +backend-v1 substitution_05pct mashmap_murmur3 262144 262144 32 21 4096 14 12 9.155 14bb15f3576e5884 104.895 2.168 11233320 0.75469589 0 1024 0 0 0.00014729 0.00000000 0.00123858 0.00553017 0.00553017 9 8 0.91930000 0.91376983 0.00027037 0.00239859 32 32 true +backend-v1 substitution_10pct packed_fmix64 262144 262144 32 21 512 14 12 153.861 7db9f39c10b39619 14.505 0.179 1886248 1.00000000 0 1024 0 0 0.00074589 0.00000000 0.00460211 0.02956942 0.02956942 15 16 0.90070000 0.87113058 0.00100881 0.00674709 32 32 true +backend-v1 substitution_10pct packed_fmix64 262144 262144 32 21 1024 14 12 153.861 7db9f39c10b39619 21.081 0.583 3221544 0.99958801 0 1024 0 0 0.00061848 0.00000000 0.00494627 0.02636942 0.02636942 15 16 0.89750000 0.87113058 0.00091005 0.00676367 32 32 true +backend-v1 substitution_10pct packed_fmix64 262144 262144 32 21 2048 14 12 153.861 7db9f39c10b39619 34.177 0.953 5892136 0.98145294 0 1024 0 0 0.00044196 0.00000000 0.00434665 0.02028508 0.02028508 13 12 0.88040000 0.86011492 0.00078623 0.00580753 32 32 true +backend-v1 substitution_10pct packed_fmix64 262144 262144 32 21 4096 14 12 153.861 7db9f39c10b39619 56.694 2.134 11233320 0.86388016 0 1024 0 0 0.00028167 0.00000000 0.00237946 0.01138790 0.01026942 6 7 0.85520000 0.86658790 0.00073528 0.00675457 32 32 true +backend-v1 substitution_10pct nthash2 262144 262144 32 21 512 14 12 236.681 87b446ad49100feb 13.370 0.179 1886248 1.00000000 0 1024 0 0 0.00076012 0.00000000 0.00595335 0.03606433 0.03606433 20 21 0.89700000 0.86093567 0.00094666 0.00729975 32 32 true +backend-v1 substitution_10pct nthash2 262144 262144 32 21 1024 14 12 236.681 87b446ad49100feb 19.895 0.585 3221544 0.99961853 0 1024 0 0 0.00052649 0.00000000 0.00405947 0.01688508 0.01688508 12 13 0.87700000 0.86011492 0.00073443 0.00693401 32 32 true +backend-v1 substitution_10pct nthash2 262144 262144 32 21 2048 14 12 236.681 87b446ad49100feb 32.981 0.959 5892136 0.98207855 0 1024 0 0 0.00034385 0.00000000 0.00286353 0.01086433 0.01086433 20 21 0.87180000 0.86093567 0.00069356 0.00568432 32 32 true +backend-v1 substitution_10pct nthash2 262144 262144 32 21 4096 14 12 236.681 87b446ad49100feb 55.617 2.150 11233320 0.86423111 0 1024 0 0 0.00025940 0.00000000 0.00216221 0.00747651 0.00744526 1 0 0.86120000 0.86867651 0.00069117 0.00537701 32 32 true +backend-v1 substitution_10pct mashmap_murmur3 262144 262144 32 21 512 14 12 9.158 c33105250c05886a 67.593 0.179 1886248 0.96994019 0 1024 0 0 0.00078072 0.00000000 0.00725335 0.02343567 0.02305877 21 20 0.83750000 0.86093567 0.00093015 0.00777819 32 32 true +backend-v1 substitution_10pct mashmap_murmur3 262144 262144 32 21 1024 14 12 9.158 c33105250c05886a 73.778 0.591 3221544 0.93801880 0 1024 0 0 0.00062275 0.00000000 0.00517263 0.02093567 0.01912358 20 21 0.84000000 0.86093567 0.00086561 0.00735720 32 32 true +backend-v1 substitution_10pct mashmap_murmur3 262144 262144 32 21 2048 14 12 9.158 c33105250c05886a 85.224 0.968 5892136 0.87484741 0 1024 0 0 0.00039133 0.00000000 0.00375947 0.01203567 0.01132358 20 21 0.84890000 0.86093567 0.00075930 0.00656787 32 32 true +backend-v1 substitution_10pct mashmap_murmur3 262144 262144 32 21 4096 14 12 9.158 c33105250c05886a 104.715 2.121 11233320 0.75434875 0 1024 0 0 0.00022252 0.00000000 0.00160211 0.00732358 0.00732358 26 25 0.87790000 0.87057642 0.00067884 0.00584228 32 32 true +backend-v1 indel_05pct packed_fmix64 262144 262144 32 21 512 14 12 156.069 2f8345318ed19cbc 14.516 0.180 1886234 1.00000000 0 1024 0 0 0.00488170 0.00000000 0.01812058 0.82384341 0.04815483 12 12 0.00000000 0.82384341 0.00662578 0.01512087 31 31 false +backend-v1 indel_05pct packed_fmix64 262144 262144 32 21 1024 14 12 156.069 2f8345318ed19cbc 21.303 0.616 3221530 0.99963379 0 1024 0 0 0.00124978 0.00000000 0.01086764 0.04617926 0.03317695 17 16 0.75600000 0.80217926 0.00324955 0.01464138 32 32 true +backend-v1 indel_05pct packed_fmix64 262144 262144 32 21 2048 14 12 156.069 2f8345318ed19cbc 34.218 0.950 5892122 0.98177338 0 1024 0 0 0.00081769 0.00000000 0.00737171 0.03317695 0.03317695 1 0 0.83630000 0.80312305 0.00309585 0.01459725 32 32 true +backend-v1 indel_05pct packed_fmix64 262144 262144 32 21 4096 14 12 156.069 2f8345318ed19cbc 56.808 2.104 11233306 0.86440659 0 1024 0 0 0.00056890 0.00000000 0.00529993 0.02017695 0.02017695 1 0 0.82330000 0.80312305 0.00301425 0.01330852 32 32 true +backend-v1 indel_05pct nthash2 262144 262144 32 21 512 14 12 232.251 16aec59d3fa6fc41 13.413 0.179 1886234 1.00000000 0 1024 0 0 0.00322221 0.00000000 0.01854610 0.81074857 0.04222074 18 17 0.00000000 0.81074857 0.00507101 0.01610127 32 32 false +backend-v1 indel_05pct nthash2 262144 262144 32 21 1024 14 12 232.251 16aec59d3fa6fc41 19.981 0.572 3221530 0.99964905 0 1024 0 0 0.00125750 0.00000000 0.00971568 0.04335483 0.04335483 2 3 0.84740000 0.80404517 0.00336389 0.01339788 32 32 true +backend-v1 indel_05pct nthash2 262144 262144 32 21 2048 14 12 232.251 16aec59d3fa6fc41 32.851 0.991 5892122 0.98190308 0 1024 0 0 0.00082274 0.00000000 0.00648675 0.03177695 0.03177695 16 15 0.83490000 0.80312305 0.00308952 0.01183564 32 32 true +backend-v1 indel_05pct nthash2 262144 262144 32 21 4096 14 12 232.251 16aec59d3fa6fc41 55.618 2.087 11233306 0.86439133 0 1024 0 0 0.00060550 0.00000000 0.00447695 0.01888226 0.01888226 13 12 0.81860000 0.79971774 0.00304472 0.01143165 32 32 true +backend-v1 indel_05pct mashmap_murmur3 262144 262144 32 21 512 14 12 9.138 9ce9a464a1f82661 67.503 0.178 1886234 0.96984863 0 1024 0 0 0.00386643 0.00000000 0.01499301 0.82885113 0.04134117 22 22 0.00000000 0.82885113 0.00536835 0.01148733 31 31 false +backend-v1 indel_05pct mashmap_murmur3 262144 262144 32 21 1024 14 12 9.138 9ce9a464a1f82661 73.930 0.591 3221530 0.93832397 0 1024 0 0 0.00111447 0.00000000 0.00893814 0.04794517 0.03391325 26 27 0.75610000 0.80404517 0.00310676 0.01106763 32 32 true +backend-v1 indel_05pct mashmap_murmur3 262144 262144 32 21 2048 14 12 9.138 9ce9a464a1f82661 85.644 0.948 5892122 0.87497711 0 1024 0 0 0.00081117 0.00000000 0.00712829 0.02641325 0.02641325 7 6 0.83000000 0.80358675 0.00319823 0.01390127 32 32 true +backend-v1 indel_05pct mashmap_murmur3 262144 262144 32 21 4096 14 12 9.138 9ce9a464a1f82661 105.014 2.103 11233306 0.75454330 0 1024 0 0 0.00066879 0.00000000 0.00575483 0.02750007 0.01971325 11 11 0.79690000 0.82440007 0.00311168 0.01280403 32 32 true +backend-v1 offset_1kb packed_fmix64 262144 262144 32 21 512 14 12 158.479 4d1b9acb2cf93df6 14.571 0.180 1886232 1.00000000 0 1024 0 0 0.00017158 0.00000000 0.00116747 0.01103221 0.01103221 0 1 0.96540000 0.95436779 0.00017294 0.00103969 32 32 true +backend-v1 offset_1kb packed_fmix64 262144 262144 32 21 1024 14 12 158.479 4d1b9acb2cf93df6 21.185 0.576 3221528 0.99955750 0 1024 0 0 0.00011291 0.00000000 0.00070000 0.00783221 0.00783221 0 1 0.96220000 0.95436779 0.00013908 0.00077605 32 32 true +backend-v1 offset_1kb packed_fmix64 262144 262144 32 21 2048 14 12 158.479 4d1b9acb2cf93df6 34.064 0.954 5892120 0.98136902 0 1024 0 0 0.00009370 0.00000000 0.00060000 0.00523221 0.00523221 0 1 0.95960000 0.95436779 0.00010994 0.00072546 32 32 true +backend-v1 offset_1kb packed_fmix64 262144 262144 32 21 4096 14 12 158.479 4d1b9acb2cf93df6 56.875 2.089 11233304 0.86398315 0 1024 0 0 0.00007281 0.00000000 0.00043253 0.00313221 0.00313221 20 21 0.95750000 0.95436779 0.00010365 0.00063614 32 32 true +backend-v1 offset_1kb nthash2 262144 262144 32 21 512 14 12 234.109 c08f9d19adc27363 13.251 0.181 1886232 1.00000000 0 1024 0 0 0.00017524 0.00000000 0.00083253 0.01113221 0.01113221 19 20 0.96550000 0.95436779 0.00018234 0.00094926 32 32 true +backend-v1 offset_1kb nthash2 262144 262144 32 21 1024 14 12 234.109 c08f9d19adc27363 20.121 0.599 3221528 0.99971008 0 1024 0 0 0.00013841 0.00000000 0.00086779 0.00813221 0.00813221 19 20 0.96250000 0.95436779 0.00015461 0.00096783 32 32 true +backend-v1 offset_1kb nthash2 262144 262144 32 21 2048 14 12 234.109 c08f9d19adc27363 32.887 0.949 5892120 0.98239136 0 1024 0 0 0.00009878 0.00000000 0.00066747 0.00642132 0.00642132 0 31 0.91140000 0.90497868 0.00013146 0.00087454 32 32 true +backend-v1 offset_1kb nthash2 262144 262144 32 21 4096 14 12 234.109 c08f9d19adc27363 55.727 2.134 11233304 0.86426163 0 1024 0 0 0.00006674 0.00000000 0.00053253 0.00363221 0.00363221 27 28 0.95800000 0.95436779 0.00011629 0.00070000 32 32 true +backend-v1 offset_1kb mashmap_murmur3 262144 262144 32 21 512 14 12 9.176 6ee49bda1ce367ac 67.503 0.194 1886232 0.97006226 0 1024 0 0 0.00020079 0.00000000 0.00150000 0.01287868 0.00623221 0 31 0.89210000 0.90497868 0.00017468 0.00138864 32 32 true +backend-v1 offset_1kb mashmap_murmur3 262144 262144 32 21 1024 14 12 9.176 6ee49bda1ce367ac 74.353 0.579 3221528 0.93801880 0 1024 0 0 0.00014119 0.00000000 0.00073253 0.00833221 0.00833221 19 20 0.96270000 0.95436779 0.00016180 0.00116747 32 32 true +backend-v1 offset_1kb mashmap_murmur3 262144 262144 32 21 2048 14 12 9.176 6ee49bda1ce367ac 85.282 0.989 5892120 0.87480164 0 1024 0 0 0.00009855 0.00000000 0.00060000 0.00737868 0.00543221 0 31 0.89760000 0.90497868 0.00011948 0.00060000 32 32 true +backend-v1 offset_1kb mashmap_murmur3 262144 262144 32 21 4096 14 12 9.176 6ee49bda1ce367ac 104.480 2.092 11233304 0.75428772 0 1024 0 0 0.00007558 0.00000000 0.00033253 0.00437868 0.00383221 0 31 0.90060000 0.90497868 0.00010336 0.00057973 32 32 true +backend-v1 central_inversion packed_fmix64 262144 262144 32 21 512 14 12 157.448 4355345cc4acfc2f 14.814 0.194 1886246 1.00000000 0 1024 0 0 0.00017632 0.00000000 0.00114378 0.00626822 0.00626822 1 0 0.97380000 0.96753178 0.00018860 0.00171397 0 0 true +backend-v1 central_inversion packed_fmix64 262144 262144 32 21 1024 14 12 157.448 4355345cc4acfc2f 21.136 0.582 3221542 0.99951172 0 1024 0 0 0.00011965 0.00000000 0.00079371 0.00589371 0.00589371 10 22 0.97320000 0.96730629 0.00013221 0.00110000 0 0 true +backend-v1 central_inversion packed_fmix64 262144 262144 32 21 2048 14 12 157.448 4355345cc4acfc2f 34.011 0.961 5892134 0.98132324 0 1024 0 0 0.00010382 0.00000000 0.00070000 0.00376822 0.00376822 6 7 0.97130000 0.96753178 0.00010988 0.00085459 0 0 true +backend-v1 central_inversion packed_fmix64 262144 262144 32 21 4096 14 12 157.448 4355345cc4acfc2f 56.608 2.088 11233318 0.86340332 0 1024 0 0 0.00008111 0.00000000 0.00055622 0.00329371 0.00329371 8 24 0.97060000 0.96730629 0.00010512 0.00098212 0 0 true +backend-v1 central_inversion nthash2 262144 262144 32 21 512 14 12 236.330 cab3646550b5a20f 13.258 0.188 1886246 1.00000000 0 1024 0 0 0.00015636 0.00000000 0.00104378 0.00859371 0.00859371 16 16 0.97590000 0.96730629 0.00018146 0.00151137 0 0 true +backend-v1 central_inversion nthash2 262144 262144 32 21 1024 14 12 236.330 cab3646550b5a20f 20.026 0.594 3221542 0.99971008 0 1024 0 0 0.00014541 0.00000000 0.00109371 0.00599371 0.00599371 20 12 0.97330000 0.96730629 0.00016488 0.00141442 0 0 true +backend-v1 central_inversion nthash2 262144 262144 32 21 2048 14 12 236.330 cab3646550b5a20f 32.932 0.954 5892134 0.98257446 0 1024 0 0 0.00009650 0.00000000 0.00065559 0.00409371 0.00409371 20 12 0.97140000 0.96730629 0.00012626 0.00080000 0 0 true +backend-v1 central_inversion nthash2 262144 262144 32 21 4096 14 12 236.330 cab3646550b5a20f 55.944 2.112 11233318 0.86457443 0 1024 0 0 0.00007234 0.00000000 0.00050629 0.00279371 0.00279371 20 12 0.97010000 0.96730629 0.00011779 0.00078230 0 0 true +backend-v1 central_inversion mashmap_murmur3 262144 262144 32 21 512 14 12 9.148 c0b408208a797941 67.594 0.179 1886246 0.97027588 0 1024 0 0 0.00021029 0.00000000 0.00175622 0.00726822 0.00726822 24 23 0.97480000 0.96753178 0.00020688 0.00164212 0 0 true +backend-v1 central_inversion mashmap_murmur3 262144 262144 32 21 1024 14 12 9.148 c0b408208a797941 73.892 0.600 3221542 0.93815613 0 1024 0 0 0.00015061 0.00000000 0.00100000 0.00874378 0.00874378 19 11 0.97650000 0.96775622 0.00015838 0.00130000 0 0 true +backend-v1 central_inversion mashmap_murmur3 262144 262144 32 21 2048 14 12 9.148 c0b408208a797941 85.453 0.960 5892134 0.87458801 0 1024 0 0 0.00008370 0.00000000 0.00044378 0.00474378 0.00474378 19 11 0.97250000 0.96775622 0.00011394 0.00100000 0 0 true +backend-v1 central_inversion mashmap_murmur3 262144 262144 32 21 4096 14 12 9.148 c0b408208a797941 104.768 2.148 11233318 0.75408936 0 1024 0 0 0.00007820 0.00000000 0.00056822 0.00299371 0.00299371 21 11 0.97030000 0.96730629 0.00010273 0.00080000 0 0 true +backend-v1 reverse_complement packed_fmix64 262144 262144 32 21 512 14 12 154.495 ce66c311efa71ecf 14.465 0.181 1886248 1.00000000 0 1024 0 0 0.00016248 0.00000000 0.00090629 0.00648111 0.00648111 1 31 0.97390000 0.96741889 0.00016321 0.00120272 2 2 true +backend-v1 reverse_complement packed_fmix64 262144 262144 32 21 1024 14 12 154.495 ce66c311efa71ecf 21.319 0.602 3221544 0.99952698 0 1024 0 0 0.00011570 0.00000000 0.00069371 0.00589371 0.00589371 10 22 0.97320000 0.96730629 0.00011384 0.00084266 2 2 true +backend-v1 reverse_complement packed_fmix64 262144 262144 32 21 2048 14 12 154.495 ce66c311efa71ecf 34.007 0.953 5892136 0.98132324 0 1024 0 0 0.00010084 0.00000000 0.00064378 0.00368111 0.00368111 1 31 0.97110000 0.96741889 0.00009714 0.00068863 2 2 true +backend-v1 reverse_complement packed_fmix64 262144 262144 32 21 4096 14 12 154.495 ce66c311efa71ecf 56.809 2.142 11233320 0.86339951 0 1024 0 0 0.00007455 0.00000000 0.00050000 0.00309371 0.00309371 2 30 0.97040000 0.96730629 0.00008509 0.00060000 2 2 true +backend-v1 reverse_complement nthash2 262144 262144 32 21 512 14 12 231.592 d715804954fb0ebe 13.397 0.179 1886248 1.00000000 0 1024 0 0 0.00014647 0.00000000 0.00085622 0.00859371 0.00859371 16 16 0.97590000 0.96730629 0.00016519 0.00140272 2 2 true +backend-v1 reverse_complement nthash2 262144 262144 32 21 1024 14 12 231.592 d715804954fb0ebe 20.154 0.570 3221544 0.99969482 0 1024 0 0 0.00013798 0.00000000 0.00089371 0.00599371 0.00599371 20 12 0.97330000 0.96730629 0.00015285 0.00130000 2 2 true +backend-v1 reverse_complement nthash2 262144 262144 32 21 2048 14 12 231.592 d715804954fb0ebe 32.660 0.966 5892136 0.98261261 0 1024 0 0 0.00009104 0.00000000 0.00055622 0.00409371 0.00409371 20 12 0.97140000 0.96730629 0.00011616 0.00075531 2 2 true +backend-v1 reverse_complement nthash2 262144 262144 32 21 4096 14 12 231.592 d715804954fb0ebe 55.682 2.140 11233320 0.86457062 0 1024 0 0 0.00006957 0.00000000 0.00050629 0.00279371 0.00279371 20 12 0.97010000 0.96730629 0.00010427 0.00069716 2 2 true +backend-v1 reverse_complement mashmap_murmur3 262144 262144 32 21 512 14 12 9.168 9bda144b012cecbc 67.491 0.188 1886248 0.97027588 0 1024 0 0 0.00019531 0.00000000 0.00170629 0.00719371 0.00719371 28 4 0.97450000 0.96730629 0.00018102 0.00150000 2 2 true +backend-v1 reverse_complement mashmap_murmur3 262144 262144 32 21 1024 14 12 9.168 9bda144b012cecbc 73.812 0.605 3221544 0.93815613 0 1024 0 0 0.00013696 0.00000000 0.00080000 0.00874378 0.00874378 19 11 0.97650000 0.96775622 0.00014019 0.00098153 2 2 true +backend-v1 reverse_complement mashmap_murmur3 262144 262144 32 21 2048 14 12 9.168 9bda144b012cecbc 85.240 0.948 5892136 0.87456512 0 1024 0 0 0.00007788 0.00000000 0.00040000 0.00474378 0.00474378 19 11 0.97250000 0.96775622 0.00010240 0.00088304 2 2 true +backend-v1 reverse_complement mashmap_murmur3 262144 262144 32 21 4096 14 12 9.168 9bda144b012cecbc 105.450 2.110 11233320 0.75408554 0 1024 0 0 0.00007299 0.00000000 0.00050000 0.00299371 0.00299371 21 11 0.97030000 0.96730629 0.00009161 0.00067879 2 2 true +backend-v1 low_complexity packed_fmix64 262144 262144 32 21 512 14 12 306.548 8cb43fbb0655523d 7.204 0.229 1886240 0.42242432 0 1024 0 0 0.00959577 0.00723298 0.03268653 0.04817143 0.02437863 0 6 0.91700000 0.96517143 0.00969322 0.03494897 32 32 true +backend-v1 low_complexity packed_fmix64 262144 262144 32 21 1024 14 12 306.548 8cb43fbb0655523d 8.425 0.608 3221536 0.28224182 0 1024 0 0 0.00664334 0.00566702 0.01482546 0.02512757 0.01500750 1 25 0.95680000 0.98192757 0.00593256 0.01606822 32 32 true +backend-v1 low_complexity packed_fmix64 262144 262144 32 21 2048 14 12 306.548 8cb43fbb0655523d 9.786 0.978 5892128 0.16718292 0 1024 0 0 0.00374793 0.00212857 0.01397295 0.01632137 0.00744110 0 25 0.89730000 0.91362137 0.00498877 0.01425304 32 32 true +backend-v1 low_complexity packed_fmix64 262144 262144 32 21 4096 14 12 306.548 8cb43fbb0655523d 10.762 2.113 11233312 0.09119034 0 1024 0 0 0.00320279 0.00225633 0.00930085 0.01330085 0.00437658 1 8 0.94750000 0.96080085 0.00476285 0.01267734 32 32 true +backend-v1 low_complexity nthash2 262144 262144 32 21 512 14 12 237.570 b493b4ce61cbb0f9 7.490 0.236 1886240 0.42416382 0 1024 0 0 0.01158373 0.00819250 0.03037037 0.06493178 0.01540750 31 12 0.90260000 0.96753178 0.01189444 0.03593925 32 32 false +backend-v1 low_complexity nthash2 262144 262144 32 21 1024 14 12 237.570 b493b4ce61cbb0f9 8.822 0.629 3221536 0.28268433 0 1024 0 0 0.00764625 0.00562342 0.02211342 0.02533178 0.00839644 31 12 0.94220000 0.96753178 0.00830635 0.01972896 32 32 true +backend-v1 low_complexity nthash2 262144 262144 32 21 2048 14 12 237.570 b493b4ce61cbb0f9 10.140 1.003 5892128 0.16634369 0 1024 0 0 0.00544318 0.00452963 0.01352757 0.02103178 0.01152857 31 11 0.94650000 0.96753178 0.00602388 0.01583123 32 32 true +backend-v1 low_complexity nthash2 262144 262144 32 21 4096 14 12 237.570 b493b4ce61cbb0f9 11.311 2.101 11233312 0.09045792 0 1024 0 0 0.00264524 0.00193298 0.00832757 0.01227037 0.00839644 1 10 0.94070000 0.95297037 0.00445449 0.01111370 32 32 true +backend-v1 low_complexity mashmap_murmur3 262144 262144 32 21 512 14 12 50.840 db01932053885cdc 15.643 0.226 1886240 0.37524414 0 1024 0 0 0.01749990 0.01554994 0.04521860 0.08217658 0.01732705 31 5 0.90850000 0.99067658 0.01783454 0.04957734 32 32 false +backend-v1 low_complexity mashmap_murmur3 262144 262144 32 21 1024 14 12 50.840 db01932053885cdc 16.921 0.635 3221536 0.25746155 0 1024 0 0 0.01055333 0.00916197 0.02451860 0.03258013 0.01797863 31 6 0.94660000 0.97918013 0.01105531 0.02540000 32 32 true +backend-v1 low_complexity mashmap_murmur3 262144 262144 32 21 2048 14 12 50.840 db01932053885cdc 18.077 0.976 5892128 0.15808868 0 1024 0 0 0.00542241 0.00489540 0.01219443 0.02117658 0.01412963 31 5 0.96950000 0.99067658 0.00647631 0.01503038 32 32 true +backend-v1 low_complexity mashmap_murmur3 262144 262144 32 21 4096 14 12 50.840 db01932053885cdc 19.419 2.111 11233312 0.08841705 0 1024 0 0 0.00428961 0.00329250 0.01253298 0.02190533 0.00732963 0 9 0.93540000 0.95730533 0.00583546 0.01241668 32 32 true +backend-v1 tandem_repeat packed_fmix64 262144 262144 32 21 512 14 12 291.769 8612db8254ae6f4c 4.314 0.196 1886238 0.05328369 0 1024 0 0 0.00001250 0.00000000 0.00000000 0.00040000 0.00000000 0 31 0.99960000 1.00000000 0.00001250 0.00000000 0 0 true +backend-v1 tandem_repeat packed_fmix64 262144 262144 32 21 1024 14 12 291.769 8612db8254ae6f4c 4.399 0.600 3221534 0.02664185 0 1024 0 0 0.00001250 0.00000000 0.00000000 0.00040000 0.00000000 0 31 0.99960000 1.00000000 0.00001250 0.00000000 0 0 true +backend-v1 tandem_repeat packed_fmix64 262144 262144 32 21 2048 14 12 291.769 8612db8254ae6f4c 4.622 0.990 5892126 0.01332092 0 1024 0 0 0.00001250 0.00000000 0.00000000 0.00040000 0.00000000 0 31 0.99960000 1.00000000 0.00001250 0.00000000 0 0 true +backend-v1 tandem_repeat packed_fmix64 262144 262144 32 21 4096 14 12 291.769 8612db8254ae6f4c 4.973 2.137 11233310 0.00666046 0 1024 0 0 0.00001250 0.00000000 0.00000000 0.00040000 0.00000000 0 31 0.99960000 1.00000000 0.00001250 0.00000000 0 0 true +backend-v1 tandem_repeat nthash2 262144 262144 32 21 512 14 12 239.898 e80768fcc831a677 4.502 0.190 1886238 0.05520630 0 1024 0 0 0.00001250 0.00000000 0.00000000 0.00040000 0.00000000 0 31 0.99960000 1.00000000 0.00001250 0.00000000 0 0 true +backend-v1 tandem_repeat nthash2 262144 262144 32 21 1024 14 12 239.898 e80768fcc831a677 4.768 0.589 3221534 0.02761841 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat nthash2 262144 262144 32 21 2048 14 12 239.898 e80768fcc831a677 5.018 0.992 5892126 0.01380920 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat nthash2 262144 262144 32 21 4096 14 12 239.898 e80768fcc831a677 5.350 2.124 11233310 0.00690460 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 262144 262144 32 21 512 14 12 42.568 b9d2333b0774a2b3 14.256 0.189 1886238 0.05322266 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 262144 262144 32 21 1024 14 12 42.568 b9d2333b0774a2b3 14.746 0.604 3221534 0.02760315 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 262144 262144 32 21 2048 14 12 42.568 b9d2333b0774a2b3 14.844 0.947 5892126 0.01380157 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 262144 262144 32 21 4096 14 12 42.568 b9d2333b0774a2b3 15.338 2.096 11233310 0.00690079 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 262144 262144 32 21 512 14 12 156.685 b35e98c334e82f2c 14.511 0.171 1886230 1.00000000 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 262144 262144 32 21 1024 14 12 156.685 b35e98c334e82f2c 21.284 0.599 3221526 0.99955750 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 262144 262144 32 21 2048 14 12 156.685 b35e98c334e82f2c 33.988 0.945 5892118 0.98159790 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 262144 262144 32 21 4096 14 12 156.685 b35e98c334e82f2c 56.877 2.092 11233302 0.86433792 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 262144 262144 32 21 512 14 12 234.026 9671d9840da6131f 13.318 0.172 1886230 1.00000000 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 262144 262144 32 21 1024 14 12 234.026 9671d9840da6131f 20.029 0.588 3221526 0.99955750 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 262144 262144 32 21 2048 14 12 234.026 9671d9840da6131f 32.831 0.947 5892118 0.98204041 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 262144 262144 32 21 4096 14 12 234.026 9671d9840da6131f 55.969 2.129 11233302 0.86434555 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 262144 262144 32 21 512 14 12 9.132 6fccb9f30ed3ec2c 67.813 0.171 1886230 0.96899414 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 262144 262144 32 21 1024 14 12 9.132 6fccb9f30ed3ec2c 74.028 0.598 3221526 0.93736267 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 262144 262144 32 21 2048 14 12 9.132 6fccb9f30ed3ec2c 90.592 0.964 5892118 0.87425232 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 262144 262144 32 21 4096 14 12 9.132 6fccb9f30ed3ec2c 105.260 2.150 11233302 0.75440598 0 1024 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 ambiguity_blocks packed_fmix64 262144 262144 32 21 512 14 12 154.307 d9d8d3b0936f6b16 13.832 0.178 1886244 1.00000000 0 1024 0 0 0.00016695 0.00000000 0.00086822 0.00686822 0.00686822 4 3 0.97440000 0.96753178 0.00016193 0.00127711 32 32 true +backend-v1 ambiguity_blocks packed_fmix64 262144 262144 32 21 1024 14 12 154.307 d9d8d3b0936f6b16 20.398 0.612 3221540 0.99871826 0 1024 0 0 0.00010631 0.00000000 0.00056822 0.00666822 0.00666822 10 9 0.97420000 0.96753178 0.00012156 0.00096822 32 32 true +backend-v1 ambiguity_blocks packed_fmix64 262144 262144 32 21 2048 14 12 154.307 d9d8d3b0936f6b16 33.299 0.985 5892132 0.96836853 0 1024 0 0 0.00010530 0.00000000 0.00086822 0.00456822 0.00456822 21 20 0.97210000 0.96753178 0.00010246 0.00060232 32 32 true +backend-v1 ambiguity_blocks packed_fmix64 262144 262144 32 21 4096 14 12 154.307 d9d8d3b0936f6b16 55.107 2.050 11233316 0.82341766 0 1024 0 0 0.00007876 0.00000000 0.00046822 0.00356822 0.00356822 21 20 0.97110000 0.96753178 0.00009380 0.00068197 32 32 true +backend-v1 ambiguity_blocks nthash2 262144 262144 32 21 512 14 12 228.848 372cbc40740b0ea8 12.804 0.179 1886244 1.00000000 0 1024 0 0 0.00015425 0.00000000 0.00106822 0.00606822 0.00606822 7 6 0.97360000 0.96753178 0.00015334 0.00097318 32 32 true +backend-v1 ambiguity_blocks nthash2 262144 262144 32 21 1024 14 12 228.848 372cbc40740b0ea8 19.519 0.584 3221540 0.99893188 0 1024 0 0 0.00014212 0.00000000 0.00126822 0.00466822 0.00466822 7 6 0.97220000 0.96753178 0.00014140 0.00109564 32 32 true +backend-v1 ambiguity_blocks nthash2 262144 262144 32 21 2048 14 12 228.848 372cbc40740b0ea8 32.131 0.982 5892132 0.96990967 0 1024 0 0 0.00009684 0.00000000 0.00086822 0.00296822 0.00296822 20 19 0.97050000 0.96753178 0.00011057 0.00090000 32 32 true +backend-v1 ambiguity_blocks nthash2 262144 262144 32 21 4096 14 12 228.848 372cbc40740b0ea8 54.936 2.229 11233316 0.82495880 0 1024 0 0 0.00006275 0.00000000 0.00036822 0.00256822 0.00256822 20 19 0.97010000 0.96753178 0.00011190 0.00080738 32 32 true +backend-v1 ambiguity_blocks mashmap_murmur3 262144 262144 32 21 512 14 12 9.131 f4e5163eba70885a 60.198 0.185 1886244 0.96630859 0 1024 0 0 0.00017098 0.00000000 0.00163178 0.00616822 0.00616822 20 19 0.97370000 0.96753178 0.00016586 0.00134388 32 32 true +backend-v1 ambiguity_blocks mashmap_murmur3 262144 262144 32 21 1024 14 12 9.131 f4e5163eba70885a 66.222 0.573 3221540 0.92959595 0 1024 0 0 0.00012415 0.00000000 0.00083178 0.00806822 0.00806822 20 19 0.97560000 0.96753178 0.00012949 0.00110000 32 32 true +backend-v1 ambiguity_blocks mashmap_murmur3 262144 262144 32 21 2048 14 12 9.131 f4e5163eba70885a 77.510 0.950 5892132 0.85670471 0 1024 0 0 0.00008262 0.00000000 0.00056822 0.00556822 0.00556822 20 19 0.97310000 0.96753178 0.00009688 0.00060000 32 32 true +backend-v1 ambiguity_blocks mashmap_murmur3 262144 262144 32 21 4096 14 12 9.131 f4e5163eba70885a 95.761 2.097 11233316 0.72158051 0 1024 0 0 0.00006900 0.00000000 0.00030000 0.00266822 0.00266822 24 23 0.97020000 0.96753178 0.00009503 0.00060566 32 32 true +backend-v1 sparse_valid_islands packed_fmix64 262144 262144 32 21 512 14 12 42.738 b911f2d8af113a0a 3.997 0.188 1886252 0.34381104 0 1024 0 0 0.00014123 0.00000000 0.00113178 0.00506822 0.00506822 8 7 0.97260000 0.96753178 0.00014123 0.00113178 32 32 true +backend-v1 sparse_valid_islands packed_fmix64 262144 262144 32 21 1024 14 12 42.738 b911f2d8af113a0a 4.382 0.577 3221548 0.19030762 0 1024 0 0 0.00008222 0.00000000 0.00046822 0.00356822 0.00356822 26 25 0.97110000 0.96753178 0.00008222 0.00046822 32 32 true +backend-v1 sparse_valid_islands packed_fmix64 262144 262144 32 21 2048 14 12 42.738 b911f2d8af113a0a 4.938 0.946 5892140 0.09996033 0 1024 0 0 0.00006020 0.00000000 0.00036822 0.00256822 0.00256822 26 25 0.97010000 0.96753178 0.00006020 0.00036822 32 32 true +backend-v1 sparse_valid_islands packed_fmix64 262144 262144 32 21 4096 14 12 42.738 b911f2d8af113a0a 5.647 2.092 11233324 0.05113983 0 1024 0 0 0.00006406 0.00000000 0.00043178 0.00216822 0.00216822 11 10 0.96970000 0.96753178 0.00006406 0.00043178 32 32 true +backend-v1 sparse_valid_islands nthash2 262144 262144 32 21 512 14 12 40.387 bcc81e61cb75952e 3.915 0.181 1886252 0.34741211 0 1024 0 0 0.00014452 0.00000000 0.00116822 0.00486822 0.00486822 11 10 0.97240000 0.96753178 0.00014452 0.00116822 32 32 true +backend-v1 sparse_valid_islands nthash2 262144 262144 32 21 1024 14 12 40.387 bcc81e61cb75952e 4.321 0.592 3221548 0.19064331 0 1024 0 0 0.00009120 0.00000000 0.00056822 0.00366822 0.00366822 11 10 0.97120000 0.96753178 0.00009120 0.00056822 32 32 true +backend-v1 sparse_valid_islands nthash2 262144 262144 32 21 2048 14 12 40.387 bcc81e61cb75952e 4.940 0.947 5892140 0.10018921 0 1024 0 0 0.00007379 0.00000000 0.00063178 0.00306822 0.00306822 8 7 0.97060000 0.96753178 0.00007379 0.00063178 32 32 true +backend-v1 sparse_valid_islands nthash2 262144 262144 32 21 4096 14 12 40.387 bcc81e61cb75952e 5.578 2.129 11233324 0.05133057 0 1024 0 0 0.00005192 0.00000000 0.00036822 0.00226822 0.00226822 8 7 0.96980000 0.96753178 0.00005192 0.00036822 32 32 true +backend-v1 sparse_valid_islands mashmap_murmur3 262144 262144 32 21 512 14 12 8.421 74501a1591534fd4 5.021 0.181 1886252 0.32452393 0 1024 0 0 0.00013474 0.00000000 0.00083178 0.00616822 0.00616822 3 2 0.97370000 0.96753178 0.00013474 0.00083178 32 32 true +backend-v1 sparse_valid_islands mashmap_murmur3 262144 262144 32 21 1024 14 12 8.421 74501a1591534fd4 5.663 0.602 3221548 0.18362427 0 1024 0 0 0.00009689 0.00000000 0.00050000 0.00426822 0.00426822 15 14 0.97180000 0.96753178 0.00009689 0.00050000 32 32 true +backend-v1 sparse_valid_islands mashmap_murmur3 262144 262144 32 21 2048 14 12 8.421 74501a1591534fd4 6.145 0.970 5892140 0.09857178 0 1024 0 0 0.00007580 0.00000000 0.00066822 0.00336822 0.00336822 15 14 0.97090000 0.96753178 0.00007580 0.00066822 32 32 true +backend-v1 sparse_valid_islands mashmap_murmur3 262144 262144 32 21 4096 14 12 8.421 74501a1591534fd4 6.785 2.081 11233324 0.05104065 0 1024 0 0 0.00005646 0.00000000 0.00036822 0.00216822 0.00216822 22 21 0.96970000 0.96753178 0.00005646 0.00036822 32 32 true +backend-v1 alternating_context packed_fmix64 262144 262144 32 21 512 14 12 245.415 0198d5f56fda1c27 11.412 0.227 1886250 0.76895142 0 1024 0 0 0.21603289 0.03229247 0.75729778 0.98175081 0.06537191 31 14 0.00000000 0.98175081 0.14040412 0.75205235 32 32 false +backend-v1 alternating_context packed_fmix64 262144 262144 32 21 1024 14 12 245.415 0198d5f56fda1c27 16.118 0.598 3221546 0.70248413 0 1024 0 0 0.15910972 0.02151425 0.71870663 0.78271102 0.07461376 2 16 0.00000000 0.78271102 0.09263374 0.74911536 32 32 false +backend-v1 alternating_context packed_fmix64 262144 262144 32 21 2048 14 12 245.415 0198d5f56fda1c27 23.845 0.987 5892138 0.57673645 0 1024 0 0 0.12128472 0.00847025 0.70893629 0.73270967 0.05158011 2 4 0.00000000 0.73270967 0.09160278 0.74845959 32 32 false +backend-v1 alternating_context packed_fmix64 262144 262144 32 21 4096 14 12 245.415 0198d5f56fda1c27 33.051 2.173 11233322 0.40495682 0 1024 0 0 0.07516484 0.00452929 0.70891989 0.73270967 0.02331376 2 4 0.00000000 0.73270967 0.08866256 0.73960000 32 32 false +backend-v1 alternating_context nthash2 262144 262144 32 21 512 14 12 233.922 a8489a6039d5148d 11.391 0.214 1886250 0.76989746 0 1024 0 0 0.16390173 0.02087025 0.72637583 0.83884986 0.09217191 2 13 0.00000000 0.83884986 0.08743793 0.74928129 32 32 false +backend-v1 alternating_context nthash2 262144 262144 32 21 1024 14 12 233.922 a8489a6039d5148d 16.238 0.564 3221546 0.70285034 0 1024 0 0 0.12415576 0.01222213 0.70893629 0.78271102 0.07843654 2 16 0.00000000 0.78271102 0.05931892 0.74710669 32 32 false +backend-v1 alternating_context nthash2 262144 262144 32 21 2048 14 12 233.922 a8489a6039d5148d 23.709 0.984 5892138 0.57888794 0 1024 0 0 0.09185748 0.00984117 0.70892809 0.73270967 0.05312045 2 4 0.00000000 0.73270967 0.07374461 0.74072584 32 32 false +backend-v1 alternating_context nthash2 262144 262144 32 21 4096 14 12 233.922 a8489a6039d5148d 33.078 2.100 11233322 0.40557861 0 1024 0 0 0.03134557 0.00485883 0.02973654 0.72637583 0.04268011 0 18 0.00000000 0.72637583 0.08289292 0.69730000 32 32 false +backend-v1 alternating_context mashmap_murmur3 262144 262144 32 21 512 14 12 21.974 608b42a65a2c5d03 32.476 0.218 1886250 0.71569824 0 1024 0 0 0.12227002 0.02346822 0.70893629 0.98175081 0.10878011 31 26 0.00000000 0.98175081 0.12637324 0.77520000 32 32 false +backend-v1 alternating_context mashmap_murmur3 262144 262144 32 21 1024 14 12 21.974 608b42a65a2c5d03 36.691 0.623 3221546 0.63131714 0 1024 0 0 0.09534774 0.01923965 0.70892809 0.73270967 0.07711376 2 8 0.00000000 0.73270967 0.09923530 0.74970000 32 32 false +backend-v1 alternating_context mashmap_murmur3 262144 262144 32 21 2048 14 12 21.974 608b42a65a2c5d03 43.574 0.986 5892138 0.51060486 0 1024 0 0 0.07294287 0.00972594 0.70891989 0.73270967 0.07851376 2 8 0.00000000 0.73270967 0.10276940 0.74862235 32 32 false +backend-v1 alternating_context mashmap_murmur3 262144 262144 32 21 4096 14 12 21.974 608b42a65a2c5d03 52.123 2.113 11233322 0.36759186 0 1024 0 0 0.02081447 0.00653973 0.03373654 0.70892809 0.05711376 18 2 0.00000000 0.70892809 0.09438896 0.73000000 32 32 false +backend-v1 unequal_length_padding packed_fmix64 262144 196608 32 21 512 14 12 153.110 bccbe7e25f8694d3 12.675 0.134 1886256 0.87500000 0 1024 0 0 0.00017187 0.00000000 0.00113178 0.00626822 0.00626822 1 0 0.97380000 0.96753178 0.00016755 0.00121956 24 24 true +backend-v1 unequal_length_padding packed_fmix64 262144 196608 32 21 1024 14 12 153.110 bccbe7e25f8694d3 18.826 0.440 3221552 0.87461853 0 1024 0 0 0.00012665 0.00000000 0.00076822 0.00556822 0.00556822 10 9 0.97310000 0.96753178 0.00011582 0.00100000 24 24 true +backend-v1 unequal_length_padding packed_fmix64 262144 196608 32 21 2048 14 12 153.110 bccbe7e25f8694d3 29.847 0.708 5892144 0.85845184 0 1024 0 0 0.00011657 0.00000000 0.00090000 0.00356822 0.00356822 1 0 0.97110000 0.96753178 0.00008595 0.00065364 24 24 true +backend-v1 unequal_length_padding packed_fmix64 262144 196608 32 21 4096 14 12 153.110 bccbe7e25f8694d3 49.861 1.596 11233328 0.75522995 0 1024 0 0 0.00008633 0.00000000 0.00066822 0.00296822 0.00296822 2 1 0.97050000 0.96753178 0.00008789 0.00080000 24 24 true +backend-v1 unequal_length_padding nthash2 262144 196608 32 21 512 14 12 225.689 60229541bbff5efe 11.905 0.147 1886256 0.87500000 0 1024 0 0 0.00015066 0.00000000 0.00073178 0.00826822 0.00826822 16 15 0.97580000 0.96753178 0.00018604 0.00151956 24 24 true +backend-v1 unequal_length_padding nthash2 262144 196608 32 21 1024 14 12 225.689 60229541bbff5efe 18.357 0.462 3221552 0.87469482 0 1024 0 0 0.00014004 0.00000000 0.00083178 0.00586822 0.00586822 20 19 0.97340000 0.96753178 0.00017439 0.00143152 24 24 true +backend-v1 unequal_length_padding nthash2 262144 196608 32 21 2048 14 12 225.689 60229541bbff5efe 29.307 0.745 5892144 0.85975647 0 1024 0 0 0.00009588 0.00000000 0.00060000 0.00406822 0.00406822 20 19 0.97160000 0.96753178 0.00013203 0.00110000 24 24 true +backend-v1 unequal_length_padding nthash2 262144 196608 32 21 4096 14 12 225.689 60229541bbff5efe 49.271 1.592 11233328 0.75695419 0 1024 0 0 0.00007285 0.00000000 0.00056822 0.00276822 0.00276822 20 19 0.97030000 0.96753178 0.00012436 0.00090171 24 24 true +backend-v1 unequal_length_padding mashmap_murmur3 262144 196608 32 21 512 14 12 9.128 a7823631067700ba 59.069 0.139 1886256 0.84848022 0 1024 0 0 0.00020312 0.00000000 0.00226822 0.00606822 0.00606822 20 19 0.97360000 0.96753178 0.00019040 0.00151649 24 24 true +backend-v1 unequal_length_padding mashmap_murmur3 262144 196608 32 21 1024 14 12 9.128 a7823631067700ba 64.734 0.440 3221552 0.82058716 0 1024 0 0 0.00015008 0.00000000 0.00096822 0.00866822 0.00866822 20 19 0.97620000 0.96753178 0.00014985 0.00117586 24 24 true +backend-v1 unequal_length_padding mashmap_murmur3 262144 196608 32 21 2048 14 12 9.128 a7823631067700ba 75.127 0.726 5892144 0.76489258 0 1024 0 0 0.00008515 0.00000000 0.00033178 0.00476822 0.00476822 20 19 0.97230000 0.96753178 0.00010985 0.00097122 24 24 true +backend-v1 unequal_length_padding mashmap_murmur3 262144 196608 32 21 4096 14 12 9.128 a7823631067700ba 91.684 1.597 11233328 0.65954590 0 1024 0 0 0.00007689 0.00000000 0.00040000 0.00296822 0.00296822 21 20 0.97050000 0.96753178 0.00010335 0.00073998 24 24 true diff --git a/docs/benchmarks/backend-smoke.tsv b/docs/benchmarks/backend-smoke.tsv new file mode 100644 index 0000000..eba3410 --- /dev/null +++ b/docs/benchmarks/backend-smoke.tsv @@ -0,0 +1,181 @@ +schema fixture hash x_bases y_bases resolution k registers b_bits hll_precision hash_mkmer_s hash_checksum build_ms matrix_median_ms bytes mean_core_occupancy fingerprint32_collisions cells false_missing false_present mean_abs_error p50_abs_error p95_abs_error max_abs_error max_overestimate worst_column worst_row worst_estimate worst_exact mashmap_mean_abs_error mashmap_p95_abs_error direction_correct_cells direction_tested_cells passed +backend-v1 identity packed_fmix64 32768 32768 8 21 512 14 12 147.306 71ad71c2586370c6 3.217 0.017 471572 0.99926758 0 64 0 0 0.00067038 0.00000000 0.00396822 0.00644268 0.00644268 7 6 0.97420000 0.96775732 0.00091037 0.00467711 8 8 true +backend-v1 identity packed_fmix64 32768 32768 8 21 1024 14 12 147.306 71ad71c2586370c6 4.528 0.035 805396 0.98291016 0 64 0 0 0.00044538 0.00000000 0.00234268 0.00386822 0.00386822 5 4 0.97140000 0.96753178 0.00062287 0.00379748 8 8 true +backend-v1 identity packed_fmix64 32768 32768 8 21 2048 14 12 147.306 71ad71c2586370c6 7.475 0.063 1473044 0.86523438 0 64 0 0 0.00034892 0.00000000 0.00236822 0.00266822 0.00266822 5 4 0.97020000 0.96753178 0.00061196 0.00402912 8 8 true +backend-v1 identity packed_fmix64 32768 32768 8 21 4096 14 12 147.306 71ad71c2586370c6 11.242 0.126 2808340 0.63104248 0 64 0 0 0.00018799 0.00000000 0.00116822 0.00186822 0.00186822 5 4 0.96940000 0.96753178 0.00069477 0.00428023 8 8 true +backend-v1 identity nthash2 32768 32768 8 21 512 14 12 241.311 ee09eaf9b1877128 2.650 0.014 471572 0.99926758 0 64 0 0 0.00085006 0.00000000 0.00516822 0.00696822 0.00696822 1 0 0.97450000 0.96753178 0.00067351 0.00251977 8 8 true +backend-v1 identity nthash2 32768 32768 8 21 1024 14 12 241.311 ee09eaf9b1877128 4.314 0.028 805396 0.98400879 0 64 0 0 0.00070052 0.00000000 0.00456822 0.00686822 0.00686822 6 5 0.97440000 0.96753178 0.00069585 0.00594092 8 8 true +backend-v1 identity nthash2 32768 32768 8 21 2048 14 12 241.311 ee09eaf9b1877128 7.090 0.063 1473044 0.86804199 0 64 0 0 0.00038756 0.00000000 0.00196822 0.00356822 0.00356822 1 0 0.97110000 0.96753178 0.00057899 0.00379748 8 8 true +backend-v1 identity nthash2 32768 32768 8 21 4096 14 12 241.311 ee09eaf9b1877128 10.599 0.137 2808340 0.63327026 0 64 0 0 0.00027663 0.00000000 0.00136822 0.00336822 0.00336822 1 0 0.97090000 0.96753178 0.00052430 0.00409748 8 8 true +backend-v1 identity mashmap_murmur3 32768 32768 8 21 512 14 12 9.134 83b36c7b87b3455a 9.474 0.013 471572 0.93896484 0 64 0 0 0.00065986 0.00000000 0.00516822 0.00616822 0.00616822 5 4 0.97370000 0.96753178 0.00069145 0.00364092 8 8 true +backend-v1 identity mashmap_murmur3 32768 32768 8 21 1024 14 12 9.134 83b36c7b87b3455a 10.925 0.035 805396 0.87353516 0 64 0 0 0.00046455 0.00000000 0.00296822 0.00536822 0.00536822 1 0 0.97290000 0.96753178 0.00048648 0.00344092 8 8 true +backend-v1 identity mashmap_murmur3 32768 32768 8 21 2048 14 12 9.134 83b36c7b87b3455a 13.155 0.060 1473044 0.75262451 0 64 0 0 0.00036227 0.00000000 0.00266822 0.00286822 0.00286822 6 5 0.97040000 0.96753178 0.00046649 0.00319748 8 8 true +backend-v1 identity mashmap_murmur3 32768 32768 8 21 4096 14 12 9.134 83b36c7b87b3455a 16.670 0.135 2808340 0.56759644 0 64 0 0 0.00023486 0.00000000 0.00116822 0.00246822 0.00246822 6 5 0.97000000 0.96753178 0.00064305 0.00384092 8 8 true +backend-v1 substitution_01pct packed_fmix64 32768 32768 8 21 512 14 12 163.281 d53f6b3b4f9979ed 2.845 0.013 471592 0.99963379 0 64 0 0 0.00065764 0.00000000 0.00423795 0.00593795 0.00593795 2 1 0.96570000 0.95976205 0.00121031 0.00578794 8 8 true +backend-v1 substitution_01pct packed_fmix64 32768 32768 8 21 1024 14 12 163.281 d53f6b3b4f9979ed 4.337 0.035 805416 0.98291016 0 64 0 0 0.00051606 0.00000000 0.00282565 0.00413502 0.00413502 5 4 0.96190000 0.95776498 0.00095318 0.00482782 8 8 true +backend-v1 substitution_01pct packed_fmix64 32768 32768 8 21 2048 14 12 163.281 d53f6b3b4f9979ed 7.346 0.066 1473064 0.86477661 0 64 0 0 0.00031364 0.00000000 0.00166271 0.00193502 0.00193502 4 5 0.95970000 0.95776498 0.00093632 0.00478794 8 8 true +backend-v1 substitution_01pct packed_fmix64 32768 32768 8 21 4096 14 12 163.281 d53f6b3b4f9979ed 10.724 0.125 2808360 0.63151550 0 64 0 0 0.00022856 0.00000000 0.00142334 0.00182334 0.00182334 3 2 0.96140000 0.95957666 0.00092906 0.00454816 8 8 true +backend-v1 substitution_01pct nthash2 32768 32768 8 21 512 14 12 237.448 cf153e51d0e7b7df 2.762 0.013 471592 0.99951172 0 64 0 0 0.00088587 0.00000000 0.00493795 0.00593795 0.00593795 2 1 0.96570000 0.95976205 0.00110266 0.00623178 8 8 true +backend-v1 substitution_01pct nthash2 32768 32768 8 21 1024 14 12 237.448 cf153e51d0e7b7df 4.265 0.032 805416 0.98291016 0 64 0 0 0.00061766 0.00000000 0.00243833 0.00717810 0.00717810 5 6 0.96530000 0.95812190 0.00105125 0.00527782 8 8 true +backend-v1 substitution_01pct nthash2 32768 32768 8 21 2048 14 12 237.448 cf153e51d0e7b7df 7.010 0.061 1473064 0.86642456 0 64 0 0 0.00039259 0.00000000 0.00173833 0.00353502 0.00353502 4 5 0.96130000 0.95776498 0.00082538 0.00375560 8 8 true +backend-v1 substitution_01pct nthash2 32768 32768 8 21 4096 14 12 237.448 cf153e51d0e7b7df 10.673 0.123 2808360 0.63244629 0 64 0 0 0.00033397 0.00000000 0.00173502 0.00300710 0.00300710 1 0 0.96170000 0.95869290 0.00083876 0.00368794 8 8 true +backend-v1 substitution_01pct mashmap_murmur3 32768 32768 8 21 512 14 12 9.004 3ea0b6b1b75a6f36 9.440 0.013 471592 0.94006348 0 64 0 0 0.00090092 0.00000000 0.00357553 0.01063502 0.01063502 4 5 0.96840000 0.95776498 0.00118809 0.00543178 8 8 true +backend-v1 substitution_01pct mashmap_murmur3 32768 32768 8 21 1024 14 12 9.004 3ea0b6b1b75a6f36 10.913 0.047 805416 0.87438965 0 64 0 0 0.00074429 0.00000000 0.00357553 0.00590710 0.00590710 0 1 0.96460000 0.95869290 0.00095035 0.00408785 8 8 true +backend-v1 substitution_01pct mashmap_murmur3 32768 32768 8 21 2048 14 12 9.004 3ea0b6b1b75a6f36 13.227 0.059 1473064 0.75405884 0 64 0 0 0.00060246 0.00000000 0.00307810 0.00420710 0.00420710 0 1 0.96290000 0.95869290 0.00077414 0.00338437 8 8 true +backend-v1 substitution_01pct mashmap_murmur3 32768 32768 8 21 4096 14 12 9.004 3ea0b6b1b75a6f36 16.461 0.122 2808360 0.56770325 0 64 0 0 0.00043217 0.00000000 0.00240710 0.00317810 0.00317810 5 6 0.96130000 0.95812190 0.00083953 0.00368785 8 8 true +backend-v1 substitution_05pct packed_fmix64 32768 32768 8 21 512 14 12 158.107 895c1bbe565d5741 2.908 0.013 471592 0.99951172 0 64 0 0 0.00176505 0.00000000 0.00740770 0.01541883 0.01541883 6 7 0.93880000 0.92338117 0.00183598 0.00939667 8 8 true +backend-v1 substitution_05pct packed_fmix64 32768 32768 8 21 1024 14 12 158.107 895c1bbe565d5741 4.537 0.041 805416 0.98187256 0 64 0 0 0.00111129 0.00000000 0.00538773 0.00811883 0.00811883 7 6 0.93150000 0.92338117 0.00117348 0.00524379 8 8 true +backend-v1 substitution_05pct packed_fmix64 32768 32768 8 21 2048 14 12 158.107 895c1bbe565d5741 7.123 0.062 1473064 0.86395264 0 64 0 0 0.00059518 0.00000000 0.00319790 0.00447031 0.00447031 2 3 0.92290000 0.91842969 0.00117323 0.00621917 8 8 true +backend-v1 substitution_05pct packed_fmix64 32768 32768 8 21 4096 14 12 158.107 895c1bbe565d5741 10.825 0.125 2808360 0.63090515 0 64 0 0 0.00055427 0.00000000 0.00256949 0.00599790 0.00599790 0 1 0.92320000 0.91720210 0.00131732 0.00861917 8 8 true +backend-v1 substitution_05pct nthash2 32768 32768 8 21 512 14 12 243.895 b9f35d43e2f04427 2.688 0.013 471592 0.99938965 0 64 0 0 0.00091439 0.00000000 0.00421883 0.00741810 0.00569790 1 2 0.92090000 0.92831810 0.00138681 0.00764379 8 8 true +backend-v1 substitution_05pct nthash2 32768 32768 8 21 1024 14 12 243.895 b9f35d43e2f04427 4.330 0.035 805416 0.98352051 0 64 0 0 0.00093034 0.00000000 0.00371883 0.00719790 0.00719790 1 0 0.92440000 0.91720210 0.00150723 0.00738332 8 8 true +backend-v1 substitution_05pct nthash2 32768 32768 8 21 2048 14 12 243.895 b9f35d43e2f04427 7.077 0.061 1473064 0.86538696 0 64 0 0 0.00077981 0.00000000 0.00410210 0.00449230 0.00428761 6 5 0.91690000 0.92139230 0.00168417 0.00938332 8 8 true +backend-v1 substitution_05pct nthash2 32768 32768 8 21 4096 14 12 243.895 b9f35d43e2f04427 10.586 0.136 2808360 0.63255310 0 64 0 0 0.00034315 0.00000000 0.00186346 0.00239230 0.00218761 5 6 0.91900000 0.92139230 0.00147272 0.00811917 8 8 true +backend-v1 substitution_05pct mashmap_murmur3 32768 32768 8 21 512 14 12 9.172 1c73c55c3b8d725c 9.413 0.013 471592 0.93945312 0 64 0 0 0.00165557 0.00000000 0.00907640 0.01258773 0.01258773 5 4 0.93710000 0.92451227 0.00164423 0.00834379 8 8 true +backend-v1 substitution_05pct mashmap_murmur3 32768 32768 8 21 1024 14 12 9.172 1c73c55c3b8d725c 10.881 0.033 805416 0.87420654 0 64 0 0 0.00135634 0.00000000 0.00607640 0.00929790 0.00929790 1 0 0.92650000 0.91720210 0.00138344 0.00769667 8 8 true +backend-v1 substitution_05pct mashmap_murmur3 32768 32768 8 21 2048 14 12 9.172 1c73c55c3b8d725c 13.174 0.067 1473064 0.75253296 0 64 0 0 0.00095743 0.00000000 0.00409790 0.00758773 0.00758773 5 4 0.93210000 0.92451227 0.00140446 0.00658332 8 8 true +backend-v1 substitution_05pct mashmap_murmur3 32768 32768 8 21 4096 14 12 9.172 1c73c55c3b8d725c 16.455 0.126 2808360 0.56613159 0 64 0 0 0.00075711 0.00000000 0.00321810 0.00579790 0.00579790 0 1 0.92300000 0.91720210 0.00138529 0.00712308 8 8 true +backend-v1 substitution_10pct packed_fmix64 32768 32768 8 21 512 14 12 150.956 f8462ef8e8749d05 2.870 0.013 471592 0.99951172 0 64 0 0 0.00259894 0.00000000 0.01197574 0.01560778 0.01560778 1 1 0.90800000 0.89239222 0.00341124 0.02030233 8 8 true +backend-v1 substitution_10pct packed_fmix64 32768 32768 8 21 1024 14 12 150.956 f8462ef8e8749d05 4.372 0.032 805416 0.98095703 0 64 0 0 0.00204722 0.00000000 0.01089615 0.01174063 0.01174063 4 4 0.91780000 0.90605937 0.00356639 0.01904323 8 8 true +backend-v1 substitution_10pct packed_fmix64 32768 32768 8 21 2048 14 12 150.956 f8462ef8e8749d05 7.178 0.066 1473064 0.86236572 0 64 0 0 0.00086760 0.00000000 0.00362942 0.00730778 0.00730778 1 1 0.89970000 0.89239222 0.00304144 0.01355866 8 8 true +backend-v1 substitution_10pct packed_fmix64 32768 32768 8 21 4096 14 12 150.956 f8462ef8e8749d05 10.841 0.126 2808360 0.63101196 0 64 0 0 0.00088284 0.00000000 0.00410385 0.00559372 0.00525713 2 1 0.86990000 0.87549372 0.00352007 0.01485866 8 8 true +backend-v1 substitution_10pct nthash2 32768 32768 8 21 512 14 12 233.117 f3fd506f732266c8 2.715 0.012 471592 0.99938965 0 64 0 0 0.00396792 0.00000000 0.01861993 0.04682426 0.01788062 6 5 0.83500000 0.88182426 0.00368699 0.01345677 8 8 true +backend-v1 substitution_10pct nthash2 32768 32768 8 21 1024 14 12 233.117 f3fd506f732266c8 4.375 0.038 805416 0.98309326 0 64 0 0 0.00266474 0.00000000 0.01047534 0.02194287 0.01047534 4 5 0.85550000 0.87744287 0.00283859 0.01231437 8 8 true +backend-v1 substitution_10pct nthash2 32768 32768 8 21 2048 14 12 233.117 f3fd506f732266c8 7.048 0.062 1473064 0.86676025 0 64 0 0 0.00161107 0.00000000 0.01001938 0.01249372 0.00458007 1 2 0.86300000 0.87549372 0.00312430 0.01380233 8 8 true +backend-v1 substitution_10pct nthash2 32768 32768 8 21 4096 14 12 233.117 f3fd506f732266c8 10.690 0.123 2808360 0.63221741 0 64 0 0 0.00072584 0.00000000 0.00324063 0.00719372 0.00324063 1 2 0.86830000 0.87549372 0.00320398 0.01580233 8 8 true +backend-v1 substitution_10pct mashmap_murmur3 32768 32768 8 21 512 14 12 9.143 e320aea69b2e597b 9.387 0.013 471592 0.93908691 0 64 0 0 0.00264974 0.00000000 0.01307574 0.01805713 0.01805713 5 4 0.89550000 0.87744287 0.00413639 0.02394323 8 8 true +backend-v1 substitution_10pct mashmap_murmur3 32768 32768 8 21 1024 14 12 9.143 e320aea69b2e597b 10.861 0.033 805416 0.87585449 0 64 0 0 0.00168455 0.00000000 0.01035713 0.01245713 0.01245713 5 4 0.88990000 0.87744287 0.00325541 0.01411437 8 8 true +backend-v1 substitution_10pct mashmap_murmur3 32768 32768 8 21 2048 14 12 9.143 e320aea69b2e597b 13.270 0.061 1473064 0.75460815 0 64 0 0 0.00122675 0.00000000 0.00623637 0.00815713 0.00815713 5 4 0.88560000 0.87744287 0.00347218 0.01757890 8 8 true +backend-v1 substitution_10pct mashmap_murmur3 32768 32768 8 21 4096 14 12 9.143 e320aea69b2e597b 16.398 0.123 2808360 0.56825256 0 64 0 0 0.00110789 0.00000000 0.00480628 0.00653637 0.00653637 7 6 0.88240000 0.87586363 0.00355880 0.01820233 8 8 true +backend-v1 indel_05pct packed_fmix64 32768 32768 8 21 512 14 12 159.795 0ea1e814088913e9 2.944 0.012 471578 0.99926758 0 64 0 0 0.00532538 0.00000000 0.02936416 0.04557171 0.04557171 5 6 0.85140000 0.80582829 0.00615482 0.03020628 8 8 true +backend-v1 indel_05pct packed_fmix64 32768 32768 8 21 1024 14 12 159.795 0ea1e814088913e9 4.447 0.030 805402 0.98309326 0 64 0 0 0.00496334 0.00000000 0.02852592 0.03676416 0.03676416 1 0 0.84430000 0.80753584 0.00623494 0.03308965 8 8 true +backend-v1 indel_05pct packed_fmix64 32768 32768 8 21 2048 14 12 159.795 0ea1e814088913e9 7.271 0.062 1473050 0.86337280 0 64 0 0 0.00385243 0.00000000 0.02086416 0.02756416 0.02756416 1 0 0.83510000 0.80753584 0.00653160 0.03468687 8 8 true +backend-v1 indel_05pct packed_fmix64 32768 32768 8 21 4096 14 12 159.795 0ea1e814088913e9 10.750 0.126 2808346 0.63098145 0 64 0 0 0.00227128 0.00000000 0.01277171 0.01492592 0.01492592 1 1 0.84500000 0.83007408 0.00723961 0.03671831 8 8 true +backend-v1 indel_05pct nthash2 32768 32768 8 21 512 14 12 241.906 184685a4cb4ac37f 2.680 0.012 471578 0.99951172 0 64 0 0 0.03023976 0.00000000 0.03047171 0.80404517 0.04667171 4 3 0.00000000 0.80404517 0.03126198 0.03051537 8 8 false +backend-v1 indel_05pct nthash2 32768 32768 8 21 1024 14 12 241.906 184685a4cb4ac37f 4.201 0.038 805402 0.98327637 0 64 0 0 0.00544846 0.00000000 0.03047171 0.04894517 0.03927171 3 4 0.75510000 0.80404517 0.00780718 0.03775436 8 8 true +backend-v1 indel_05pct nthash2 32768 32768 8 21 2048 14 12 241.906 184685a4cb4ac37f 7.104 0.062 1473050 0.86682129 0 64 0 0 0.00292148 0.00000000 0.01262592 0.02517171 0.02517171 5 6 0.83100000 0.80582829 0.00713123 0.03382287 8 8 true +backend-v1 indel_05pct nthash2 32768 32768 8 21 4096 14 12 241.906 184685a4cb4ac37f 10.675 0.136 2808346 0.63267517 0 64 0 0 0.00203309 0.00000000 0.01032829 0.01646416 0.01646416 1 0 0.82400000 0.80753584 0.00738875 0.03872287 8 8 true +backend-v1 indel_05pct mashmap_murmur3 32768 32768 8 21 512 14 12 9.118 e4616b187969067d 9.424 0.012 471578 0.93933105 0 64 0 0 0.03115307 0.00000000 0.03097171 0.81972040 0.03950890 3 3 0.00000000 0.81972040 0.03268110 0.05245436 7 7 false +backend-v1 indel_05pct mashmap_murmur3 32768 32768 8 21 1024 14 12 9.118 e4616b187969067d 10.776 0.034 805402 0.87384033 0 64 0 0 0.00395913 0.00000000 0.02072829 0.03146416 0.03146416 1 0 0.83900000 0.80753584 0.00671255 0.04432572 8 8 true +backend-v1 indel_05pct mashmap_murmur3 32768 32768 8 21 2048 14 12 9.118 e4616b187969067d 13.196 0.061 1473050 0.75408936 0 64 0 0 0.00302319 0.00000000 0.01387960 0.02117171 0.02117171 2 3 0.82700000 0.80582829 0.00745891 0.03568687 8 8 true +backend-v1 indel_05pct mashmap_murmur3 32768 32768 8 21 4096 14 12 9.118 e4616b187969067d 16.380 0.124 2808346 0.56723022 0 64 0 0 0.00217315 0.00000000 0.01014517 0.01494517 0.01197695 3 4 0.78910000 0.80404517 0.00799067 0.03245559 8 8 true +backend-v1 offset_1kb packed_fmix64 32768 32768 8 21 512 14 12 160.072 f63a843c67943b7f 2.959 0.013 471576 0.99951172 0 64 0 0 0.00091838 0.00000000 0.00558226 0.01096404 0.01096404 6 7 0.94730000 0.93633596 0.00089826 0.00733000 8 8 true +backend-v1 offset_1kb packed_fmix64 32768 32768 8 21 1024 14 12 160.072 f63a843c67943b7f 4.445 0.032 805400 0.98205566 0 64 0 0 0.00059568 0.00000000 0.00414309 0.00728226 0.00728226 1 2 0.94340000 0.93611774 0.00083075 0.00513125 8 8 true +backend-v1 offset_1kb packed_fmix64 32768 32768 8 21 2048 14 12 160.072 f63a843c67943b7f 7.152 0.061 1473048 0.86340332 0 64 0 0 0.00036461 0.00000000 0.00158226 0.00608226 0.00608226 1 2 0.94220000 0.93611774 0.00088126 0.00654409 8 8 true +backend-v1 offset_1kb packed_fmix64 32768 32768 8 21 4096 14 12 160.072 f63a843c67943b7f 10.742 0.122 2808344 0.63070679 0 64 0 0 0.00016287 0.00000000 0.00140574 0.00218226 0.00218226 4 5 0.93830000 0.93611774 0.00084532 0.00503125 8 8 true +backend-v1 offset_1kb nthash2 32768 32768 8 21 512 14 12 244.085 4e13d5f215c6831b 2.722 0.013 471576 0.99963379 0 64 0 0 0.00072812 0.00000000 0.00498226 0.01078226 0.01078226 0 1 0.94690000 0.93611774 0.00069101 0.00443228 8 8 true +backend-v1 offset_1kb nthash2 32768 32768 8 21 1024 14 12 244.085 4e13d5f215c6831b 4.280 0.034 805400 0.98266602 0 64 0 0 0.00074891 0.00000000 0.00498226 0.00688226 0.00688226 0 1 0.94300000 0.93611774 0.00100690 0.00673125 8 8 true +backend-v1 offset_1kb nthash2 32768 32768 8 21 2048 14 12 244.085 4e13d5f215c6831b 6.990 0.061 1473048 0.86614990 0 64 0 0 0.00032784 0.00000000 0.00150574 0.00428226 0.00428226 0 1 0.94040000 0.93611774 0.00082501 0.00553125 8 8 true +backend-v1 offset_1kb nthash2 32768 32768 8 21 4096 14 12 244.085 4e13d5f215c6831b 10.690 0.130 2808344 0.63282776 0 64 0 0 0.00025229 0.00000000 0.00136404 0.00278226 0.00278226 0 1 0.93890000 0.93611774 0.00078438 0.00483125 8 8 true +backend-v1 offset_1kb mashmap_murmur3 32768 32768 8 21 512 14 12 9.113 2059497f210f5a5e 9.475 0.015 471576 0.93798828 0 64 0 0 0.00099194 0.00000000 0.00798226 0.01088226 0.01088226 4 5 0.94700000 0.93611774 0.00123388 0.00698974 8 8 true +backend-v1 offset_1kb mashmap_murmur3 32768 32768 8 21 1024 14 12 9.113 2059497f210f5a5e 10.900 0.032 805400 0.87304688 0 64 0 0 0.00064625 0.00000000 0.00478226 0.00845691 0.00838226 0 7 0.92700000 0.93545691 0.00079794 0.00414409 8 8 true +backend-v1 offset_1kb mashmap_murmur3 32768 32768 8 21 2048 14 12 9.113 2059497f210f5a5e 13.221 0.061 1473048 0.75189209 0 64 0 0 0.00048063 0.00000000 0.00248226 0.00818226 0.00818226 0 1 0.94430000 0.93611774 0.00088590 0.00473000 8 8 true +backend-v1 offset_1kb mashmap_murmur3 32768 32768 8 21 4096 14 12 9.113 2059497f210f5a5e 16.413 0.124 2808344 0.56733704 0 64 0 0 0.00040444 0.00000000 0.00238226 0.00518226 0.00518226 0 1 0.94130000 0.93611774 0.00093018 0.00633125 8 8 true +backend-v1 central_inversion packed_fmix64 32768 32768 8 21 512 14 12 162.219 d4a735207cbba64c 2.889 0.012 471590 0.99926758 0 64 0 0 0.01140584 0.00000000 0.00527974 0.67295010 0.00644268 5 5 0.00000000 0.67295010 0.00139023 0.00654759 0 0 false +backend-v1 central_inversion packed_fmix64 32768 32768 8 21 1024 14 12 162.219 d4a735207cbba64c 4.371 0.035 805414 0.98303223 0 64 0 0 0.01098311 0.00000000 0.00234268 0.67295010 0.00372026 5 5 0.00000000 0.67295010 0.00091664 0.00521995 0 0 false +backend-v1 central_inversion packed_fmix64 32768 32768 8 21 2048 14 12 162.219 d4a735207cbba64c 7.230 0.062 1473062 0.86526489 0 64 0 0 0.01091381 0.00000000 0.00236822 0.67295010 0.00262026 5 5 0.00000000 0.67295010 0.00088871 0.00432138 0 0 false +backend-v1 central_inversion packed_fmix64 32768 32768 8 21 4096 14 12 162.219 d4a735207cbba64c 10.707 0.138 2808358 0.63095093 0 64 0 0 0.00071784 0.00000000 0.00182026 0.02944990 0.02944990 5 5 0.70240000 0.67295010 0.01201215 0.00528375 0 0 true +backend-v1 central_inversion nthash2 32768 32768 8 21 512 14 12 235.421 d38182afb0b16c62 2.704 0.013 471590 0.99926758 0 64 0 0 0.01141790 0.00000000 0.00512037 0.67295010 0.00696822 5 5 0.00000000 0.67295010 0.00104159 0.00742138 0 0 false +backend-v1 central_inversion nthash2 32768 32768 8 21 1024 14 12 235.421 d38182afb0b16c62 4.127 0.035 805414 0.98400879 0 64 0 0 0.01138657 0.00000000 0.00472037 0.67295010 0.00686822 5 5 0.00000000 0.67295010 0.00122520 0.00666046 0 0 false +backend-v1 central_inversion nthash2 32768 32768 8 21 2048 14 12 235.421 d38182afb0b16c62 7.004 0.061 1473062 0.86807251 0 64 0 0 0.01101054 0.00000000 0.00282026 0.67295010 0.00356822 5 5 0.00000000 0.67295010 0.00103402 0.00581995 0 0 false +backend-v1 central_inversion nthash2 32768 32768 8 21 4096 14 12 235.421 d38182afb0b16c62 10.686 0.124 2808358 0.63316345 0 64 0 0 0.01081671 0.00000000 0.00183178 0.67295010 0.00336822 5 5 0.00000000 0.67295010 0.00091840 0.00512138 0 0 false +backend-v1 central_inversion mashmap_murmur3 32768 32768 8 21 512 14 12 9.301 350d3c7a475b57b5 9.571 0.013 471590 0.93884277 0 64 0 0 0.01141776 0.00000000 0.00542026 0.67295010 0.00662037 5 5 0.00000000 0.67295010 0.00096668 0.00622138 0 0 false +backend-v1 central_inversion mashmap_murmur3 32768 32768 8 21 1024 14 12 9.301 350d3c7a475b57b5 10.763 0.035 805414 0.87347412 0 64 0 0 0.01118339 0.00000000 0.00400244 0.67295010 0.00556822 5 5 0.00000000 0.67295010 0.00082672 0.00474759 0 0 false +backend-v1 central_inversion mashmap_murmur3 32768 32768 8 21 2048 14 12 9.301 350d3c7a475b57b5 13.131 0.062 1473062 0.75250244 0 64 0 0 0.01099335 0.00000000 0.00286822 0.67295010 0.00326822 5 5 0.00000000 0.67295010 0.00089023 0.00389494 0 0 false +backend-v1 central_inversion mashmap_murmur3 32768 32768 8 21 4096 14 12 9.301 350d3c7a475b57b5 16.445 0.124 2808358 0.56758118 0 64 0 0 0.01081202 0.00000000 0.00240244 0.67295010 0.00246822 5 5 0.00000000 0.67295010 0.00089023 0.00429748 0 0 false +backend-v1 reverse_complement packed_fmix64 32768 32768 8 21 512 14 12 164.374 d83ccc49ea17b8ad 2.915 0.013 471592 0.99926758 0 64 0 0 0.00064584 0.00000000 0.00382037 0.00632037 0.00632037 6 0 0.97430000 0.96797963 0.00085500 0.00413118 2 2 true +backend-v1 reverse_complement packed_fmix64 32768 32768 8 21 1024 14 12 164.374 d83ccc49ea17b8ad 4.451 0.032 805416 0.98297119 0 64 0 0 0.00042552 0.00000000 0.00212037 0.00382037 0.00382037 4 2 0.97180000 0.96797963 0.00059934 0.00378672 2 2 true +backend-v1 reverse_complement packed_fmix64 32768 32768 8 21 2048 14 12 164.374 d83ccc49ea17b8ad 7.138 0.060 1473064 0.86511230 0 64 0 0 0.00034300 0.00000000 0.00209482 0.00262026 0.00262026 5 3 0.96970000 0.96707974 0.00058755 0.00368672 2 2 true +backend-v1 reverse_complement packed_fmix64 32768 32768 8 21 4096 14 12 164.374 d83ccc49ea17b8ad 10.824 0.123 2808360 0.63088989 0 64 0 0 0.00019613 0.00000000 0.00112026 0.00182026 0.00182026 5 3 0.96890000 0.96707974 0.00064380 0.00398672 2 2 true +backend-v1 reverse_complement nthash2 32768 32768 8 21 512 14 12 230.890 c40509237f18f381 2.685 0.012 471592 0.99926758 0 64 0 0 0.00083177 0.00000000 0.00472026 0.00739482 0.00739482 1 7 0.97470000 0.96730518 0.00069930 0.00333678 2 2 true +backend-v1 reverse_complement nthash2 32768 32768 8 21 1024 14 12 230.890 c40509237f18f381 4.231 0.047 805416 0.98394775 0 64 0 0 0.00069067 0.00000000 0.00462037 0.00662037 0.00662037 5 1 0.97460000 0.96797963 0.00069126 0.00515301 2 2 true +backend-v1 reverse_complement nthash2 32768 32768 8 21 2048 14 12 230.890 c40509237f18f381 6.996 0.061 1473064 0.86791992 0 64 0 0 0.00039740 0.00000000 0.00202037 0.00389482 0.00389482 1 7 0.97120000 0.96730518 0.00053636 0.00382912 2 2 true +backend-v1 reverse_complement nthash2 32768 32768 8 21 4096 14 12 230.890 c40509237f18f381 10.718 0.123 2808360 0.63310242 0 64 0 0 0.00028021 0.00000000 0.00132026 0.00359482 0.00359482 1 7 0.97090000 0.96730518 0.00049105 0.00379748 2 2 true +backend-v1 reverse_complement mashmap_murmur3 32768 32768 8 21 512 14 12 9.098 8ba75e5efe9b250f 9.405 0.012 471592 0.93896484 0 64 0 0 0.00064769 0.00000000 0.00482037 0.00672037 0.00672037 4 2 0.97470000 0.96797963 0.00064858 0.00295301 2 2 true +backend-v1 reverse_complement mashmap_murmur3 32768 32768 8 21 1024 14 12 9.098 8ba75e5efe9b250f 10.877 0.032 805416 0.87347412 0 64 0 0 0.00046332 0.00000000 0.00262037 0.00589482 0.00589482 1 7 0.97320000 0.96730518 0.00051033 0.00315301 2 2 true +backend-v1 reverse_complement mashmap_murmur3 32768 32768 8 21 2048 14 12 9.098 8ba75e5efe9b250f 13.197 0.068 1473064 0.75256348 0 64 0 0 0.00035673 0.00000000 0.00272037 0.00299482 0.00299482 1 7 0.97030000 0.96730518 0.00043324 0.00242245 2 2 true +backend-v1 reverse_complement mashmap_murmur3 32768 32768 8 21 4096 14 12 9.098 8ba75e5efe9b250f 16.328 0.125 2808360 0.56758118 0 64 0 0 0.00023427 0.00000000 0.00129482 0.00242026 0.00242026 6 2 0.96950000 0.96707974 0.00060511 0.00338023 2 2 true +backend-v1 low_complexity packed_fmix64 32768 32768 8 21 512 14 12 292.230 2913054a0763fe6c 1.137 0.013 471584 0.27575684 0 64 0 0 0.09174639 0.00617086 0.85955893 0.86705409 0.01912266 0 3 0.00000000 0.86705409 0.03912967 0.14361668 8 8 false +backend-v1 low_complexity packed_fmix64 32768 32768 8 21 1024 14 12 292.230 2913054a0763fe6c 1.271 0.035 805408 0.16204834 0 64 0 0 0.06276511 0.00280574 0.85955893 0.86504196 0.01454591 2 4 0.00000000 0.86504196 0.03781174 0.12609753 7 7 false +backend-v1 low_complexity packed_fmix64 32768 32768 8 21 2048 14 12 292.230 2913054a0763fe6c 1.441 0.062 1473056 0.08938599 0 64 0 0 0.06099626 0.00378812 0.85955893 0.86504196 0.00652914 2 4 0.00000000 0.86504196 0.03674999 0.13649753 7 7 false +backend-v1 low_complexity packed_fmix64 32768 32768 8 21 4096 14 12 292.230 2913054a0763fe6c 1.639 0.124 2808352 0.04681396 0 64 0 0 0.06042732 0.00150574 0.85955893 0.86504196 0.00707000 2 4 0.00000000 0.86504196 0.03787480 0.14549753 7 7 false +backend-v1 low_complexity nthash2 32768 32768 8 21 512 14 12 245.533 0e3d3f1ca209c330 1.172 0.013 471584 0.27648926 0 64 0 0 0.13377850 0.00920000 0.86705409 1.00000000 0.02432914 7 4 0.00000000 1.00000000 0.10817744 0.92622333 7 7 false +backend-v1 low_complexity nthash2 32768 32768 8 21 1024 14 12 245.533 0e3d3f1ca209c330 1.320 0.037 805408 0.16326904 0 64 0 0 0.09099037 0.00310574 0.86504196 0.89615050 0.01542914 0 2 0.00000000 0.89615050 0.06509136 0.16432333 7 7 false +backend-v1 low_complexity nthash2 32768 32768 8 21 2048 14 12 245.533 0e3d3f1ca209c330 1.436 0.062 1473056 0.08874512 0 64 0 0 0.03080993 0.00199638 0.02085050 0.86705409 0.01097667 0 3 0.00000000 0.86705409 0.05934712 0.14319753 7 7 false +backend-v1 low_complexity nthash2 32768 32768 8 21 4096 14 12 245.533 0e3d3f1ca209c330 1.585 0.125 2808352 0.04652405 0 64 0 0 0.00258392 0.00164107 0.00587000 0.02495050 0.00627000 0 2 0.87120000 0.89615050 0.08751311 0.86120000 7 7 true +backend-v1 low_complexity mashmap_murmur3 32768 32768 8 21 512 14 12 50.383 698f60d192f57281 2.231 0.014 471584 0.25268555 0 64 0 0 0.04176819 0.01001188 0.08825409 0.89407086 0.03555804 2 1 0.00000000 0.89407086 0.12415529 0.90060000 8 8 false +backend-v1 low_complexity mashmap_murmur3 32768 32768 8 21 1024 14 12 50.383 698f60d192f57281 2.321 0.038 805408 0.15319824 0 64 0 0 0.01067519 0.00940000 0.02620000 0.04370000 0.01915804 7 2 0.95630000 1.00000000 0.09312924 0.87630000 7 7 true +backend-v1 low_complexity mashmap_murmur3 32768 32768 8 21 2048 14 12 50.383 698f60d192f57281 2.596 0.067 1473056 0.08636475 0 64 0 0 0.00553605 0.00424107 0.02093000 0.02230000 0.00925804 7 2 0.97770000 1.00000000 0.09025855 0.86710000 7 7 true +backend-v1 low_complexity mashmap_murmur3 32768 32768 8 21 4096 14 12 50.383 698f60d192f57281 2.657 0.123 2808352 0.04602051 0 64 0 0 0.00449820 0.00290574 0.02037086 0.02595050 0.00621188 0 2 0.87020000 0.89615050 0.09030792 0.86330000 7 7 true +backend-v1 tandem_repeat packed_fmix64 32768 32768 8 21 512 14 12 307.614 ec7941e8bb185d4c 0.620 0.012 471582 0.05493164 0 64 0 0 0.00005000 0.00000000 0.00040000 0.00040000 0.00000000 0 7 0.99960000 1.00000000 0.00005000 0.00040000 0 0 true +backend-v1 tandem_repeat packed_fmix64 32768 32768 8 21 1024 14 12 307.614 ec7941e8bb185d4c 0.660 0.039 805406 0.02746582 0 64 0 0 0.00005000 0.00000000 0.00040000 0.00040000 0.00000000 0 7 0.99960000 1.00000000 0.00005000 0.00040000 0 0 true +backend-v1 tandem_repeat packed_fmix64 32768 32768 8 21 2048 14 12 307.614 ec7941e8bb185d4c 0.712 0.059 1473054 0.01373291 0 64 0 0 0.00005000 0.00000000 0.00040000 0.00040000 0.00000000 0 7 0.99960000 1.00000000 0.00005000 0.00040000 0 0 true +backend-v1 tandem_repeat packed_fmix64 32768 32768 8 21 4096 14 12 307.614 ec7941e8bb185d4c 0.795 0.121 2808350 0.00686646 0 64 0 0 0.00005000 0.00000000 0.00040000 0.00040000 0.00000000 0 7 0.99960000 1.00000000 0.00005000 0.00040000 0 0 true +backend-v1 tandem_repeat nthash2 32768 32768 8 21 512 14 12 233.082 e80768fcc8353277 0.703 0.012 471582 0.05676270 0 64 0 0 0.00005000 0.00000000 0.00040000 0.00040000 0.00000000 0 7 0.99960000 1.00000000 0.00005000 0.00040000 0 0 true +backend-v1 tandem_repeat nthash2 32768 32768 8 21 1024 14 12 233.082 e80768fcc8353277 0.701 0.042 805406 0.02844238 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat nthash2 32768 32768 8 21 2048 14 12 233.082 e80768fcc8353277 0.735 0.060 1473054 0.01422119 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat nthash2 32768 32768 8 21 4096 14 12 233.082 e80768fcc8353277 0.849 0.122 2808350 0.00711060 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 32768 32768 8 21 512 14 12 41.081 536bccd4a1119eb3 1.864 0.012 471582 0.05468750 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 32768 32768 8 21 1024 14 12 41.081 536bccd4a1119eb3 1.900 0.053 805406 0.02838135 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 32768 32768 8 21 2048 14 12 41.081 536bccd4a1119eb3 2.011 0.059 1473054 0.01419067 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 tandem_repeat mashmap_murmur3 32768 32768 8 21 4096 14 12 41.081 536bccd4a1119eb3 2.118 0.124 2808350 0.00709534 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 32768 32768 8 21 512 14 12 159.116 9c2eb9df19354e0a 3.027 0.011 471574 0.99914551 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 32768 32768 8 21 1024 14 12 159.116 9c2eb9df19354e0a 4.404 0.030 805398 0.98199463 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 32768 32768 8 21 2048 14 12 159.116 9c2eb9df19354e0a 7.215 0.067 1473046 0.86370850 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated packed_fmix64 32768 32768 8 21 4096 14 12 159.116 9c2eb9df19354e0a 10.859 0.136 2808342 0.63038635 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 32768 32768 8 21 512 14 12 219.877 21ba26f606cdd3d5 2.721 0.011 471574 0.99951172 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 32768 32768 8 21 1024 14 12 219.877 21ba26f606cdd3d5 4.344 0.037 805398 0.98333740 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 32768 32768 8 21 2048 14 12 219.877 21ba26f606cdd3d5 7.017 0.060 1473046 0.86633301 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated nthash2 32768 32768 8 21 4096 14 12 219.877 21ba26f606cdd3d5 10.685 0.119 2808342 0.63296509 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 32768 32768 8 21 512 14 12 9.117 2e012eb93760b179 9.392 0.011 471574 0.93652344 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 32768 32768 8 21 1024 14 12 9.117 2e012eb93760b179 10.947 0.032 805398 0.87188721 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 32768 32768 8 21 2048 14 12 9.117 2e012eb93760b179 13.168 0.060 1473046 0.75439453 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 unrelated mashmap_murmur3 32768 32768 8 21 4096 14 12 9.117 2e012eb93760b179 16.737 0.122 2808342 0.56845093 0 64 0 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 NaN NaN 0.00000000 0.00000000 0 0 true +backend-v1 ambiguity_blocks packed_fmix64 32768 32768 8 21 512 14 12 159.601 cebbdffb33fd7318 2.777 0.013 471588 0.99853516 0 64 0 0 0.00072772 0.00000000 0.00363693 0.00493693 0.00493693 7 6 0.97890000 0.97396307 0.00085385 0.00560494 8 8 true +backend-v1 ambiguity_blocks packed_fmix64 32768 32768 8 21 1024 14 12 159.601 cebbdffb33fd7318 4.131 0.035 805412 0.97070312 0 64 0 0 0.00044022 0.00000000 0.00263693 0.00296307 0.00263693 4 3 0.97100000 0.97396307 0.00099135 0.00739121 8 8 true +backend-v1 ambiguity_blocks packed_fmix64 32768 32768 8 21 2048 14 12 159.601 cebbdffb33fd7318 6.927 0.062 1473060 0.82586670 0 64 0 0 0.00034185 0.00000000 0.00226307 0.00246307 0.00213693 4 3 0.97150000 0.97396307 0.00103510 0.00689121 8 8 true +backend-v1 ambiguity_blocks packed_fmix64 32768 32768 8 21 4096 14 12 159.601 cebbdffb33fd7318 9.978 0.124 2808356 0.58074951 0 64 0 0 0.00022853 0.00000000 0.00143693 0.00266307 0.00143693 4 3 0.97130000 0.97396307 0.00106322 0.00659141 8 8 true +backend-v1 ambiguity_blocks nthash2 32768 32768 8 21 512 14 12 228.622 e2c8c58bcdb2fb26 2.572 0.013 471588 0.99902344 0 64 0 0 0.00073017 0.00000000 0.00506307 0.00536307 0.00493693 2 1 0.96860000 0.97396307 0.00104759 0.00622516 8 8 true +backend-v1 ambiguity_blocks nthash2 32768 32768 8 21 1024 14 12 228.622 e2c8c58bcdb2fb26 4.212 0.032 805412 0.97216797 0 64 0 0 0.00041216 0.00000000 0.00323693 0.00386307 0.00323693 6 5 0.97010000 0.97396307 0.00090166 0.00572516 8 8 true +backend-v1 ambiguity_blocks nthash2 32768 32768 8 21 2048 14 12 228.622 e2c8c58bcdb2fb26 6.698 0.061 1473060 0.82733154 0 64 0 0 0.00023947 0.00000000 0.00156307 0.00243693 0.00243693 1 0 0.97640000 0.97396307 0.00089572 0.00549121 8 8 true +backend-v1 ambiguity_blocks nthash2 32768 32768 8 21 4096 14 12 228.622 e2c8c58bcdb2fb26 10.042 0.126 2808356 0.58129883 0 64 0 0 0.00024096 0.00000000 0.00163693 0.00263693 0.00263693 1 0 0.97660000 0.97396307 0.00089259 0.00522516 8 8 true +backend-v1 ambiguity_blocks mashmap_murmur3 32768 32768 8 21 512 14 12 9.163 22c7f541f7f1ecf6 8.461 0.013 471588 0.92993164 0 64 0 0 0.00060034 0.00000000 0.00296307 0.00473693 0.00473693 5 4 0.97870000 0.97396307 0.00095791 0.00591512 8 8 true +backend-v1 ambiguity_blocks mashmap_murmur3 32768 32768 8 21 1024 14 12 9.163 22c7f541f7f1ecf6 9.861 0.038 805412 0.85583496 0 64 0 0 0.00052303 0.00000000 0.00353693 0.00393693 0.00393693 1 0 0.97790000 0.97396307 0.00082072 0.00539017 8 8 true +backend-v1 ambiguity_blocks mashmap_murmur3 32768 32768 8 21 2048 14 12 9.163 22c7f541f7f1ecf6 11.988 0.062 1473060 0.71997070 0 64 0 0 0.00028404 0.00000000 0.00193693 0.00226307 0.00193693 6 5 0.97170000 0.97396307 0.00082384 0.00591512 8 8 true +backend-v1 ambiguity_blocks mashmap_murmur3 32768 32768 8 21 4096 14 12 9.163 22c7f541f7f1ecf6 14.884 0.126 2808356 0.52633667 0 64 0 0 0.00016834 0.00000000 0.00083693 0.00196307 0.00083693 2 1 0.97200000 0.97396307 0.00080697 0.00562516 8 8 true +backend-v1 sparse_valid_islands packed_fmix64 32768 32768 8 21 512 14 12 44.166 13eab36f1aa26f3e 0.538 0.012 471596 0.19091797 0 64 0 0 0.00014062 0.00000000 0.00110000 0.00170000 0.00000000 6 5 0.99830000 1.00000000 0.00014062 0.00110000 8 8 true +backend-v1 sparse_valid_islands packed_fmix64 32768 32768 8 21 1024 14 12 44.166 13eab36f1aa26f3e 0.577 0.032 805420 0.09985352 0 64 0 0 0.00010781 0.00000000 0.00050000 0.00180000 0.00000000 6 5 0.99820000 1.00000000 0.00010781 0.00050000 8 8 true +backend-v1 sparse_valid_islands packed_fmix64 32768 32768 8 21 2048 14 12 44.166 13eab36f1aa26f3e 0.697 0.061 1473068 0.05145264 0 64 0 0 0.00009531 0.00000000 0.00040000 0.00180000 0.00000000 6 5 0.99820000 1.00000000 0.00009531 0.00040000 8 8 true +backend-v1 sparse_valid_islands packed_fmix64 32768 32768 8 21 4096 14 12 44.166 13eab36f1aa26f3e 0.769 0.127 2808364 0.02600098 0 64 0 0 0.00009531 0.00000000 0.00060000 0.00110000 0.00000000 6 5 0.99890000 1.00000000 0.00009531 0.00060000 8 8 true +backend-v1 sparse_valid_islands nthash2 32768 32768 8 21 512 14 12 37.874 3f13c851396367c0 0.533 0.012 471596 0.18969727 0 64 0 0 0.00014375 0.00000000 0.00140000 0.00150000 0.00000000 5 4 0.99850000 1.00000000 0.00014375 0.00140000 8 8 true +backend-v1 sparse_valid_islands nthash2 32768 32768 8 21 1024 14 12 37.874 3f13c851396367c0 0.569 0.030 805420 0.09948730 0 64 0 0 0.00013594 0.00000000 0.00130000 0.00190000 0.00000000 5 4 0.99810000 1.00000000 0.00013594 0.00130000 8 8 true +backend-v1 sparse_valid_islands nthash2 32768 32768 8 21 2048 14 12 37.874 3f13c851396367c0 0.626 0.062 1473068 0.05126953 0 64 0 0 0.00010625 0.00000000 0.00080000 0.00100000 0.00000000 2 2 0.99900000 1.00000000 0.00010625 0.00080000 8 8 true +backend-v1 sparse_valid_islands nthash2 32768 32768 8 21 4096 14 12 37.874 3f13c851396367c0 0.787 0.124 2808364 0.02597046 0 64 0 0 0.00006406 0.00000000 0.00050000 0.00080000 0.00000000 2 2 0.99920000 1.00000000 0.00006406 0.00050000 8 8 true +backend-v1 sparse_valid_islands mashmap_murmur3 32768 32768 8 21 512 14 12 11.976 4e90fcdc27b3c028 0.622 0.012 471596 0.18554688 0 64 0 0 0.00011406 0.00000000 0.00090000 0.00180000 0.00000000 6 5 0.99820000 1.00000000 0.00011406 0.00090000 8 8 true +backend-v1 sparse_valid_islands mashmap_murmur3 32768 32768 8 21 1024 14 12 11.976 4e90fcdc27b3c028 0.655 0.032 805420 0.09851074 0 64 0 0 0.00014219 0.00000000 0.00140000 0.00190000 0.00000000 6 6 0.99810000 1.00000000 0.00014219 0.00140000 8 8 true +backend-v1 sparse_valid_islands mashmap_murmur3 32768 32768 8 21 2048 14 12 11.976 4e90fcdc27b3c028 0.695 0.059 1473068 0.05133057 0 64 0 0 0.00006094 0.00000000 0.00050000 0.00100000 0.00000000 6 5 0.99900000 1.00000000 0.00006094 0.00050000 8 8 true +backend-v1 sparse_valid_islands mashmap_murmur3 32768 32768 8 21 4096 14 12 11.976 4e90fcdc27b3c028 0.786 0.123 2808364 0.02597046 0 64 0 0 0.00005469 0.00000000 0.00050000 0.00060000 0.00000000 6 5 0.99940000 1.00000000 0.00005469 0.00050000 8 8 true +backend-v1 alternating_context packed_fmix64 32768 32768 8 21 512 14 12 239.036 b3fb523a3bc929bb 1.824 0.013 471594 0.50927734 0 64 0 0 0.24995411 0.02313738 0.89407086 0.91150118 0.04065804 5 1 0.00000000 0.91150118 0.05683712 0.17400000 8 8 false +backend-v1 alternating_context packed_fmix64 32768 32768 8 21 1024 14 12 239.036 b3fb523a3bc929bb 2.604 0.036 805418 0.41058350 0 64 0 0 0.18150248 0.01353738 0.86504196 0.89407086 0.06365804 5 0 0.00000000 0.89407086 0.09345604 0.88580000 8 8 false +backend-v1 alternating_context packed_fmix64 32768 32768 8 21 2048 14 12 239.036 b3fb523a3bc929bb 3.536 0.062 1473066 0.31195068 0 64 0 0 0.12788709 0.00746407 0.80769893 0.89407086 0.05584990 5 0 0.00000000 0.89407086 0.14517727 0.89990000 8 8 false +backend-v1 alternating_context packed_fmix64 32768 32768 8 21 4096 14 12 239.036 b3fb523a3bc929bb 4.551 0.123 2808362 0.20974731 0 64 0 0 0.11410595 0.00483000 0.80769893 0.89407086 0.03884990 5 0 0.00000000 0.89407086 0.15123195 0.87970000 8 8 false +backend-v1 alternating_context nthash2 32768 32768 8 21 512 14 12 229.241 e0c29c0c6617c721 1.869 0.013 471594 0.50646973 0 64 0 0 0.25508945 0.01062928 0.91150118 1.00000000 0.07655804 7 4 0.00000000 1.00000000 0.08055876 0.83601831 7 7 false +backend-v1 alternating_context nthash2 32768 32768 8 21 1024 14 12 229.241 e0c29c0c6617c721 2.531 0.037 805418 0.40777588 0 64 0 0 0.23879197 0.00740000 0.89407086 1.00000000 0.04685804 7 4 0.00000000 1.00000000 0.09164182 0.83601831 7 7 false +backend-v1 alternating_context nthash2 32768 32768 8 21 2048 14 12 229.241 e0c29c0c6617c721 3.654 0.060 1473066 0.31088257 0 64 0 0 0.09524296 0.00442802 0.86042110 0.89407086 0.03266225 5 0 0.00000000 0.89407086 0.17228244 0.87510000 8 8 false +backend-v1 alternating_context nthash2 32768 32768 8 21 4096 14 12 229.241 e0c29c0c6617c721 4.576 0.127 2808362 0.20808411 0 64 0 0 0.04410212 0.00335804 0.03930118 0.89407086 0.01732802 5 0 0.00000000 0.89407086 0.22087683 0.87330000 8 8 false +backend-v1 alternating_context mashmap_murmur3 32768 32768 8 21 512 14 12 22.617 cea655f6ff3fd750 4.562 0.013 471594 0.46716309 0 64 0 0 0.14333423 0.01893000 0.86042110 1.00000000 0.09675804 7 4 0.00000000 1.00000000 0.17756444 0.94550000 8 8 false +backend-v1 alternating_context mashmap_murmur3 32768 32768 8 21 1024 14 12 22.617 cea655f6ff3fd750 5.065 0.036 805418 0.37316895 0 64 0 0 0.13918396 0.01317738 0.86042110 1.00000000 0.06675804 7 4 0.00000000 1.00000000 0.17303314 0.92190000 8 8 false +backend-v1 alternating_context mashmap_murmur3 32768 32768 8 21 2048 14 12 22.617 cea655f6ff3fd750 5.975 0.061 1473066 0.27975464 0 64 0 0 0.09551294 0.00679893 0.70909254 0.86504196 0.04035804 5 3 0.00000000 0.86504196 0.18010291 0.90540000 8 8 false +backend-v1 alternating_context mashmap_murmur3 32768 32768 8 21 4096 14 12 22.617 cea655f6ff3fd750 7.186 0.126 2808362 0.19219971 0 64 0 0 0.06650603 0.00715804 0.67295010 0.86504196 0.02176719 5 3 0.00000000 0.86504196 0.20481072 0.90070000 8 8 false +backend-v1 unequal_length_padding packed_fmix64 32768 24576 8 21 512 14 12 163.512 c886fa1d7d90e5dd 2.621 0.010 471600 0.87426758 0 64 0 0 0.00049066 0.00000000 0.00224268 0.00439482 0.00439482 6 5 0.97170000 0.96730518 0.00091050 0.00419748 6 6 true +backend-v1 unequal_length_padding packed_fmix64 32768 24576 8 21 1024 14 12 163.512 c886fa1d7d90e5dd 3.862 0.025 805424 0.86004639 0 64 0 0 0.00042816 0.00000000 0.00186822 0.00386822 0.00386822 5 4 0.97140000 0.96753178 0.00076467 0.00379748 6 6 true +backend-v1 unequal_length_padding packed_fmix64 32768 24576 8 21 2048 14 12 163.512 c886fa1d7d90e5dd 6.345 0.046 1473072 0.75631714 0 64 0 0 0.00037212 0.00000000 0.00236822 0.00266822 0.00266822 5 4 0.97020000 0.96753178 0.00072761 0.00402912 6 6 true +backend-v1 unequal_length_padding packed_fmix64 32768 24576 8 21 4096 14 12 163.512 c886fa1d7d90e5dd 9.572 0.090 2808368 0.55181885 0 64 0 0 0.00021379 0.00000000 0.00116822 0.00186822 0.00186822 5 4 0.96940000 0.96753178 0.00082761 0.00428023 6 6 true +backend-v1 unequal_length_padding nthash2 32768 24576 8 21 512 14 12 241.960 2ee4b7f640d29a6f 2.358 0.014 471600 0.87438965 0 64 0 0 0.00096171 0.00000000 0.00516822 0.00696822 0.00696822 1 0 0.97450000 0.96753178 0.00085346 0.00562912 6 6 true +backend-v1 unequal_length_padding nthash2 32768 24576 8 21 1024 14 12 241.960 2ee4b7f640d29a6f 3.763 0.026 805424 0.86102295 0 64 0 0 0.00074087 0.00000000 0.00456822 0.00570518 0.00456822 6 5 0.96160000 0.96730518 0.00071467 0.00594092 6 6 true +backend-v1 unequal_length_padding nthash2 32768 24576 8 21 2048 14 12 241.960 2ee4b7f640d29a6f 6.389 0.047 1473072 0.75946045 0 64 0 0 0.00044504 0.00000000 0.00260518 0.00356822 0.00356822 1 0 0.97110000 0.96753178 0.00073386 0.00379748 6 6 true +backend-v1 unequal_length_padding nthash2 32768 24576 8 21 4096 14 12 241.960 2ee4b7f640d29a6f 9.400 0.094 2808368 0.55393982 0 64 0 0 0.00031171 0.00000000 0.00116822 0.00336822 0.00336822 1 0 0.97090000 0.96753178 0.00063386 0.00409748 6 6 true +backend-v1 unequal_length_padding mashmap_murmur3 32768 24576 8 21 512 14 12 9.252 6fec7f6811f46ddb 8.335 0.010 471600 0.82189941 0 64 0 0 0.00065962 0.00000000 0.00516822 0.00616822 0.00616822 5 4 0.97370000 0.96753178 0.00075842 0.00364092 6 6 true +backend-v1 unequal_length_padding mashmap_murmur3 32768 24576 8 21 1024 14 12 9.252 6fec7f6811f46ddb 9.482 0.027 805424 0.76452637 0 64 0 0 0.00054977 0.00000000 0.00296822 0.00536822 0.00536822 1 0 0.97290000 0.96753178 0.00066259 0.00344092 6 6 true +backend-v1 unequal_length_padding mashmap_murmur3 32768 24576 8 21 2048 14 12 9.252 6fec7f6811f46ddb 11.637 0.047 1473072 0.65884399 0 64 0 0 0.00036655 0.00000000 0.00266822 0.00290518 0.00266822 6 5 0.96440000 0.96730518 0.00059636 0.00348457 6 6 true +backend-v1 unequal_length_padding mashmap_murmur3 32768 24576 8 21 4096 14 12 9.252 6fec7f6811f46ddb 14.562 0.093 2808368 0.49681091 0 64 0 0 0.00019977 0.00000000 0.00116822 0.00160000 0.00116822 1 1 0.99840000 1.00000000 0.00074427 0.00384092 6 6 true diff --git a/docs/benchmarks/backend-wasm-chm13.tsv b/docs/benchmarks/backend-wasm-chm13.tsv new file mode 100644 index 0000000..0a23375 --- /dev/null +++ b/docs/benchmarks/backend-wasm-chm13.tsv @@ -0,0 +1,4 @@ +schema sequence hash bases resolution registers build_median_ms build_runs_ms matrix_median_ms matrix_runs_ms estimated_bytes +backend-v1 NC_060925.1 packed_fmix64 248387328 1000 1024 3351.500 3351.300,3351.500,3396.600 285.800 278.300,285.800,287.100 150846442 +backend-v1 NC_060925.1 nthash2 248387328 1000 1024 2687.900 2672.600,2687.900,2694.800 282.100 276.900,282.100,295.500 150846442 +backend-v1 NC_060925.1 mashmap_murmur3 248387328 1000 1024 33164.100 33064.500,33164.100,33200.400 285.400 283.400,285.400,289.000 150846442 diff --git a/docs/benchmarks/phase0-chm13-chr1-hor.tsv b/docs/benchmarks/phase0-chm13-chr1-hor.tsv new file mode 100644 index 0000000..aab20b4 --- /dev/null +++ b/docs/benchmarks/phase0-chm13-chr1-hor.tsv @@ -0,0 +1,6 @@ +schema fixture kernel sequence_bases resolution k sketch_units build_ms matrix_median_ms bytes cells missing_cells mean_abs_error p50_abs_error p95_abs_error max_abs_error mashmap_mean_abs_error mashmap_p95_abs_error forward_votes reverse_votes ambiguous_votes direction_correct_cells direction_tested_cells +phase0-v1 chm13_chr1_hor coordinated_bottom_s 262144 32 21 1024 127.572 8.905 2097152 1024 0 0.00094111 0.00077177 0.00250990 0.00415277 0.00000000 0.00000000 384219 0 0 0 0 +phase0-v1 chm13_chr1_hor minmer_postings 262144 32 21 1024 178.876 1.539 8140928 1024 1 0.01192177 0.01219825 0.01888458 0.02346004 0.01157640 0.01865070 519920 0 0 0 0 +phase0-v1 chm13_chr1_hor bbit_oph 262144 32 21 1024 17.420 0.688 3221714 1024 0 0.00085709 0.00073211 0.00207705 0.00409261 0.00114741 0.00306969 394456 0 0 0 0 +phase0-v1 chm13_chr1_hor densified_bbit_oph 262144 32 21 1024 28.322 0.635 3795154 1024 0 0.00086459 0.00074277 0.00214306 0.00421224 0.00114856 0.00299172 381225 0 0 0 0 +phase0-v1 chm13_chr1_hor setsketch_like_joint 262144 32 21 1024 13.722 29.169 2097152 1024 0 0.00219946 0.00172010 0.00579071 0.01010540 0.00239738 0.00695123 378319 0 0 0 0 diff --git a/docs/benchmarks/phase0-smoke.tsv b/docs/benchmarks/phase0-smoke.tsv new file mode 100644 index 0000000..85d4861 --- /dev/null +++ b/docs/benchmarks/phase0-smoke.tsv @@ -0,0 +1,56 @@ +schema fixture kernel sequence_bases resolution k sketch_units build_ms matrix_median_ms bytes cells missing_cells mean_abs_error p50_abs_error p95_abs_error max_abs_error mashmap_mean_abs_error mashmap_p95_abs_error forward_votes reverse_votes ambiguous_votes direction_correct_cells direction_tested_cells +phase0-v1 identity coordinated_bottom_s 32768 8 21 256 26.182 0.062 131072 64 0 0.00061600 0.00000000 0.00444845 0.00476570 0.00000000 0.00000000 1862 0 0 8 8 +phase0-v1 identity minmer_postings 32768 8 21 256 28.161 0.022 649088 64 1 0.00908461 0.00000000 0.03428656 0.03543714 0.00940984 0.03282951 1975 0 0 7 7 +phase0-v1 identity bbit_oph 32768 8 21 256 1.906 0.008 304660 64 0 0.00091227 0.00000000 0.00574268 0.00766822 0.00078477 0.00451543 3012 0 0 8 8 +phase0-v1 identity densified_bbit_oph 32768 8 21 256 2.738 0.007 340500 64 0 0.00091428 0.00000000 0.00577821 0.00764351 0.00078133 0.00449072 1902 0 0 8 8 +phase0-v1 identity setsketch_like_joint 32768 8 21 256 1.577 0.251 131072 64 0 0.43621593 0.74961172 0.92107600 0.93379023 0.43604708 0.92107600 1879 0 0 8 8 +phase0-v1 substitution_01pct coordinated_bottom_s 32768 8 21 256 25.081 0.080 131072 64 0 0.00091954 0.00000000 0.00425159 0.00883888 0.00000000 0.00000000 1441 0 0 8 8 +phase0-v1 substitution_01pct minmer_postings 32768 8 21 256 28.249 0.022 648896 64 1 0.00898265 0.00000000 0.03266926 0.03484517 0.00954477 0.03334324 1635 0 0 7 7 +phase0-v1 substitution_01pct bbit_oph 32768 8 21 256 1.904 0.008 304680 64 0 0.00089928 0.00000000 0.00477666 0.00633795 0.00101362 0.00470089 1459 0 0 8 8 +phase0-v1 substitution_01pct densified_bbit_oph 32768 8 21 256 2.916 0.008 340520 64 0 0.00090293 0.00000000 0.00481526 0.00637999 0.00101690 0.00467949 1459 0 0 8 8 +phase0-v1 substitution_01pct setsketch_like_joint 32768 8 21 256 1.638 0.292 131072 64 0 0.36482850 0.00790728 0.92657493 0.93847307 0.36473567 0.92657493 1407 0 0 8 8 +phase0-v1 substitution_05pct coordinated_bottom_s 32768 8 21 256 25.075 0.089 131072 64 0 0.00145549 0.00000000 0.00759879 0.01010142 0.00000000 0.00000000 531 0 0 8 8 +phase0-v1 substitution_05pct minmer_postings 32768 8 21 256 28.021 0.020 648768 64 1 0.00908820 0.00000000 0.03317911 0.03883456 0.00973806 0.03660680 692 0 0 7 7 +phase0-v1 substitution_05pct bbit_oph 32768 8 21 256 2.068 0.008 304680 64 0 0.00238281 0.00000000 0.01178773 0.01741883 0.00225122 0.00969040 604 0 0 8 8 +phase0-v1 substitution_05pct densified_bbit_oph 32768 8 21 256 2.923 0.007 340520 64 0 0.00238584 0.00000000 0.01181486 0.01738110 0.00225341 0.00973589 604 0 0 8 8 +phase0-v1 substitution_05pct setsketch_like_joint 32768 8 21 256 1.659 0.332 131072 64 0 0.43726838 0.03324813 0.92948754 0.95123882 0.43754329 0.92948754 533 0 0 8 8 +phase0-v1 substitution_10pct coordinated_bottom_s 32768 8 21 256 24.965 0.081 131072 64 0 0.00323099 0.00000000 0.01384519 0.04504814 0.00000000 0.00000000 146 0 0 8 8 +phase0-v1 substitution_10pct minmer_postings 32768 8 21 256 28.297 0.019 649088 64 1 0.00957216 0.00000000 0.03916555 0.04834425 0.00932694 0.03662336 201 0 0 7 7 +phase0-v1 substitution_10pct bbit_oph 32768 8 21 256 1.930 0.008 304680 64 0 0.00450217 0.00000000 0.02079810 0.02877534 0.00552771 0.02615866 192 0 0 8 8 +phase0-v1 substitution_10pct densified_bbit_oph 32768 8 21 256 2.968 0.007 340520 64 0 0.00450129 0.00000000 0.02081694 0.02874270 0.00552849 0.02619803 192 0 0 8 8 +phase0-v1 substitution_10pct setsketch_like_joint 32768 8 21 256 1.767 0.288 131072 64 0 0.51350191 0.85941275 0.93400360 0.94099438 0.51491511 0.93400360 146 0 0 8 8 +phase0-v1 indel_05pct coordinated_bottom_s 32768 8 21 256 25.264 0.081 131072 64 0 0.00756169 0.00000000 0.03614775 0.05119855 0.00000000 0.00000000 46 0 0 8 8 +phase0-v1 indel_05pct minmer_postings 32768 8 21 256 28.142 0.017 649024 64 1 0.00409631 0.00000000 0.02185305 0.03637355 0.00997501 0.04278354 58 0 0 7 7 +phase0-v1 indel_05pct bbit_oph 32768 8 21 256 1.928 0.007 304666 64 0 0.11843668 0.00000000 0.80917409 0.83102714 0.11973305 0.83601831 29 0 0 5 5 +phase0-v1 indel_05pct densified_bbit_oph 32768 8 21 256 2.923 0.006 340506 64 0 0.11843772 0.00000000 0.80917409 0.83102714 0.11973545 0.83601831 29 0 0 5 5 +phase0-v1 indel_05pct setsketch_like_joint 32768 8 21 256 1.647 0.294 131072 64 0 0.51690350 0.82403546 0.92452520 0.93827495 0.51446911 0.92452520 33 0 0 7 7 +phase0-v1 offset_1kb coordinated_bottom_s 32768 8 21 256 25.139 0.089 131072 64 0 0.00093350 0.00000000 0.00531350 0.01291226 0.00000000 0.00000000 1908 0 0 8 8 +phase0-v1 offset_1kb minmer_postings 32768 8 21 256 28.040 0.027 648960 64 1 0.00881045 0.00000000 0.03250223 0.03620880 0.00966709 0.03346440 2055 0 0 7 7 +phase0-v1 offset_1kb bbit_oph 32768 8 21 256 2.026 0.008 304664 64 0 0.00136186 0.00000000 0.00776404 0.01588226 0.00143070 0.00756405 2112 0 0 8 8 +phase0-v1 offset_1kb densified_bbit_oph 32768 8 21 256 2.883 0.007 340504 64 0 0.00135873 0.00000000 0.00774729 0.01584745 0.00142807 0.00757804 1982 0 0 8 8 +phase0-v1 offset_1kb setsketch_like_joint 32768 8 21 256 1.610 0.298 131072 64 0 0.36201823 0.00528040 0.92172265 0.94240259 0.36192459 0.92172265 1923 0 0 8 8 +phase0-v1 central_inversion coordinated_bottom_s 32768 8 21 256 25.086 0.090 131072 64 0 0.01141145 0.00000000 0.00565316 0.67295010 0.00000000 0.00000000 1023 1026 0 0 0 +phase0-v1 central_inversion minmer_postings 32768 8 21 256 28.049 0.023 649344 64 1 0.02160528 0.00000000 0.03428656 0.67295010 0.01084728 0.03292565 1057 1190 0 0 0 +phase0-v1 central_inversion bbit_oph 32768 8 21 256 1.916 0.008 304678 64 0 0.01149676 0.00000000 0.00574268 0.67295010 0.00123918 0.00775218 1422 1161 0 0 0 +phase0-v1 central_inversion densified_bbit_oph 32768 8 21 256 2.783 0.007 340518 64 0 0.01150048 0.00000000 0.00577821 0.67295010 0.00124229 0.00777962 1078 1029 0 0 0 +phase0-v1 central_inversion setsketch_like_joint 32768 8 21 256 1.746 0.262 131072 64 0 0.40193781 0.01191594 0.92107600 0.93379023 0.41246859 0.92107600 1029 1024 0 0 0 +phase0-v1 reverse_complement coordinated_bottom_s 32768 8 21 256 24.923 0.068 131072 64 0 0.00057929 0.00000000 0.00348358 0.00480698 0.00000000 0.00000000 0 1860 0 2 2 +phase0-v1 reverse_complement minmer_postings 32768 8 21 256 28.058 0.023 649088 64 1 0.00939178 0.00000000 0.03383452 0.03543714 0.00969411 0.03282951 0 2144 0 2 2 +phase0-v1 reverse_complement bbit_oph 32768 8 21 256 1.878 0.007 304680 64 0 0.00091064 0.00000000 0.00529482 0.00812026 0.00076003 0.00451543 0 2346 0 2 2 +phase0-v1 reverse_complement densified_bbit_oph 32768 8 21 256 2.806 0.007 340520 64 0 0.00091022 0.00000000 0.00531117 0.00813656 0.00075993 0.00451858 0 1905 0 2 2 +phase0-v1 reverse_complement setsketch_like_joint 32768 8 21 256 1.683 0.228 131072 64 0 0.42359824 0.01261606 0.92107600 0.93379023 0.42345071 0.92107600 0 1875 0 2 2 +phase0-v1 low_complexity coordinated_bottom_s 32768 8 21 256 5.318 0.044 72800 64 0 0.08680465 0.00989442 0.85955893 0.86705409 0.00000000 0.00000000 117 0 12 7 7 +phase0-v1 low_complexity minmer_postings 32768 8 21 256 7.963 0.013 309600 64 1 0.14264300 0.05916924 0.86504196 1.00000000 0.07336927 0.15213601 223 0 10 6 6 +phase0-v1 low_complexity bbit_oph 32768 8 21 256 0.960 0.009 304672 64 0 0.15029524 0.01010362 0.89615050 0.92622333 0.09379799 0.93611774 199 0 0 8 8 +phase0-v1 low_complexity densified_bbit_oph 32768 8 21 256 1.513 0.008 340512 64 0 0.15183038 0.00997416 0.89615050 0.92622333 0.09442427 0.93611774 220 0 0 8 8 +phase0-v1 low_complexity setsketch_like_joint 32768 8 21 256 0.904 0.182 131072 64 0 0.17543138 0.04152902 0.95143147 0.97833150 0.21584303 0.95143147 137 0 0 7 7 +phase0-v1 tandem_repeat coordinated_bottom_s 32768 8 21 256 5.155 0.006 14912 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1792 0 0 0 0 +phase0-v1 tandem_repeat minmer_postings 32768 8 21 256 5.196 0.006 41120 64 1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1764 0 0 0 0 +phase0-v1 tandem_repeat bbit_oph 32768 8 21 256 0.480 0.007 304670 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 3240 0 0 0 0 +phase0-v1 tandem_repeat densified_bbit_oph 32768 8 21 256 0.793 0.007 340510 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 15672 0 0 0 0 +phase0-v1 tandem_repeat setsketch_like_joint 32768 8 21 256 0.678 0.139 131072 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1664 0 0 0 0 +phase0-v1 unrelated coordinated_bottom_s 32768 8 21 256 25.523 0.106 131072 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated minmer_postings 32768 8 21 256 28.182 0.016 648512 64 1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated bbit_oph 32768 8 21 256 1.975 0.006 304662 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated densified_bbit_oph 32768 8 21 256 3.026 0.005 340502 64 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated setsketch_like_joint 32768 8 21 256 1.781 0.280 131072 64 0 0.65505915 0.89126923 0.93943533 0.94212871 0.65505915 0.93943533 0 0 0 0 0 diff --git a/docs/benchmarks/phase0-synthetic.tsv b/docs/benchmarks/phase0-synthetic.tsv new file mode 100644 index 0000000..da4904e --- /dev/null +++ b/docs/benchmarks/phase0-synthetic.tsv @@ -0,0 +1,56 @@ +schema fixture kernel sequence_bases resolution k sketch_units build_ms matrix_median_ms bytes cells missing_cells mean_abs_error p50_abs_error p95_abs_error max_abs_error mashmap_mean_abs_error mashmap_p95_abs_error forward_votes reverse_votes ambiguous_votes direction_correct_cells direction_tested_cells +phase0-v1 identity coordinated_bottom_s 262144 32 21 1024 219.790 5.683 2097152 1024 0 0.00009423 0.00000000 0.00034866 0.00366960 0.00000000 0.00000000 29905 0 0 32 32 +phase0-v1 identity minmer_postings 262144 32 21 1024 282.234 0.411 10271872 1024 1 0.00274224 0.00000000 0.03038075 0.03505588 0.00281151 0.03148836 32769 0 0 31 31 +phase0-v1 identity bbit_oph 262144 32 21 1024 20.421 0.568 3221524 1024 0 0.00011553 0.00000000 0.00066822 0.00556822 0.00011426 0.00061876 46542 0 0 32 32 +phase0-v1 identity densified_bbit_oph 262144 32 21 1024 33.201 0.524 3794964 1024 0 0.00011555 0.00000000 0.00063743 0.00560105 0.00011462 0.00065564 29824 0 0 32 32 +phase0-v1 identity setsketch_like_joint 262144 32 21 1024 16.172 21.223 2097152 1024 0 0.52413559 0.81413007 0.89979650 0.92746929 0.52411299 0.89979650 29789 0 0 32 32 +phase0-v1 substitution_01pct coordinated_bottom_s 262144 32 21 1024 219.538 5.830 2097152 1024 0 0.00013753 0.00000000 0.00089027 0.00583905 0.00000000 0.00000000 22615 0 0 32 32 +phase0-v1 substitution_01pct minmer_postings 262144 32 21 1024 284.078 0.440 10272192 1024 1 0.00272316 0.00000000 0.03012875 0.03564685 0.00280121 0.03115953 26569 0 0 31 31 +phase0-v1 substitution_01pct bbit_oph 262144 32 21 1024 20.203 0.560 3221544 1024 0 0.00014413 0.00000000 0.00107759 0.00527810 0.00016593 0.00105328 22541 0 0 32 32 +phase0-v1 substitution_01pct densified_bbit_oph 262144 32 21 1024 33.249 0.632 3794984 1024 0 0.00014393 0.00000000 0.00106510 0.00528384 0.00016584 0.00106308 22541 0 0 32 32 +phase0-v1 substitution_01pct setsketch_like_joint 262144 32 21 1024 16.256 22.189 2097152 1024 0 0.52198422 0.82283054 0.90052546 0.93034737 0.52200241 0.90052546 22014 0 0 32 32 +phase0-v1 substitution_05pct coordinated_bottom_s 262144 32 21 1024 219.810 5.760 2097152 1024 0 0.00025720 0.00000000 0.00246535 0.00734994 0.00000000 0.00000000 8485 0 0 32 32 +phase0-v1 substitution_05pct minmer_postings 262144 32 21 1024 282.522 0.354 10276224 1024 1 0.00260632 0.00000000 0.02862321 0.03514035 0.00276670 0.03078736 11441 0 0 31 31 +phase0-v1 substitution_05pct bbit_oph 262144 32 21 1024 20.167 0.513 3221544 1024 0 0.00034051 0.00000000 0.00287420 0.01211761 0.00038870 0.00278185 8547 0 0 32 32 +phase0-v1 substitution_05pct densified_bbit_oph 262144 32 21 1024 33.113 0.607 3794984 1024 0 0.00034004 0.00000000 0.00284177 0.01209771 0.00038852 0.00273594 8547 0 0 32 32 +phase0-v1 substitution_05pct setsketch_like_joint 262144 32 21 1024 16.288 22.065 2097152 1024 0 0.56721164 0.84365226 0.90461801 0.92661462 0.56719881 0.90461801 8305 0 0 32 32 +phase0-v1 substitution_10pct coordinated_bottom_s 262144 32 21 1024 219.245 5.716 2097152 1024 0 0.00061101 0.00000000 0.00523025 0.02453192 0.00000000 0.00000000 2657 0 0 32 32 +phase0-v1 substitution_10pct minmer_postings 262144 32 21 1024 284.618 0.301 10277824 1024 1 0.00241744 0.00000000 0.02606301 0.04462446 0.00262844 0.02768418 3839 0 0 31 31 +phase0-v1 substitution_10pct bbit_oph 262144 32 21 1024 20.036 0.571 3221544 1024 0 0.00061848 0.00000000 0.00494627 0.02636942 0.00091005 0.00676367 2679 0 0 32 32 +phase0-v1 substitution_10pct densified_bbit_oph 262144 32 21 1024 33.177 0.632 3794984 1024 0 0.00061822 0.00000000 0.00490780 0.02633072 0.00091019 0.00675155 2679 0 0 32 32 +phase0-v1 substitution_10pct setsketch_like_joint 262144 32 21 1024 16.253 21.934 2097152 1024 0 0.57447274 0.84644846 0.90625145 0.92738339 0.57443696 0.90625145 2497 0 0 32 32 +phase0-v1 indel_05pct coordinated_bottom_s 262144 32 21 1024 219.504 5.706 2097152 1024 0 0.00293850 0.00000000 0.01221082 0.80312305 0.00000000 0.00000000 510 0 0 32 32 +phase0-v1 indel_05pct minmer_postings 262144 32 21 1024 283.489 0.273 10274496 1024 1 0.00229917 0.00000000 0.02159677 0.08560500 0.00403882 0.03030550 704 0 0 31 31 +phase0-v1 indel_05pct bbit_oph 262144 32 21 1024 20.108 0.524 3221530 1024 0 0.00124978 0.00000000 0.01086764 0.04617926 0.00324955 0.01464138 525 0 0 32 32 +phase0-v1 indel_05pct densified_bbit_oph 262144 32 21 1024 33.146 0.642 3794970 1024 0 0.00125011 0.00000000 0.01085517 0.04620793 0.00324955 0.01464677 525 0 0 32 32 +phase0-v1 indel_05pct setsketch_like_joint 262144 32 21 1024 16.319 21.818 2097152 1024 0 0.54976820 0.83050916 0.90284927 0.93491695 0.55116917 0.90284927 451 0 0 32 32 +phase0-v1 offset_1kb coordinated_bottom_s 262144 32 21 1024 219.284 5.985 2097152 1024 0 0.00011464 0.00000000 0.00039909 0.00636420 0.00000000 0.00000000 30189 0 0 32 32 +phase0-v1 offset_1kb minmer_postings 262144 32 21 1024 283.904 0.433 10273792 1024 1 0.00274497 0.00000000 0.03063648 0.03337656 0.00283285 0.03129152 32988 0 0 31 31 +phase0-v1 offset_1kb bbit_oph 262144 32 21 1024 20.201 0.583 3221528 1024 0 0.00011291 0.00000000 0.00070000 0.00783221 0.00013908 0.00077605 35385 0 0 32 32 +phase0-v1 offset_1kb densified_bbit_oph 262144 32 21 1024 33.053 0.657 3794968 1024 0 0.00011290 0.00000000 0.00071016 0.00779125 0.00013924 0.00076723 30138 0 0 32 32 +phase0-v1 offset_1kb setsketch_like_joint 262144 32 21 1024 16.192 22.413 2097152 1024 0 0.51559196 0.82096381 0.89827501 0.92241987 0.51559654 0.89827501 30074 0 0 32 32 +phase0-v1 central_inversion coordinated_bottom_s 262144 32 21 1024 219.714 5.945 2097152 1024 0 0.00010314 0.00000000 0.00053746 0.00369272 0.00000000 0.00000000 15375 15290 0 0 0 +phase0-v1 central_inversion minmer_postings 262144 32 21 1024 284.447 0.414 10272000 1024 1 0.00286749 0.00000000 0.03072265 0.03496104 0.00292752 0.03148836 16667 17111 0 0 0 +phase0-v1 central_inversion bbit_oph 262144 32 21 1024 20.079 0.583 3221542 1024 0 0.00011965 0.00000000 0.00079371 0.00589371 0.00013221 0.00110000 22747 19069 0 0 0 +phase0-v1 central_inversion densified_bbit_oph 262144 32 21 1024 33.081 0.651 3794982 1024 0 0.00011985 0.00000000 0.00080568 0.00585609 0.00013208 0.00107743 15252 15384 0 0 0 +phase0-v1 central_inversion setsketch_like_joint 262144 32 21 1024 16.232 21.471 2097152 1024 0 0.52089826 0.81346390 0.89964112 0.92891761 0.52088105 0.89964112 15322 15191 0 0 0 +phase0-v1 reverse_complement coordinated_bottom_s 262144 32 21 1024 219.283 6.011 2097152 1024 0 0.00009468 0.00000000 0.00044439 0.00369272 0.00000000 0.00000000 0 29897 0 2 2 +phase0-v1 reverse_complement minmer_postings 262144 32 21 1024 283.713 0.413 10272000 1024 1 0.00276020 0.00000000 0.03044156 0.03496104 0.00282960 0.03147516 0 33458 0 2 2 +phase0-v1 reverse_complement bbit_oph 262144 32 21 1024 20.137 0.582 3221544 1024 0 0.00011570 0.00000000 0.00069371 0.00589371 0.00011384 0.00084266 0 37999 0 2 2 +phase0-v1 reverse_complement densified_bbit_oph 262144 32 21 1024 32.984 0.649 3794984 1024 0 0.00011613 0.00000000 0.00069389 0.00585609 0.00011391 0.00086630 0 29812 0 2 2 +phase0-v1 reverse_complement setsketch_like_joint 262144 32 21 1024 16.216 21.711 2097152 1024 0 0.51913335 0.81730034 0.89877557 0.92891761 0.51911372 0.89877557 0 29707 0 2 2 +phase0-v1 low_complexity coordinated_bottom_s 262144 32 21 1024 44.260 2.153 983072 1024 0 0.00348331 0.00401719 0.00818059 0.01184502 0.00000000 0.00000000 17030 0 512 32 32 +phase0-v1 low_complexity minmer_postings 262144 32 21 1024 77.747 0.189 4148688 1024 1 0.02370388 0.01962470 0.02806702 0.26463655 0.02375319 0.02806702 17253 0 528 31 31 +phase0-v1 low_complexity bbit_oph 262144 32 21 1024 7.648 0.606 3221536 1024 0 0.00664334 0.00566702 0.01482546 0.02512757 0.00593256 0.01606822 14603 0 0 32 32 +phase0-v1 low_complexity densified_bbit_oph 262144 32 21 1024 16.061 0.624 3794976 1024 0 0.00895794 0.00897175 0.02139495 0.03742456 0.00848519 0.02167486 18438 0 16 32 32 +phase0-v1 low_complexity setsketch_like_joint 262144 32 21 1024 7.149 10.998 2097152 1024 0 0.01366385 0.01067638 0.03241834 0.14871645 0.01494241 0.03729026 14007 0 400 32 32 +phase0-v1 tandem_repeat coordinated_bottom_s 262144 32 21 1024 41.016 0.097 57920 1024 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 28672 0 0 0 0 +phase0-v1 tandem_repeat minmer_postings 262144 32 21 1024 41.454 0.055 154784 1024 1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 28644 0 0 0 0 +phase0-v1 tandem_repeat bbit_oph 262144 32 21 1024 3.442 0.637 3221534 1024 0 0.00001250 0.00000000 0.00000000 0.00040000 0.00001250 0.00000000 54432 0 0 0 0 +phase0-v1 tandem_repeat densified_bbit_oph 262144 32 21 1024 11.215 0.660 3794974 1024 0 0.00001650 0.00000000 0.00000000 0.00052794 0.00001650 0.00000000 1035360 0 0 0 0 +phase0-v1 tandem_repeat setsketch_like_joint 262144 32 21 1024 5.599 8.214 2097152 1024 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 27648 0 0 0 0 +phase0-v1 unrelated coordinated_bottom_s 262144 32 21 1024 219.437 5.781 2097152 1024 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated minmer_postings 262144 32 21 1024 283.491 0.268 10273024 1024 1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated bbit_oph 262144 32 21 1024 20.285 0.556 3221526 1024 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated densified_bbit_oph 262144 32 21 1024 32.998 0.629 3794966 1024 0 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0 0 0 0 0 +phase0-v1 unrelated setsketch_like_joint 262144 32 21 1024 16.252 21.770 2097152 1024 0 0.60657877 0.84674820 0.90529632 0.92338909 0.60657877 0.90529632 0 0 0 0 0 diff --git a/docs/releases/v0.2.0-beta.1/browser-performance.json b/docs/releases/v0.2.0-beta.1/browser-performance.json new file mode 100644 index 0000000..7eacab4 --- /dev/null +++ b/docs/releases/v0.2.0-beta.1/browser-performance.json @@ -0,0 +1,72 @@ +{ + "generatedAt": "2026-08-29T04:42:43.541Z", + "software": { + "version": "0.2.0-beta.1", + "commit": "7695b3125935d81fa39a162ba84a1a82b2bed94d", + "worktreeDirty": false + }, + "host": { + "platform": "darwin", + "release": "25.5.0", + "architecture": "arm64", + "processor": "Apple M5 Pro", + "logicalCores": 18, + "totalMemoryBytes": 51539607552 + }, + "browser": "152.0.7977.64", + "viewport": { "width": 1440, "height": 1000 }, + "parameters": { + "overviewResolution": 1000, + "previewRegisters": 256, + "detailedRegisters": 1024, + "k": 21, + "sparseCorrection": false + }, + "inputs": { + "chm13": { + "accession": "NC_060925.1", + "bases": 248387328, + "bytes": 251935807, + "sha256": "8471eac90037b6886d110288712bb46b2af620e47965a672a7d1555ba0e36c4f" + }, + "hg002Paternal": { + "accession": "CP139546.2", + "bases": 252060741, + "bytes": 255661664, + "sha256": "8cc00c7b8b4a96adb1a71f16524ab4aff166648eae427724cb99d612c2db9041" + } + }, + "self": { + "sequenceLoadMs": 2136, + "firstPreviewTileMs": 2963, + "coherentPreviewMs": 4777, + "firstRefinedTileMs": 5446, + "stableViewportMs": 7592, + "workerElapsedMs": 5457, + "finalMemoryText": "Memory ~155.5 MiB", + "wasmEstimatedBytes": 139001856, + "jsHeapBytes": 15557472 + }, + "pairwise": { + "sequenceLoadMs": 4280, + "firstPreviewTileMs": 600, + "coherentPreviewMs": 5474, + "firstRefinedTileMs": 6831, + "stableViewportMs": 11148, + "workerElapsedMs": 10547, + "finalMemoryText": "Memory ~253.8 MiB", + "wasmEstimatedBytes": 242089984, + "jsHeapBytes": 28206174 + }, + "cacheTrace": { + "cycles": [ + ["Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB"], + ["Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB", "Memory ~391.5 MiB"] + ] + }, + "cancellation": { + "firstReplacementPreviewMs": 1030, + "replacementResolution": 8000 + }, + "unexpectedRequests": [] +} diff --git a/docs/releases/v0.2.0-beta.1/estimator-validation.tsv b/docs/releases/v0.2.0-beta.1/estimator-validation.tsv new file mode 100644 index 0000000..ec6a721 --- /dev/null +++ b/docs/releases/v0.2.0-beta.1/estimator-validation.tsv @@ -0,0 +1,31 @@ +estimator registers hll mean_abs_ani p95_abs_ani bias fn80 fp80 fn85 fp85 fn90 fp90 fn95 fp95 diagonal_holes80 one_pixel_holes85 isolated_false85 forward_continuity85 reverse_continuity85 local3_error local5_error neighbor_error_corr top_feature_overlap component_count_error85 fingerprint_collisions comparison_ms +BbitCollisionCorrected 256 8 0.067156 0.232625 -0.060335 145 49 148 51 137 27 132 0 21 117 5 0.791250 0.728477 0.028994 0.028974 0.191884 0.688889 0.541667 0 0.424 +VerifiedWinners 256 8 0.067115 0.232054 -0.060289 145 49 148 49 137 27 132 0 21 117 5 0.791250 0.728477 0.028979 0.028974 0.192154 0.688889 0.541667 0 0.414 +BbitCollisionCorrected 256 9 0.067118 0.232629 -0.060336 145 50 148 51 136 25 132 0 21 117 6 0.788750 0.730132 0.028984 0.028961 0.186663 0.700000 0.583333 0 0.421 +VerifiedWinners 256 9 0.067075 0.232057 -0.060292 145 50 147 50 136 25 132 0 21 116 5 0.790000 0.731788 0.028968 0.028960 0.186588 0.700000 0.541667 0 0.404 +BbitCollisionCorrected 256 10 0.067099 0.232628 -0.060547 145 50 146 54 136 26 132 0 21 115 5 0.793750 0.731788 0.028970 0.028956 0.168543 0.708333 0.541667 0 0.415 +VerifiedWinners 256 10 0.067052 0.232057 -0.060505 145 50 145 52 136 26 132 0 21 114 5 0.795000 0.733444 0.028955 0.028956 0.168478 0.708333 0.541667 0 0.416 +BbitCollisionCorrected 256 11 0.067110 0.232626 -0.060475 145 50 145 54 136 27 132 0 21 114 5 0.795000 0.733444 0.028975 0.028961 0.178025 0.697222 0.541667 0 0.418 +VerifiedWinners 256 11 0.067068 0.232055 -0.060430 145 50 145 52 136 27 132 0 21 114 5 0.795000 0.733444 0.028960 0.028962 0.178212 0.697222 0.541667 0 0.401 +BbitCollisionCorrected 256 12 0.067095 0.232624 -0.060490 145 50 145 54 136 27 132 0 21 114 5 0.795000 0.733444 0.028970 0.028956 0.175781 0.708333 0.541667 0 0.413 +VerifiedWinners 256 12 0.067056 0.232053 -0.060442 145 50 145 52 136 27 132 0 21 114 5 0.795000 0.733444 0.028956 0.028956 0.176120 0.711111 0.541667 0 0.410 +BbitCollisionCorrected 1024 8 0.008806 0.027056 -0.004584 23 23 18 20 16 20 36 0 9 14 3 0.967500 0.960265 0.003955 0.003745 0.164932 0.766667 0.291667 0 1.366 +VerifiedWinners 1024 8 0.008568 0.027331 -0.004366 22 22 18 21 16 20 24 0 9 14 3 0.967500 0.960265 0.003880 0.003671 0.165831 0.772222 0.291667 0 1.372 +BbitCollisionCorrected 1024 9 0.008557 0.027807 -0.004597 23 20 19 23 15 19 48 0 9 13 2 0.967500 0.960265 0.003887 0.003676 0.164209 0.769444 0.250000 0 1.370 +VerifiedWinners 1024 9 0.008313 0.027005 -0.004376 23 18 19 24 15 18 12 0 9 13 1 0.967500 0.960265 0.003809 0.003598 0.165124 0.772222 0.208333 0 1.386 +BbitCollisionCorrected 1024 10 0.008350 0.026505 -0.004629 24 18 19 20 15 19 24 0 10 13 1 0.967500 0.960265 0.003814 0.003602 0.118963 0.777778 0.208333 0 1.365 +VerifiedWinners 1024 10 0.008108 0.025736 -0.004412 24 16 19 22 15 18 12 0 10 13 1 0.967500 0.960265 0.003736 0.003524 0.119985 0.780556 0.208333 0 1.384 +BbitCollisionCorrected 1024 11 0.008302 0.027644 -0.004556 24 18 19 20 15 17 24 0 10 13 1 0.967500 0.960265 0.003807 0.003596 0.144909 0.777778 0.208333 0 1.373 +VerifiedWinners 1024 11 0.008058 0.025584 -0.004336 24 16 19 22 15 18 12 0 10 13 2 0.967500 0.960265 0.003728 0.003517 0.146430 0.780556 0.250000 0 1.360 +BbitCollisionCorrected 1024 12 0.008293 0.026801 -0.004592 24 18 19 20 15 18 24 0 10 13 1 0.967500 0.960265 0.003801 0.003589 0.129355 0.766667 0.208333 0 1.367 +VerifiedWinners 1024 12 0.008070 0.026199 -0.004389 24 16 19 20 15 18 12 0 10 13 1 0.967500 0.960265 0.003727 0.003518 0.130368 0.769444 0.208333 0 1.356 +BbitCollisionCorrected 2048 8 0.005558 0.023809 -0.002738 10 23 6 12 2 15 0 0 7 3 1 0.997500 0.995033 0.001792 0.001632 0.202269 0.800000 0.125000 0 2.762 +VerifiedWinners 2048 8 0.005283 0.022065 -0.002451 9 25 6 12 1 15 0 0 7 3 1 0.997500 0.995033 0.001698 0.001540 0.204823 0.805556 0.125000 0 2.768 +BbitCollisionCorrected 2048 9 0.005347 0.025963 -0.002805 10 21 5 13 2 13 0 0 7 2 1 0.997500 0.996689 0.001739 0.001576 0.194804 0.800000 0.125000 0 2.759 +VerifiedWinners 2048 9 0.005063 0.024223 -0.002510 11 21 5 13 2 14 0 0 7 2 1 0.997500 0.996689 0.001641 0.001479 0.197277 0.802778 0.125000 0 2.764 +BbitCollisionCorrected 2048 10 0.005073 0.022071 -0.002763 10 21 4 14 2 13 0 0 7 2 1 0.998750 0.996689 0.001638 0.001473 0.165093 0.802778 0.083333 0 2.811 +VerifiedWinners 2048 10 0.004798 0.020423 -0.002477 11 20 3 14 2 13 0 0 7 1 1 0.998750 0.998344 0.001540 0.001378 0.167412 0.802778 0.083333 0 2.763 +BbitCollisionCorrected 2048 11 0.004978 0.022370 -0.002626 10 21 4 12 2 14 0 0 7 2 1 0.998750 0.996689 0.001605 0.001444 0.185451 0.811111 0.083333 0 2.771 +VerifiedWinners 2048 11 0.004710 0.020623 -0.002345 11 20 4 13 2 14 0 0 7 2 1 0.998750 0.996689 0.001510 0.001352 0.188402 0.811111 0.083333 0 2.794 +BbitCollisionCorrected 2048 12 0.004952 0.022046 -0.002653 10 21 3 13 2 13 0 0 7 1 1 0.998750 0.998344 0.001600 0.001436 0.159109 0.811111 0.083333 0 2.758 +VerifiedWinners 2048 12 0.004678 0.020576 -0.002364 11 20 3 13 2 13 0 0 7 1 1 0.998750 0.998344 0.001502 0.001342 0.161881 0.811111 0.083333 0 2.748 diff --git a/docs/releases/v0.2.1/estimator-validation.tsv b/docs/releases/v0.2.1/estimator-validation.tsv new file mode 100644 index 0000000..ee67989 --- /dev/null +++ b/docs/releases/v0.2.1/estimator-validation.tsv @@ -0,0 +1,31 @@ +estimator registers hll mean_abs_ani_c p95_abs_ani_c ani_c_bias ani_c_error_variance mean_union_occupancy minimum_union_occupancy fn80 fp80 fn85 fp85 fn90 fp90 fn95 fp95 diagonal_holes80 one_pixel_holes85 isolated_false85 forward_continuity85 reverse_continuity85 local3_error local5_error neighbor_error_corr top_feature_overlap component_count_error85 fingerprint_collisions comparison_ms selected_config_gate +BbitCollisionCorrected 256 8 0.067156 0.232625 -0.060335 0.04163417 0.999993 0.996094 145 49 148 51 137 27 132 0 21 117 5 0.791250 0.728477 0.028994 0.028974 0.191884 0.688889 0.541667 0 0.436 not_applicable +VerifiedWinners 256 8 0.067115 0.232054 -0.060289 0.04162407 0.999993 0.996094 145 49 148 49 137 27 132 0 21 117 5 0.791250 0.728477 0.028979 0.028974 0.192154 0.688889 0.541667 0 0.405 not_applicable +BbitCollisionCorrected 256 9 0.067118 0.232629 -0.060336 0.04162285 0.999993 0.996094 145 50 148 51 136 25 132 0 21 117 6 0.788750 0.730132 0.028984 0.028961 0.186663 0.700000 0.583333 0 0.421 not_applicable +VerifiedWinners 256 9 0.067075 0.232057 -0.060292 0.04161261 0.999993 0.996094 145 50 147 50 136 25 132 0 21 116 5 0.790000 0.731788 0.028968 0.028960 0.186588 0.700000 0.541667 0 0.421 not_applicable +BbitCollisionCorrected 256 10 0.067099 0.232628 -0.060547 0.04162733 0.999993 0.996094 145 50 146 54 136 26 132 0 21 115 5 0.793750 0.731788 0.028970 0.028956 0.168543 0.708333 0.541667 0 0.437 not_applicable +VerifiedWinners 256 10 0.067052 0.232057 -0.060505 0.04161681 0.999993 0.996094 145 50 145 52 136 26 132 0 21 114 5 0.795000 0.733444 0.028955 0.028956 0.168478 0.708333 0.541667 0 0.442 pass +BbitCollisionCorrected 256 11 0.067110 0.232626 -0.060475 0.04162742 0.999993 0.996094 145 50 145 54 136 27 132 0 21 114 5 0.795000 0.733444 0.028975 0.028961 0.178025 0.697222 0.541667 0 0.447 not_applicable +VerifiedWinners 256 11 0.067068 0.232055 -0.060430 0.04161728 0.999993 0.996094 145 50 145 52 136 27 132 0 21 114 5 0.795000 0.733444 0.028960 0.028962 0.178212 0.697222 0.541667 0 0.404 not_applicable +BbitCollisionCorrected 256 12 0.067095 0.232624 -0.060490 0.04162224 0.999993 0.996094 145 50 145 54 136 27 132 0 21 114 5 0.795000 0.733444 0.028970 0.028956 0.175781 0.708333 0.541667 0 0.409 not_applicable +VerifiedWinners 256 12 0.067056 0.232053 -0.060442 0.04161244 0.999993 0.996094 145 50 145 52 136 27 132 0 21 114 5 0.795000 0.733444 0.028956 0.028956 0.176120 0.711111 0.541667 0 0.441 not_applicable +BbitCollisionCorrected 1024 8 0.008806 0.027056 -0.004584 0.00084204 0.990056 0.876953 23 23 18 20 16 20 36 0 9 14 3 0.967500 0.960265 0.003955 0.003745 0.164932 0.766667 0.291667 0 1.364 not_applicable +VerifiedWinners 1024 8 0.008568 0.027331 -0.004366 0.00082453 0.990056 0.876953 22 22 18 21 16 20 24 0 9 14 3 0.967500 0.960265 0.003880 0.003671 0.165831 0.772222 0.291667 0 1.381 not_applicable +BbitCollisionCorrected 1024 9 0.008557 0.027807 -0.004597 0.00083674 0.990056 0.876953 23 20 19 23 15 19 48 0 9 13 2 0.967500 0.960265 0.003887 0.003676 0.164209 0.769444 0.250000 0 1.375 not_applicable +VerifiedWinners 1024 9 0.008313 0.027005 -0.004376 0.00081966 0.990056 0.876953 23 18 19 24 15 18 12 0 9 13 1 0.967500 0.960265 0.003809 0.003598 0.165124 0.772222 0.208333 0 1.367 not_applicable +BbitCollisionCorrected 1024 10 0.008350 0.026505 -0.004629 0.00083032 0.990056 0.876953 24 18 19 20 15 19 24 0 10 13 1 0.967500 0.960265 0.003814 0.003602 0.118963 0.777778 0.208333 0 1.401 not_applicable +VerifiedWinners 1024 10 0.008108 0.025736 -0.004412 0.00081348 0.990056 0.876953 24 16 19 22 15 18 12 0 10 13 1 0.967500 0.960265 0.003736 0.003524 0.119985 0.780556 0.208333 0 1.385 pass +BbitCollisionCorrected 1024 11 0.008302 0.027644 -0.004556 0.00082684 0.990056 0.876953 24 18 19 20 15 17 24 0 10 13 1 0.967500 0.960265 0.003807 0.003596 0.144909 0.777778 0.208333 0 1.399 not_applicable +VerifiedWinners 1024 11 0.008058 0.025584 -0.004336 0.00081023 0.990056 0.876953 24 16 19 22 15 18 12 0 10 13 2 0.967500 0.960265 0.003728 0.003517 0.146430 0.780556 0.250000 0 1.375 not_applicable +BbitCollisionCorrected 1024 12 0.008293 0.026801 -0.004592 0.00082780 0.990056 0.876953 24 18 19 20 15 18 24 0 10 13 1 0.967500 0.960265 0.003801 0.003589 0.129355 0.766667 0.208333 0 1.694 not_applicable +VerifiedWinners 1024 12 0.008070 0.026199 -0.004389 0.00081099 0.990056 0.876953 24 16 19 20 15 18 12 0 10 13 1 0.967500 0.960265 0.003727 0.003518 0.130368 0.769444 0.208333 0 1.372 not_applicable +BbitCollisionCorrected 2048 8 0.005558 0.023809 -0.002738 0.00010407 0.951325 0.662109 10 23 6 12 2 15 0 0 7 3 1 0.997500 0.995033 0.001792 0.001632 0.202269 0.800000 0.125000 0 2.839 not_applicable +VerifiedWinners 2048 8 0.005283 0.022065 -0.002451 0.00009406 0.951325 0.662109 9 25 6 12 1 15 0 0 7 3 1 0.997500 0.995033 0.001698 0.001540 0.204823 0.805556 0.125000 0 2.804 not_applicable +BbitCollisionCorrected 2048 9 0.005347 0.025963 -0.002805 0.00010028 0.951325 0.662109 10 21 5 13 2 13 0 0 7 2 1 0.997500 0.996689 0.001739 0.001576 0.194804 0.800000 0.125000 0 2.796 not_applicable +VerifiedWinners 2048 9 0.005063 0.024223 -0.002510 0.00009055 0.951325 0.662109 11 21 5 13 2 14 0 0 7 2 1 0.997500 0.996689 0.001641 0.001479 0.197277 0.802778 0.125000 0 2.828 not_applicable +BbitCollisionCorrected 2048 10 0.005073 0.022071 -0.002763 0.00009270 0.951325 0.662109 10 21 4 14 2 13 0 0 7 2 1 0.998750 0.996689 0.001638 0.001473 0.165093 0.802778 0.083333 0 2.850 not_applicable +VerifiedWinners 2048 10 0.004798 0.020423 -0.002477 0.00008350 0.951325 0.662109 11 20 3 14 2 13 0 0 7 1 1 0.998750 0.998344 0.001540 0.001378 0.167412 0.802778 0.083333 0 2.792 not_applicable +BbitCollisionCorrected 2048 11 0.004978 0.022370 -0.002626 0.00008898 0.951325 0.662109 10 21 4 12 2 14 0 0 7 2 1 0.998750 0.996689 0.001605 0.001444 0.185451 0.811111 0.083333 0 2.830 not_applicable +VerifiedWinners 2048 11 0.004710 0.020623 -0.002345 0.00008005 0.951325 0.662109 11 20 4 13 2 14 0 0 7 2 1 0.998750 0.996689 0.001510 0.001352 0.188402 0.811111 0.083333 0 2.849 not_applicable +BbitCollisionCorrected 2048 12 0.004952 0.022046 -0.002653 0.00008995 0.951325 0.662109 10 21 3 13 2 13 0 0 7 1 1 0.998750 0.998344 0.001600 0.001436 0.159109 0.811111 0.083333 0 2.858 not_applicable +VerifiedWinners 2048 12 0.004678 0.020576 -0.002364 0.00008098 0.951325 0.662109 11 20 3 13 2 13 0 0 7 1 1 0.998750 0.998344 0.001502 0.001342 0.161881 0.811111 0.083333 0 2.945 not_applicable diff --git a/docs/releases/v0.5.0/scientific-grid.tsv b/docs/releases/v0.5.0/scientific-grid.tsv new file mode 100644 index 0000000..479a439 --- /dev/null +++ b/docs/releases/v0.5.0/scientific-grid.tsv @@ -0,0 +1,3 @@ +registers mean_abs_ani_c p95_abs_ani_c ani_c_bias ani_c_error_variance mean_union_occupancy minimum_union_occupancy fn80 fp80 fn85 fp85 fn90 fp90 fn95 fp95 visibility_crossings80 crossing_rate80 zero_inflation diagonal_holes80 one_pixel_holes85 isolated_false85 forward_continuity85 reverse_continuity85 local3_error local5_error neighbor_error_corr top_feature_overlap component_count_error85 observed_fingerprint_collisions expected_fingerprint_collisions_per_cell expected_fingerprint_collisions_per_12x12_matrix comparison_ms selected_config_gate +256 0.035283 0.226630 -0.027959 0.02130277 0.999998 0.996094 181 165 150 73 154 67 145 12 346 0.028604 232 41 119 10 0.933411 0.896096 0.018167 0.018198 0.126497 0.728571 0.297619 0 0.0000001192 0.00001717 1.504 pass +1024 0.005333 0.017890 -0.001471 0.00030159 0.996153 0.876953 77 68 21 21 26 37 21 10 145 0.011987 19 27 15 0 0.988318 0.983627 0.002567 0.002386 0.103401 0.783333 0.047619 0 0.0000004732 0.00006814 5.382 pass diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..d056662 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.98.0" +profile = "minimal" +components = ["clippy", "rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4937083 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +edition = "2024" +max_width = 100 +use_field_init_shorthand = true diff --git a/scripts/audit-default-symbols.sh b/scripts/audit-default-symbols.sh new file mode 100755 index 0000000..794fb6b --- /dev/null +++ b/scripts/audit-default-symbols.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -eu + +repository_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +cd "$repository_root" + +cargo build --release -p moddotplot-core + +rejected='BbitCollisionCorrected|project_registers|compute_(frozen_)?tile_adaptive|exact_sparse_tile_direction' +if nm -g target/release/libmoddotplot_core.rlib 2>/dev/null | rg "$rejected"; then + echo "default core artifact contains a validation-only global symbol" >&2 + exit 1 +fi + +echo "default core global-symbol audit passed" diff --git a/scripts/benchmark-browser.mjs b/scripts/benchmark-browser.mjs new file mode 100755 index 0000000..fe9b193 --- /dev/null +++ b/scripts/benchmark-browser.mjs @@ -0,0 +1,231 @@ +#!/usr/bin/env node + +import playwright from "../web/node_modules/@playwright/test/index.js"; +import { createReadStream, readFileSync, statSync } from "node:fs"; +import { createHash } from "node:crypto"; +import { execFileSync } from "node:child_process"; +import { arch, cpus, platform, release, totalmem } from "node:os"; +import { basename, resolve } from "node:path"; + +const [chm13Argument, hg002Argument, url = "http://127.0.0.1:4174"] = process.argv.slice(2); +const { chromium } = playwright; +if (!chm13Argument || !hg002Argument) { + console.error("usage: scripts/benchmark-browser.mjs CHM13_CHR1_FASTA HG002_PAT_CHR1_FASTA [URL]"); + process.exit(2); +} + +const chm13 = resolve(chm13Argument); +const hg002 = resolve(hg002Argument); +for (const path of [chm13, hg002]) statSync(path); + +const browser = await chromium.launch({ channel: "chrome", headless: true }); +const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); +const unexpectedRequests = []; +page.on("request", (request) => { + const target = new URL(request.url()); + if ( + (target.protocol === "http:" || target.protocol === "https:") + && target.origin !== new URL(url).origin + ) { + unexpectedRequests.push(request.url()); + } +}); +await page.addInitScript(() => { + const NativeWorker = window.Worker; + window.__moddotplotBenchmark = { origin: 0, events: [] }; + window.Worker = class ObservedWorker extends NativeWorker { + constructor(...args) { + super(...args); + this.addEventListener("message", (event) => { + const state = window.__moddotplotBenchmark; + if (!state || !event.data || typeof event.data.type !== "string") return; + const message = event.data; + const record = { tMs: performance.now() - state.origin, type: message.type }; + for (const field of [ + "text", "progress", "busy", "generation", "requestId", "quality", "resolution", + "x", "y", "width", "height", "elapsedMs", "estimatedBytes", + ]) { + if (field in message) record[field] = message[field]; + } + if (message.type === "comparison-ready") record.parameters = message.parameters; + state.events.push(record); + }); + } + }; +}); + +await page.goto(url); +await page.locator("#resolution").selectOption("1000", { force: true }); +await page.locator("#preview-register-count").selectOption("256", { force: true }); +await page.locator("#detailed-register-count").selectOption("1024", { force: true }); + +const self = await benchmarkComparison([chm13], false); +await page.locator("#clear-button").click(); +const pairwise = await benchmarkComparison([chm13, hg002], true); +const cacheTrace = await runCacheTrace(); +const cancellation = await benchmarkCancellation(); +const packageMetadata = JSON.parse(readFileSync(new URL("../web/package.json", import.meta.url))); + +const result = { + generatedAt: new Date().toISOString(), + software: { + version: packageMetadata.version, + commit: git("rev-parse", "HEAD"), + worktreeDirty: git("status", "--porcelain").length > 0, + }, + host: { + platform: platform(), + release: release(), + architecture: arch(), + processor: cpus()[0]?.model ?? "unknown", + logicalCores: cpus().length, + totalMemoryBytes: totalmem(), + }, + browser: await browser.version(), + viewport: { width: 1440, height: 1000 }, + parameters: { + overviewResolution: 1_000, + previewRegisters: 256, + detailedRegisters: 1_024, + k: 21, + }, + inputs: { + chm13: await fileMetadata(chm13), + hg002Paternal: await fileMetadata(hg002), + }, + self, + pairwise, + cacheTrace, + cancellation, + unexpectedRequests, +}; + +console.log(JSON.stringify(result, null, 2)); +await browser.close(); + +async function benchmarkComparison(paths, selectPairwise) { + await resetObservations(); + const start = performance.now(); + await page.locator("#file-input").setInputFiles(paths); + await page.waitForFunction( + (count) => document.querySelectorAll("#x-sequence option").length >= count, + paths.length, + { timeout: 120_000 }, + ); + const loadMs = performance.now() - start; + if (selectPairwise) { + await resetObservations(); + await page.locator("#y-sequence").selectOption("1"); + } + await page.waitForFunction(() => { + const state = window.__moddotplotBenchmark; + return state?.events.some((event) => event.type === "complete"); + }, undefined, { timeout: 180_000 }); + const events = await observations(); + const firstPreview = events.find((event) => event.type === "tile" && event.quality === "preview"); + const previewComplete = events.find((event) => event.type === "preview-complete"); + const firstRefined = events.find((event) => event.type === "tile" && event.quality === "refined"); + const complete = [...events].reverse().find((event) => event.type === "complete"); + return { + sequenceLoadMs: Math.round(loadMs), + firstPreviewTileMs: rounded(firstPreview?.tMs), + coherentPreviewMs: rounded(previewComplete?.tMs), + firstRefinedTileMs: rounded(firstRefined?.tMs), + stableViewportMs: rounded(complete?.tMs), + workerElapsedMs: rounded(complete?.elapsedMs), + finalMemoryText: await page.locator("#memory-usage").textContent(), + wasmEstimatedBytes: complete?.estimatedBytes ?? null, + jsHeapBytes: await chromiumHeapBytes(), + }; +} + +async function runCacheTrace() { + const canvas = page.locator("#plot-canvas"); + await canvas.focus(); + const cycles = []; + for (let cycle = 0; cycle < 2; cycle += 1) { + const samples = []; + for (const key of ["+", "+", "+", "ArrowRight", "ArrowUp", "ArrowLeft", "ArrowDown"]) { + await page.keyboard.press(key); + await waitForIdle(); + samples.push(await page.locator("#memory-usage").textContent()); + } + await page.locator("#reset-view").click(); + await waitForIdle(); + samples.push(await page.locator("#memory-usage").textContent()); + cycles.push(samples); + } + return { cycles }; +} + +async function benchmarkCancellation() { + await page.locator("details").evaluate((element) => { element.open = true; }); + await resetObservations(); + await page.locator("#detailed-register-count").selectOption("4096"); + await page.waitForFunction(() => document.querySelector("#status")?.textContent + ?.includes("Building high-detail signatures"), undefined, { timeout: 180_000 }); + await resetObservations(); + const canvas = page.locator("#plot-canvas"); + const box = await canvas.boundingBox(); + if (!box) throw new Error("plot canvas has no layout box"); + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); + await page.mouse.wheel(0, -1_100); + await page.waitForFunction(() => window.__moddotplotBenchmark?.events.some( + (event) => event.type === "tile" && event.quality === "preview" && event.resolution > 1_000, + ), undefined, { timeout: 180_000 }); + const events = await observations(); + const first = events.find( + (event) => event.type === "tile" && event.quality === "preview" && event.resolution > 1_000, + ); + return { + firstReplacementPreviewMs: rounded(first?.tMs), + replacementResolution: first?.resolution ?? null, + }; +} + +async function resetObservations() { + await page.evaluate(() => { + window.__moddotplotBenchmark.origin = performance.now(); + window.__moddotplotBenchmark.events = []; + }); +} + +async function observations() { + return page.evaluate(() => window.__moddotplotBenchmark.events); +} + +async function chromiumHeapBytes() { + return page.evaluate(() => performance.memory?.usedJSHeapSize ?? null); +} + +async function waitForIdle() { + // Let the viewport debounce replace the preceding Ready state before waiting for idle. + await page.waitForTimeout(180); + await page.waitForFunction(() => { + const text = document.querySelector("#status")?.textContent ?? ""; + const progress = document.querySelector("#compute-progress"); + return text.startsWith("Ready") && progress?.hasAttribute("hidden"); + }, undefined, { timeout: 180_000 }); +} + +async function fileMetadata(path) { + return { name: basename(path), bytes: statSync(path).size, sha256: await sha256(path) }; +} + +function rounded(value) { + return typeof value === "number" ? Math.round(value) : null; +} + +function git(...arguments_) { + return execFileSync("git", arguments_, { encoding: "utf8" }).trim(); +} + +function sha256(path) { + return new Promise((resolveHash, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(path); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolveHash(hash.digest("hex"))); + }); +} diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs new file mode 100644 index 0000000..9cc37cd --- /dev/null +++ b/scripts/check-docs.mjs @@ -0,0 +1,65 @@ +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { dirname, join, normalize, relative, resolve } from "node:path"; + +const repository = resolve(import.meta.dirname, ".."); +const allowedMarkdown = new Set([ + "README.md", + "docs/CITATIONS.md", + "docs/DEPLOYMENT.md", + "docs/METHODS.md", + "docs/PRIVACY.md", + "docs/RELEASE_CHECKLIST.md", + "docs/USER_GUIDE.md", +]); +const ignoredDirectories = new Set([ + ".git", + "node_modules", + "target", + "dist", + "release-artifacts", +]); +const markdown = []; + +function collect(directory) { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue; + const absolutePath = join(directory, entry.name); + if (entry.isDirectory()) collect(absolutePath); + else if (entry.name.endsWith(".md")) markdown.push(relative(repository, absolutePath)); + } +} + +collect(repository); + +const failures = []; +for (const expected of allowedMarkdown) { + if (!existsSync(join(repository, expected))) failures.push(`${expected}: required document is missing`); +} +for (const found of markdown) { + if (!allowedMarkdown.has(found)) failures.push(`${found}: Markdown file is outside the retained documentation set`); +} + +for (const relativePath of [...allowedMarkdown].filter((path) => existsSync(join(repository, path)))) { + const absolutePath = join(repository, relativePath); + const contents = readFileSync(absolutePath, "utf8"); + if (relativePath.startsWith("docs/") && !contents.includes("This project was generated by ChatGPT 5.6 Sol")) { + failures.push(`${relativePath}: missing the generated-project note`); + } + + for (const match of contents.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) { + let target = match[1].trim(); + if (target.startsWith("<") && target.endsWith(">")) target = target.slice(1, -1); + target = target.split("#", 1)[0]; + if (!target || /^(?:https?:|mailto:)/i.test(target)) continue; + const resolved = target.startsWith("/") + ? join(repository, target.slice(1)) + : normalize(join(dirname(absolutePath), target)); + if (!existsSync(resolved)) failures.push(`${relativePath}: broken local link ${match[1]}`); + } +} + +if (failures.length > 0) { + throw new Error(`Documentation checks failed:\n${failures.map((failure) => `- ${failure}`).join("\n")}`); +} + +console.log(`Checked ${markdown.length} retained Markdown documents and their local links.`); diff --git a/scripts/check-reproducible-build.sh b/scripts/check-reproducible-build.sh new file mode 100755 index 0000000..b51c3e7 --- /dev/null +++ b/scripts/check-reproducible-build.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -eu + +repository_root=$(git rev-parse --show-toplevel) +if [ -n "$(git -C "$repository_root" status --porcelain)" ]; then + echo "reproducibility checking requires a clean Git worktree" >&2 + exit 1 +fi + +scratch_parent=${TMPDIR:-/tmp} +scratch_root=$(mktemp -d "${scratch_parent%/}/moddotplot-interactive-reproducibility.XXXXXX") +trap 'rm -rf "$scratch_root"' EXIT INT TERM + +for clone_name in first second; do + clone_root="$scratch_root/$clone_name" + git clone --quiet --no-hardlinks "$repository_root" "$clone_root" + ( + cd "$clone_root/web" + npm ci --ignore-scripts --no-audit --no-fund + npm run build + ) + ( + cd "$clone_root/web/dist" + find . -type f -print | LC_ALL=C sort | xargs shasum -a 256 + ) > "$scratch_root/$clone_name.sha256" +done + +diff -u "$scratch_root/first.sha256" "$scratch_root/second.sha256" +echo "two clean builds produced byte-identical static assets" diff --git a/scripts/current-documentation-contract.mjs b/scripts/current-documentation-contract.mjs new file mode 100644 index 0000000..a4f4a9a --- /dev/null +++ b/scripts/current-documentation-contract.mjs @@ -0,0 +1,129 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +function read(root, relativePath) { + return readFileSync(join(root, relativePath), "utf8"); +} + +function numericProduct(expression, name) { + if (!expression) throw new Error(`missing numeric constant ${name}`); + return expression + .split("*") + .map((item) => Number(item.trim().replaceAll("_", ""))) + .reduce((product, factor) => product * factor, 1); +} + +function byteBudget(source, name) { + return numericProduct( + source.match(new RegExp(`const ${name}: usize = ([^;]+);`))?.[1], + name, + ); +} + +function typescriptConstant(source, name) { + return numericProduct( + source.match(new RegExp(`export const ${name} = ([^;]+);`))?.[1], + name, + ); +} + +function englishList(items) { + if (items.length < 2) return items[0] ?? ""; + if (items.length === 2) return `${items[0]} and ${items[1]}`; + return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`; +} + +function estimatorHeader(source) { + const start = source.indexOf('"estimator\\tregisters\\thll'); + if (start < 0) throw new Error("missing estimator validation header literal"); + const end = source.indexOf('"', start + 1); + if (end < 0) throw new Error("unterminated estimator validation header literal"); + return JSON.parse(source.slice(start, end + 1)).split("\t"); +} + +export function currentDocumentationContract(root) { + const wasm = [ + read(root, "crates/wasm/src/lib.rs"), + read(root, "crates/wasm/src/resource.rs"), + ].join("\n"); + const html = read(root, "web/index.html"); + const main = read(root, "web/src/main.ts"); + const webPackage = JSON.parse(read(root, "web/package.json")); + const importedTrackTypes = read(root, "web/src/imported-track-types.ts"); + const estimatorValidation = read(root, "crates/core/tests/estimator_validation.rs"); + const releaseValidation = read(root, "docs/releases/v0.2.1/estimator-validation.tsv"); + + const formatDeclaration = importedTrackTypes.match( + /export type ImportedTrackFormat = ([^;]+);/, + )?.[1]; + if (!formatDeclaration) throw new Error("missing ImportedTrackFormat declaration"); + const formats = [...formatDeclaration.matchAll(/"([^"]+)"/g)].map((match) => match[1]); + const formatLabels = new Map([ + ["bed", "BED3–12"], + ["gff3", "GFF3"], + ["gtf", "GTF"], + ]); + const labels = formats.map((format) => { + const label = formatLabels.get(format); + if (!label) throw new Error(`missing documentation label for imported format ${format}`); + return label; + }); + + const accept = html.match(/]*accept="([^"]+)"/)?.[1] + ?.split(",") + .map((extension) => extension.trim()); + if (!accept) throw new Error("missing track-file-input accept contract"); + const accepted = new Set(accept); + const baseExtensions = new Map([ + ["bed", [".bed"]], + ["gff3", [".gff", ".gff3"]], + ["gtf", [".gtf", ".gff2"]], + ]); + for (const format of formats) { + const bases = baseExtensions.get(format); + if (!bases) throw new Error(`missing accepted-extension mapping for ${format}`); + if (!bases.some((extension) => accepted.has(extension))) { + throw new Error(`${format} lacks a plain-text accepted extension`); + } + if (!bases.some((extension) => accepted.has(`${extension}.gz`))) { + throw new Error(`${format} lacks a gzip accepted extension`); + } + if (!bases.some((extension) => accepted.has(`${extension}.bgz`)) && !accepted.has(".bgzf")) { + throw new Error(`${format} lacks a BGZF accepted extension`); + } + } + + const sparseControl = html.match(/]*)>/i)?.[1]; + const sparseAssignment = /sparseCorrection:\s*sparseCorrectionInput\.checked/.test(main); + if (sparseControl !== undefined || sparseAssignment) { + throw new Error("browser sparse-correction controls or protocol fields reappeared"); + } + if (webPackage.scripts?.["build:wasm"]?.includes("--features validation")) { + throw new Error("browser build unexpectedly includes validation-only kernels"); + } + + const validationColumns = estimatorHeader(estimatorValidation); + const releaseValidationColumns = releaseValidation.split(/\r?\n/, 1)[0].split("\t"); + if (validationColumns.join("\t") !== releaseValidationColumns.join("\t")) { + throw new Error("estimator validation source header differs from the release TSV schema"); + } + + return { + budgets: { + zoomAxisBytes: byteBudget(wasm, "MAX_CACHED_AXIS_BYTES"), + preparedAxisBytes: byteBudget(wasm, "MAX_PREPARED_AXIS_BYTES"), + wasmSessionBytes: byteBudget(wasm, "MAX_WASM_SESSION_BYTES"), + }, + importedAnnotationSummary: `${englishList(labels)}; plain text, gzip, and BGZF`, + annotationLimits: { + bytes: typescriptConstant(importedTrackTypes, "MAX_IMPORTED_ANNOTATION_BYTES"), + records: typescriptConstant(importedTrackTypes, "MAX_IMPORTED_ANNOTATION_RECORDS"), + }, + browserSparseCorrectionSummary: "Removed; retained only in the non-default Rust validation module", + validationColumns, + }; +} + +export function formatMib(bytes) { + return `${bytes / 1024 / 1024} MiB`; +} diff --git a/scripts/generate-arabidopsis-overview.mjs b/scripts/generate-arabidopsis-overview.mjs new file mode 100644 index 0000000..6153764 --- /dev/null +++ b/scripts/generate-arabidopsis-overview.mjs @@ -0,0 +1,272 @@ +#!/usr/bin/env node + +import { createHash } from "node:crypto"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { basename, dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { gzipSync } from "node:zlib"; +import { encodePrecomputedOverview } from "../web/src/precomputed-overview.ts"; +import { transposeTile } from "../web/src/tile.ts"; +import { initSync, ComputeSession, ScientificConfig } from "../web/src/wasm/moddotplot_wasm.js"; + +const SCRIPT_VERSION = 2; +const RESOLUTIONS = [1_000, 2_000, 4_000]; +const K = 21; +const REGISTER_COUNT = 1_024; +const TILE_EDGE = 256; +const FASTA_CHUNK_BYTES = 8 * 1024 * 1024; +const PREPARATION_CHUNK_BINS = 64; +const scriptDirectory = dirname(fileURLToPath(import.meta.url)); +const repositoryRoot = resolve(scriptDirectory, ".."); +const examplesDirectory = resolve(repositoryRoot, "web/public/examples"); +const wasmDirectory = resolve(repositoryRoot, "web/src/wasm"); +const paths = { + fasta: resolve(examplesDirectory, "Col-CEN_v1.2.fasta"), + fai: resolve(examplesDirectory, "Col-CEN_v1.2.fasta.fai"), + annotation: resolve(examplesDirectory, "ColCEN_CEN180.gff3"), + wasm: resolve(wasmDirectory, "moddotplot_wasm_bg.wasm"), +}; + +function outputPath(resolution) { + return resolve( + examplesDirectory, + resolution === 1_000 + ? "Col-CEN_v1.2.Chr1.mdp-overview-v1.gz" + : `Col-CEN_v1.2.Chr1.${resolution}.mdp-overview-v1.gz`, + ); +} + +const checkOnly = process.argv.slice(2).includes("--check"); +const unknownArguments = process.argv.slice(2).filter((argument) => argument !== "--check"); +if (unknownArguments.length > 0) { + throw new Error(`Unknown argument(s): ${unknownArguments.join(", ")}`); +} +const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10); +if (nodeMajor < 25) { + throw new Error("Generating the precomputed overview requires Node.js 25 or newer for native TypeScript imports"); +} + +const wasmBytes = readFileSync(paths.wasm); +initSync({ module: wasmBytes }); +const fastaBytes = readFileSync(paths.fasta); +const faiBytes = readFileSync(paths.fai); +const annotationBytes = readFileSync(paths.annotation); +const faiText = faiBytes.toString("utf8"); +const faiRecords = parseFai(faiText); +const session = new ComputeSession(); +const config = new ScientificConfig(K, REGISTER_COUNT); + +try { + const parsedRecords = ingestFasta(session, fastaBytes); + assertRecordsMatchFai(parsedRecords, faiRecords); + const first = parsedRecords[0]; + if (!first || first.name !== "Chr1") { + throw new Error("The bundled overview contract requires Chr1 to be FASTA record zero"); + } + + const configMetadata = scientificMetadata(config); + for (const resolution of RESOLUTIONS) { + session.begin_prepare_scientific(0, 0, resolution, config, false); + let progress = 0; + while (progress < 1) progress = session.prepare_comparison_chunk(PREPARATION_CHUNK_BINS); + + const tileMap = computeWorkerEquivalentSelfTiles(session, config, resolution); + const artifact = { + schemaVersion: 1, + generator: `moddotplot-interactive/generate-arabidopsis-overview@${SCRIPT_VERSION}`, + sourceAssets: [ + sourceAsset("fasta", paths.fasta, fastaBytes), + sourceAsset("fai", paths.fai, faiBytes), + sourceAsset("annotation", paths.annotation, annotationBytes), + ], + fastaIndexText: faiText, + sequences: faiRecords.map((record, index) => ({ + index, + name: record.name, + description: "", + length: record.length, + sourceFile: basename(paths.fasta), + })), + comparison: { + xIndex: 0, + yIndex: 0, + resolution, + k: K, + configDigest: configMetadata.digest, + }, + configurations: [configMetadata], + tiles: [...tileMap.values()].sort((left, right) => left.y - right.y || left.x - right.x), + }; + const encoded = encodePrecomputedOverview(artifact); + const compressed = deterministicGzip(encoded); + const output = outputPath(resolution); + + if (checkOnly) { + if (!existsSync(output)) throw new Error(`Missing generated artifact: ${output}`); + const checkedIn = readFileSync(output); + if (!checkedIn.equals(compressed)) { + throw new Error( + `Generated overview is stale: run '${process.execPath} scripts/generate-arabidopsis-overview.mjs'`, + ); + } + console.log(`Precomputed ${resolution.toLocaleString()} overview is reproducible (${compressed.byteLength.toLocaleString()} bytes)`); + } else { + writeFileSync(output, compressed); + console.log(`Wrote ${output}`); + console.log(` resolution: ${resolution.toLocaleString()}`); + console.log(` raw: ${encoded.byteLength.toLocaleString()} bytes`); + console.log(` gzip: ${compressed.byteLength.toLocaleString()} bytes`); + console.log(` sha256: ${sha256(compressed)}`); + console.log(` decoded-sha256: ${sha256(encoded)}`); + console.log(` config: ${configMetadata.digest} (${configMetadata.identity})`); + } + } +} finally { + config.free(); + session.free(); +} + +function ingestFasta(target, fasta) { + target.begin_fasta(); + const records = []; + for (let offset = 0; offset < fasta.byteLength; offset += FASTA_CHUNK_BYTES) { + const end = Math.min(fasta.byteLength, offset + FASTA_CHUNK_BYTES); + appendRecords(records, target.push_fasta_chunk(fasta.subarray(offset, end))); + } + appendRecords(records, target.finish_fasta()); + return records; +} + +function appendRecords(output, value) { + if (!Array.isArray(value)) throw new Error("Wasm FASTA parsing returned invalid record metadata"); + for (const record of value) { + if ( + typeof record !== "object" || record === null + || typeof record.name !== "string" + || !(typeof record.length === "number" || typeof record.length === "bigint") + ) { + throw new Error("Wasm FASTA parsing returned a malformed record"); + } + output.push({ name: record.name, length: Number(record.length) }); + } +} + +function parseFai(text) { + return text.split(/\r?\n/u).filter(Boolean).map((line) => { + const fields = line.split("\t"); + if (fields.length < 5) throw new Error(`Malformed FAI line: ${line}`); + const name = fields[0]; + const length = Number(fields[1]); + if (!name || !Number.isSafeInteger(length) || length <= 0) { + throw new Error(`Invalid FAI record: ${line}`); + } + return { name, length }; + }); +} + +function assertRecordsMatchFai(records, fai) { + if (records.length !== fai.length) { + throw new Error(`FASTA produced ${records.length} records but its FAI declares ${fai.length}`); + } + for (let index = 0; index < fai.length; index += 1) { + const parsed = records[index]; + const indexed = fai[index]; + if (!parsed || !indexed || parsed.name !== indexed.name || parsed.length !== indexed.length) { + throw new Error(`FASTA record ${index} does not match its FAI metadata`); + } + } +} + +function computeWorkerEquivalentSelfTiles(target, scientificConfig, resolution) { + const tiles = new Map(); + for (let y = 0; y < resolution; y += TILE_EDGE) { + for (let x = y; x < resolution; x += TILE_EDGE) { + const wasmTile = target.compute_tile_scientific(x, y, TILE_EDGE, TILE_EDGE, scientificConfig); + const tile = { + quality: "refined", + configDigest: scientificConfig.digest, + resolution, + x, + y, + width: wasmTile.width, + height: wasmTile.height, + identity: wasmTile.take_identity(), + direction: wasmTile.take_direction(), + directionSupport: wasmTile.take_direction_support(), + }; + wasmTile.free(); + tiles.set(tileKey(x, y), tile); + if (x !== y) { + const mirror = transposeTile( + tile.identity, + tile.direction, + tile.directionSupport, + tile.width, + tile.height, + ); + tiles.set(tileKey(y, x), { + ...tile, + x: y, + y: x, + width: mirror.width, + height: mirror.height, + identity: mirror.identity, + direction: mirror.direction, + directionSupport: mirror.directionSupport, + }); + } + } + } + const tilesPerAxis = Math.ceil(resolution / TILE_EDGE); + if (tiles.size !== tilesPerAxis * tilesPerAxis) { + throw new Error(`Expected ${tilesPerAxis * tilesPerAxis} full-matrix tiles, generated ${tiles.size}`); + } + return tiles; +} + +function tileKey(x, y) { + return `${x}:${y}`; +} + +function scientificMetadata(scientificConfig) { + const identity = scientificConfig.identity; + if (typeof identity !== "string" || !/^[0-9a-f]{108}$/u.test(identity)) { + throw new Error("The generated Wasm bindings do not expose the canonical ScientificConfig.identity"); + } + return { + version: scientificConfig.version, + identity, + digest: scientificConfig.digest, + hashAlgorithm: scientificConfig.hash_algorithm, + hashSeed: scientificConfig.hash_seed, + estimator: scientificConfig.estimator, + k: scientificConfig.k, + registerCount: scientificConfig.register_count, + hllPrecision: scientificConfig.hll_precision, + bBits: scientificConfig.b_bits, + verificationBits: scientificConfig.verification_bits, + identityScale: scientificConfig.identity_scale, + missingIdentity: scientificConfig.missing_identity, + }; +} + +function sourceAsset(role, path, bytes) { + return { + role, + fileName: basename(path), + byteLength: bytes.byteLength, + sha256: sha256(bytes), + }; +} + +function sha256(bytes) { + return createHash("sha256").update(bytes).digest("hex"); +} + +function deterministicGzip(bytes) { + const compressed = gzipSync(bytes, { level: 9, mtime: 0 }); + // RFC 1952 byte 9 is an informational originating-OS marker. Neutralizing it + // makes output identical across Unix, macOS, and Windows Node distributions. + compressed[9] = 255; + return compressed; +} diff --git a/scripts/generate-current-parameters.mjs b/scripts/generate-current-parameters.mjs new file mode 100644 index 0000000..6d13203 --- /dev/null +++ b/scripts/generate-current-parameters.mjs @@ -0,0 +1,126 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + currentDocumentationContract, + formatMib, +} from "./current-documentation-contract.mjs"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const config = readFileSync(join(root, "crates/core/src/config.rs"), "utf8"); +const adaptive = readFileSync(join(root, "crates/core/src/validation/adaptive.rs"), "utf8"); +const wasm = ["lib.rs", "resource.rs"] + .map((name) => readFileSync(join(root, "crates/wasm/src", name), "utf8")) + .join("\n"); +const refinement = readFileSync(join(root, "web/src/refinement.ts"), "utf8"); +const html = readFileSync(join(root, "web/index.html"), "utf8"); +const documentationContract = currentDocumentationContract(root); + +function rustConstant(source, name) { + const match = source.match(new RegExp(`pub const ${name}: [^=]+ = ([^;]+);`)); + if (!match?.[1]) throw new Error(`missing Rust constant ${name}`); + return match[1].trim().replaceAll("_", ""); +} + +function selectedOption(id) { + const select = html.match(new RegExp(``))?.[1]; + const value = select?.match(/