diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..81ce140d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustdocflags = ["--html-in-header", "katex-header.html"] diff --git a/.github/agents/code-generation.agent.md b/.github/agents/code-generation.agent.md new file mode 100644 index 00000000..eb7a6201 --- /dev/null +++ b/.github/agents/code-generation.agent.md @@ -0,0 +1,76 @@ +--- +name: code-generation +description: > + Code generation and programming problem solving agent for the Fink-FAT project. + Uses Context7 to retrieve up-to-date library documentation before writing or + debugging code. Prefer this agent for any implementation task: new features, + bug fixes, scripts, tests, refactors, and dependency/API questions. +model: "GPT-5.3-Codex (copilot)" +argument-hint: Describe the programming task or problem to solve +--- + +# Code Generation Agent + +## Role + +Implement, debug, and improve code across the Fink-FAT project. +The project contains Rust crates (`fink-fat-engine`, `fink-fat-eval`) and +Python scripts (`test_exp/`, `edge_ml_prediction/`). + +## Library documentation workflow + +Before writing or fixing code that depends on an external library, always +retrieve up-to-date documentation via Context7: + +1. Call `mcp_io_github_ups_resolve-library-id` with the library name to get + its Context7-compatible ID. +2. Call `mcp_io_github_ups_get-library-docs` with that ID to fetch relevant + documentation and code examples. +3. Use the retrieved documentation to write correct, idiomatic code. + +Always resolve the library first — never guess API signatures from memory alone +when Context7 can confirm them. + +## Implementation discipline + +- Read and understand existing code before modifying it. +- Make only changes that are directly requested or clearly necessary. +- Do not add features, comments, or error handling beyond the scope of the task. +- Validate changes by running the code (`run_in_terminal`) or checking errors + (`get_errors`) after every non-trivial edit. +- Use `manage_todo_list` for multi-step tasks. +- Ask to user for any terminal commands or code edits that have side effects or are not easily reversible. + +## Git policy + +Read-only git inspection commands are allowed: `git status`, `git log`, +`git diff`, `git show`, `git branch`, `git stash list`, etc. + +The following operations are **strictly forbidden** — do not run them under any +circumstances, even if explicitly asked: +`git push`, `git pull`, `git fetch`, `git merge`, `git rebase`, `git reset`, +`git stash` (push/pop/drop/clear), `git commit`, `git tag`, `git remote`, +`git cherry-pick`, `git revert`, `git clean`, `git rm`, `git mv`, +`git submodule`, `git worktree add/remove`, and any command with +`--force` / `-f` flags. + +If a task would require a forbidden git operation, explain what needs to be done +and let the user execute it manually. + +## Language conventions + +All code, comments, docstrings, variable names, commit messages, and file +content must be written in **English only**. Never use French (or any other +language) in files, regardless of what the user writes in chat. + +### Rust +- Follow standard Rust idioms: `?` for error propagation, iterators over loops, + `clippy`-clean code. +- Run `cargo check` or `cargo clippy` after edits to catch compile errors early. + +### Python +- Target Python 3.12. +- Use `pathlib.Path` over `os.path`. +- Prefer numpy vectorised operations over Python loops on large arrays. +- Format with `black` when editing existing files that already use it. +- Use pdm for dependency management and packaging in `edge_ml_prediction/` and more generally. diff --git a/.github/agents/fink_fat_doc.agent.md b/.github/agents/fink_fat_doc.agent.md new file mode 100644 index 00000000..ba5c7163 --- /dev/null +++ b/.github/agents/fink_fat_doc.agent.md @@ -0,0 +1,274 @@ +--- +name: fink_fat_doc +description: Documentation writer for the Fink-FAT asteroid detection pipeline +model: "GPT-5.4 mini (copilot)" +argument-hint: Specify the module or component to document +--- + +# Fink-FAT Documentation Agent + +## Project context + +Fink-FAT is an asteroid detection pipeline written in Rust. It ingests photometric +alerts from large sky surveys (primarily ZTF and the Vera Rubin Observatory). Each +alert carries at minimum: + +- sky position (RA/Dec), +- observation time (MJD), +- magnitude and its associated uncertainty. + +The pipeline links alerts across nights to form candidate trajectories, which are +then forwarded to an orbit estimator for confirmation. + +## Agent goal + +This agent reads the Fink-FAT codebase and writes or updates Rust documentation +(docstrings and module-level `//!` comments). It must: + +1. Read and understand the relevant source files before writing anything. +2. Write documentation that is accurate, complete, and consistent with the code. +3. Keep documentation up to date as the codebase evolves. +4. Maintain a todo list of undocumented or outdated items when working across + multiple files. + +## Documentation rules + +### Language and tone +- All documentation is written in **English**. +- Tone is neutral, technical, and scientific — no conversational language. +- No mentions of AI, conversations, or the generation process. + +### Format +- Docstrings use standard Rust `///` line comments. +- Module-level documentation uses `//!` comments at the top of each file. +- Markdown formatting is allowed and encouraged inside docstrings: + headings, bullet points, backtick code spans, bold, italic. +- Do **not** add usage examples or code snippets unless explicitly requested. +- Mathematical expressions use LaTeX syntax: inline with `$...$`, + display block with `$$...$$`. KaTeX is enabled in this project. + +### LaTeX rendering in doc comments + +KaTeX is enabled via `--html-in-header katex-header.html`. However, because +`rustdoc` renders doc comments as Markdown before passing them to KaTeX, +**Markdown parsing can corrupt LaTeX syntax** before KaTeX sees it. + +#### Underscores + +The most common issue is with **subscript underscores**: Markdown interprets +`_text_` as italics. Any underscore inside a `$...$` expression that is not +escaped will be consumed by the Markdown parser. + +**Rule: escape every underscore used as a LaTeX subscript with `\_`.** + +| Source | Rendered outcome | +|-----------------------------------------------|---------------------------| +| `$x_i$` | ❌ Markdown eats `_i$` | +| `$x\_i$` | ✅ KaTeX sees `x_i` | +| `$\mathbf{p}_{\text{to}}$` | ❌ broken | +| `$\mathbf{p}\_{\text{to}}$` | ✅ correct | +| `$\mathbf{r} = \mathbf{p}\_{\mathrm{pred}}$` | ✅ correct | + +#### Thin spaces (`\,`) + +Never use `\,` (LaTeX thin space) inside `$$...$$` blocks in rustdoc. +KaTeX renders it as a visible comma in this context. + +| Source | Rendered outcome | +|-------------------------------------|-------------------------------| +| `$$\frac{1}{2}\,\chi^2$$` | ❌ renders a comma | +| `$$\frac{1}{2}\chi^2$$` | ✅ correct | + +#### Block formulas (`$$...$$`) + +Markdown can parse `+`, `-`, or `*` at the start of a line as a list item, +even inside a `$$...$$` block that spans multiple `///` lines. This fragments +the formula before KaTeX sees it. + +**Rule: always write block formulas on a single line, using `\begin{align}` +and `\\` for visual line breaks.** + +``` +/// $$\begin{align} c &= \frac{1}{2}\chi^2\_{\text{pos}} \\ &+ \frac{1}{2}\chi^2\_{\text{vel}} \end{align}$$ +``` + +Never split a `$$...$$` block across multiple `///` lines. + +#### Subscript label style + +Prefer `\mathrm{...}` over `\text{...}` for subscript labels — both work +equally well with KaTeX, but `\mathrm` is more semantically accurate for +mathematical identifiers. + +#### Reference working example + +```rust +/// $$\begin{align} c &= \frac{1}{2}\chi^2\_{\text{pos}} \\ &+ \frac{1}{2}\chi^2\_{\text{vel}} \\ &+ \frac{1}{2}z\_{\text{flux}}^{2} \\ &+ \frac{1}{2}\bigl[\ln(|r\_{\sigma}| + \varepsilon)\bigr]^2 \\ &+ \ln(\varepsilon\_{\text{band}} + b\_{\text{shared}}) \end{align}$$ +/// +/// where $r\_{\sigma}$ is `flux_std_ratio` and $b\_{\text{shared}} \in \{0, 1\}$. +``` + +#### Summary of LaTeX rules + +| Rule | Correct | Broken | +|-----------------------------------|--------------------------------|---------------------------| +| Subscript underscore | `$x\_i$` | `$x_i$` | +| Thin space before fraction result | `\frac{1}{2}\chi^2` | `\frac{1}{2}\,\chi^2` | +| Multiline block formula | single line with `\begin{align}` | split across `///` lines | +| Subscript label style | `\mathrm{pos}` | `\text{pos}` (acceptable but less precise) | + +### Cross-references + +Use Rust intra-doc links to reference related types, traits, methods, structs, +and enums whenever it helps the reader navigate the codebase. + +**Syntax:** + +- Item from this crate: [`crate::module::SubModule::Item`] +- External crate item with display text: [`AHashMap`](ahash::AHashMap) +- Method on a local type: [`SeedNode::seed_edge_candidates`](crate::seeds::SeedNode::seed_edge_candidates) + +**Rules:** + +- For any item defined in this crate, use the **full path** starting + from `crate::` — partial paths may resolve in the current module but will + produce dead links elsewhere in the generated documentation. Only exception is + when the doc compilation triggers a redundant link warning, in which case you + can omit the `crate::` prefix. +- For items from external crates, prefer the `[display text](crate::path)` + form to keep the rendered text readable. +- Only add cross-references when they genuinely help understanding — do not + link every mention of every type mechanically. +- Verify that the referenced path actually exists before inserting the link. + A dead link (`[`Foo`]` pointing to nothing) is worse than plain text. + +### Docstring structure + +The **minimum required sections** for any function or method are: + +- `Arguments` — one entry per parameter. +- `Return` — description of the return value or error variants. + +The following sections are **optional** and should be included only when they +add meaningful information: + +- One-line summary (always, as the very first line). +- Extended description paragraph (when the behavior is non-trivial). +- `Behavior` — when the function operates in multiple modes. +- `Parallelism` — when concurrency affects observable behavior. +- `Errors` — when error conditions deserve more detail than the Return section. +- `Panics` — when the function can panic and under which conditions. +- `Notes` — for caveats, ordering guarantees, complexity, or cross-references. + +Use judgment: a two-line helper does not need six sections. + +### Reference docstring + +The following example illustrates the expected style and level of detail: + + /// Build directed edges between two seed slices. + /// + /// This is the main entrypoint to construct the inter-night bipartite edge + /// set between two seed collections (typically two nights). + /// + /// Behavior (two modes) + /// -------------------- + /// Controlled by `edge_config.emit_all_edges`: + /// + /// - If `true`: + /// - emits *all* candidate edges returned by `SeedNode::seed_edge_candidates`, + /// - computes `EdgeFeatures`, + /// - derives the solver cost from + /// `EdgeFeatures::kinematic_log_likelihood_cost()`. + /// + /// - If `false`: + /// - requires `model_pool` to be `Some(...)`, + /// - ranks candidates per-left seed using ONNX ML + /// (`rank_topk_edges_for_left`), + /// - keeps only `top_k_per_left` best candidates (by `p(class=1)`), + /// - derives the solver cost from features. + /// + /// Parallelism + /// ----------- + /// Controlled by: + /// + /// - `edge_config.parallel_left_batches` + /// - `edge_config.parallel_left_batch_size` + /// + /// If enabled: + /// - left seeds are processed in Rayon parallel chunks. + /// + /// If disabled: + /// - the same chunking logic is applied sequentially. + /// + /// Arguments + /// --------- + /// * `left` – Slice of source seeds (earlier epoch). + /// * `right` – Slice of target seeds (later epoch). + /// * `edge_config` – Configuration controlling: + /// - candidate search constraints, + /// - ML toggle, + /// - Top-K pruning, + /// - ONNX batching, + /// - parallelism. + /// * `spatial_binner` – Spatial partitioner used to index `right`. + /// * `time_binner_width` – Time bin width (days) for the uniform time index. + /// * `model_pool` – Optional ML model pool: + /// - required if `emit_all_edges == false`, + /// - ignored otherwise. + /// * `progress_sink` – Progress reporter updated per processed chunk. + /// + /// Return + /// ------ + /// * `Ok(Vec)` – Constructed edges referencing `left` and `right`. + /// * `Err(EdgeBuilderError)` – If: + /// - input slices are invalid, + /// - ML mode is enabled but no model pool is provided, + /// - ONNX inference fails. + /// + /// Notes + /// ----- + /// - The returned edge list is **not globally sorted**. + /// If deterministic ordering is required, sort at the call site. + /// - `SeedSpatialIndex::build` is invoked exactly once. + +### Module-level docstrings + +Every module must have a `//!` block at the top of its file. It should cover: + +- The purpose of the module and its role in the pipeline. +- The main types, traits, or functions it exposes. +- Any domain-specific concepts needed to understand the module + (e.g., what a "seed", an "edge", or a "trajectory" means in this context). +- Relevant mathematical background when applicable, using `$$...$$` blocks. + +**Syntax rule:** module-level comments **must** use `//!` (inner doc comments), +not `///` (outer doc comments). A `///` comment at the top of a file is not +attached to the module and will not be rendered by `rustdoc`. The correct +pattern is: + + //! # Module name + //! + //! Description of the module... + //! + //! ## Main types + //! + //! - [`Foo`](crate::module::Foo) — does X. + //! - [`Bar`](crate::module::Bar) — does Y. + +Every line of the module-level block must start with `//!`, including blank +separator lines. A blank line without `//!` terminates the inner doc comment +block, and everything after it is silently ignored by `rustdoc`. + +## Workflow + +1. Read the target file(s) with the `read` tool. +2. Identify undocumented or poorly documented items. +3. Cross-reference related modules with `search` if needed to ensure accuracy. +4. Write or update docstrings with the `edit` tool. +5. Use the `todo` tool to track items that span multiple sessions. +6. Test the generated documentation by running `cargo doc` and verifying the output. There should be no errors or warnings. The command to run is: + +```bash +RUSTDOCFLAGS="--html-in-header $(pwd)/katex-header.html" cargo doc --workspace +``` diff --git a/.github/skills/add-engine-feature/SKILL.md b/.github/skills/add-engine-feature/SKILL.md new file mode 100644 index 00000000..071e7242 --- /dev/null +++ b/.github/skills/add-engine-feature/SKILL.md @@ -0,0 +1,237 @@ +--- +name: add-engine-feature +description: 'Step-by-step guide for adding a new feature to the fink-fat-engine Rust crate. Use when implementing a new module, solver, seeding strategy, cost function, edge feature, config option, or pipeline stage. Covers: module placement, naming conventions, error types, EngineConfig wiring, re-exports, unit/integration tests, rustdoc, and verification commands.' +argument-hint: 'Describe the feature to add (e.g. "new min-cost-flow solver variant", "new edge feature for photometry residual")' +--- + +# Add a Feature to fink-fat-engine + +## Overview + +The engine lives in `crates/fink-fat-engine/src/`. All features follow the same layered structure: +domain module → config struct → error type → pipeline wiring → tests → rustdoc. + +--- + +## Step 1 — Decide the module location + +Use the table below to find where the new code belongs. + +| What you're adding | Location | +|---|---| +| New seeding strategy (pairs / triplets) | `src/seeding/` | +| New solver variant | `src/solver/` | +| New cost function or edge feature | `src/graph/edge/` | +| New spatial/temporal bucketing | `src/spacetime_bucket/` | +| New pipeline stage | `src/pipeline/stages/` | +| Reusable math primitives | `src/astro_math.rs` | +| New top-level domain | `src//` (new folder) | + +If adding a **new top-level domain**, create: +``` +src// + mod.rs ← public API + re-exports + error.rs ← domain-specific error enum + .rs ← implementation file(s) +``` + +--- + +## Step 2 — Create the implementation file(s) + +> **Delegate to the `code-generation` agent** for this step and all subsequent implementation steps (Steps 2–5 and Step 7). + +### Naming conventions +- **Newtypes** for semantic types: `MyDomainId(u32)`, `MyKey` +- **`*Store`** for indexed collections +- **`*Builder`** / `build_*` for construction functions +- **`*Config`** for configuration structs +- **`*Error`** for error enums +- **`*Pool`** for reusable / thread-local resources +- Use `AHashMap` (from `ahash`) instead of `std::HashMap` in hot paths +- Use `smallvec::SmallVec` for collections that are usually small + +### Error type (src//error.rs) + +```rust +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("...")] + Variant(/* cause */), +} +``` + +Then add a variant to `src/error.rs` wrapping it: +```rust +#[error(transparent)] +(#[from] crate::::error::Error), +``` + +--- + +## Step 3 — Wire up the module + +### 3a. Declare the submodule in the parent `mod.rs` + +```rust +// In src//mod.rs (or src/lib.rs for top-level domains) +pub mod ; +pub use ::{MyType, MyKey}; // re-export what belongs to the public API +``` + +### 3b. For top-level domains — register in `src/lib.rs` + +```rust +pub mod ; +// optional public re-export: +pub use ::MyType; +``` + +### 3c. For new pipeline stages — update `src/pipeline/stages/` + +1. Add a variant to the `PipelineStage` enum. +2. Implement the stage handler (input/output types must match surrounding stages). +3. Register it in `src/pipeline/mod.rs` (`PipelineRunner::run`). + +--- + +## Step 4 — Add configuration + +If the feature is configurable, add a sub-config struct in `src/engine_config/`: + +```rust +// src/engine_config/_config.rs +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub param: f64, +} + +impl Default for Config { + fn default() -> Self { Self { param: 1.0 } } +} +``` + +Then embed it in `EngineConfig` in `src/engine_config/mod.rs`: +```rust +pub : Config, +``` + +Config is loaded from YAML + env vars automatically (prefix `FINK_FAT__`, separator `__`). + +--- + +## Step 5 — Write tests + +### Unit tests (inline in the implementation file) + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_behavior() { + // ... + } +} +``` + +### Integration tests (in `crates/fink-fat-engine/tests/`) + +For features that touch the full pipeline, add a file under `tests/_test.rs` and declare it in `tests/mod.rs`: +```rust +mod _test; +``` + +Use `SyntheticDatasetBuilder` (from `tests/synthetic_alerts.rs`) to generate realistic multi-night datasets without needing real Parquet files: +```rust +use crate::synthetic_alerts::SyntheticDatasetBuilder; + +let dataset = SyntheticDatasetBuilder::default() + .with_nea_tracks(10) + .build(); +``` + +### Property-based tests (for algorithmic invariants) + +```rust +use proptest::prelude::*; + +proptest! { + #[test] + fn prop_invariant_holds(input in 0.0f64..1.0) { + // ... + } +} +``` + +--- + +## Step 6 — Add rustdoc to the public API + +> **Delegate to the `fink_fat_doc` agent** for this step. Provide it with the list of `pub` items added in Steps 2–4. + +Every `pub` type, function, and module that is part of the public API needs a `///` doc comment: + +```rust +/// Short one-line summary. +/// +/// Longer explanation if needed. Reference related types with [`OtherType`]. +/// +/// # Errors +/// +/// Returns [`MyDomainError::Variant`] when ... +pub fn my_function(...) -> Result<..., MyDomainError> { ... } +``` + +--- + +## Step 7 — Verify the change + +Run these commands in order from the workspace root: + +```bash +# 1. Compile-check the engine crate only (fast) +cargo check -p fink-fat-engine + +# 2. Clippy (catches common patterns and style issues) +cargo clippy -p fink-fat-engine -- -D warnings + +# 3. Run all tests +cargo test -p fink-fat-engine + +# 4. Run a specific test by name +cargo test -p fink-fat-engine + +# 5. Run benchmarks (optional – only when changing hot paths) +cargo bench -p fink-fat-engine +# Or via the helper script: +# crates/fink-fat-engine/run_bench.sh +``` + +If adding a **new pipeline stage**, also run the eval suite to check for regressions: +```bash +cargo run -p fink-fat-eval -- --config crates/fink-fat-eval/eval_config_best.yml +``` + +--- + +## Checklist + +Before marking the feature complete, verify each item: + +- [ ] Module file(s) created in the right domain folder +- [ ] Domain error type created in `error.rs` using `thiserror` +- [ ] New error variant added to top-level `src/error.rs` +- [ ] `pub mod` + `pub use` added in parent `mod.rs` +- [ ] Registered in `src/lib.rs` if it's a new top-level domain +- [ ] `*Config` struct added to `src/engine_config/` (if configurable) +- [ ] Embedded in `EngineConfig` with `Default` impl +- [ ] Inline `#[cfg(test)]` unit tests written +- [ ] Integration test file added under `tests/` (if touching pipeline) +- [ ] `///` rustdoc on all `pub` items +- [ ] `cargo check`, `cargo clippy`, `cargo test` all pass diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8baca7ca..559477d6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,8 +10,8 @@ permissions: contents: read jobs: - rust-tests: - name: Rust tests + fmt: + name: Format check runs-on: ubuntu-latest steps: - name: Checkout @@ -19,24 +19,39 @@ jobs: - name: Install Rust (stable) uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: cargo fmt + run: cargo fmt --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Install system dependencies + # Note: Needed for plotters in the fink-fat-eval crate + run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev - name: Cache Rust uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - name: Run cargo tests - run: | - cargo test --all --all-targets --verbose + - name: cargo clippy + run: cargo clippy --all-targets --all-features -- -D warnings - python-tests: - name: Python tests (maturin + pytest) + check: + name: Cargo check runs-on: ubuntu-latest - needs: rust-tests - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12"] steps: - name: Checkout uses: actions/checkout@v4 @@ -44,35 +59,78 @@ jobs: - name: Install Rust (stable) uses: dtolnay/rust-toolchain@stable + - name: Install system dependencies + # Note: Needed for plotters in the fink-fat-eval crate + run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev + - name: Cache Rust uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - name: cargo check + run: cargo check --all-targets --all-features + + doc: + name: Documentation check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Install system dependencies + # Note: Needed for plotters in the fink-fat-eval crate + run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 with: - python-version: ${{ matrix.python-version }} + cache-on-failure: true - - name: Create virtualenv (.venv) - run: | - python -m venv .venv - ./.venv/bin/python -V + - name: cargo doc + run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features - - name: Upgrade pip & install test deps (in .venv) - run: | - ./.venv/bin/python -m pip install --upgrade pip wheel setuptools - ./.venv/bin/pip install pytest pytest-cov maturin + coverage: + name: Tests & Coverage + runs-on: ubuntu-latest + needs: [fmt, clippy, check, doc] + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Build and develop-install the extension (maturin) - run: | - ./.venv/bin/maturin develop --release --features python-extension + - name: Install Rust stable + llvm-tools + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview - - name: Show installed package info - run: | - ./.venv/bin/python -c "import sys; print('Python:', sys.version)" - ./.venv/bin/python -c "import fink_fat as m; print('fink_fat:', getattr(m,'__version__','unknown')); print('from:', m.__file__)" + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Clean coverage artifacts + run: cargo llvm-cov clean --workspace - - name: Run pytest + - name: Generate coverage (engine only, lcov) + env: + RAYON_NUM_THREADS: 2 + RUST_TEST_THREADS: 1 run: | - ./.venv/bin/pytest -q --maxfail=1 --disable-warnings -ra + cargo llvm-cov \ + --package fink-fat-engine \ + --all-features \ + --lcov --output-path lcov.info + + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + files: lcov.info + flags: rust + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 524d7432..73c7f5de 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ __pycache__/ .Python .venv/ env/ -bin/ +# bin/ build/ develop-eggs/ dist/ @@ -81,3 +81,11 @@ linked_trajectories.parquet linking_metric.py linking_inter.py Fink-FAT/ +min_cost_flow.py +test_config.toml +test_exp/ + +crates/fink-fat-engine/.github/agents/ +docs/ +edge_plots/ +model_eval_plots/ diff --git a/.husky/hooks/pre-commit b/.husky/hooks/pre-commit index c20ca629..d2075240 100644 --- a/.husky/hooks/pre-commit +++ b/.husky/hooks/pre-commit @@ -10,4 +10,4 @@ echo "Running cargo check..." cargo check --all-targets --all-features || exit 1 echo "Checking documentation build..." -RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features || exit 1 +RUSTDOCFLAGS="-D warnings --html-in-header $(pwd)/katex-header.html" cargo doc --no-deps --all-features -p fink-fat -p fink-fat-engine || exit 1 diff --git a/Cargo.lock b/Cargo.lock index d68b5ced..e138708c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,43 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aberth" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb783fafbd88179a8f541eddc7d23edb991791ff3ed4f86098dd64a418626fb" +dependencies = [ + "arrayvec", + "num-complex", + "num-traits", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -15,8 +46,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "const-random", + "getrandom 0.3.3", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -31,1323 +64,7291 @@ dependencies = [ ] [[package]] -name = "anes" -version = "0.1.6" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] -name = "anstyle" -version = "1.0.13" +name = "alloc-stdlib" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] [[package]] -name = "autocfg" -version = "1.5.0" +name = "alloca" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] [[package]] -name = "base64" -version = "0.21.7" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "bit-set" -version = "0.8.0" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "bit-vec", + "libc", ] [[package]] -name = "bit-vec" -version = "0.8.0" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "bitflags" -version = "1.3.2" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] [[package]] -name = "bitflags" -version = "2.9.4" +name = "anstyle" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] -name = "bumpalo" -version = "3.19.0" +name = "anstyle-parse" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] [[package]] -name = "byteorder" -version = "1.5.0" +name = "anstyle-query" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.1", +] [[package]] -name = "camino" -version = "1.2.1" +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.1", +] [[package]] -name = "cast" -version = "0.3.0" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "cdshealpix" -version = "0.7.3" +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c857a9ddf0d44d0a5724ebcd53e299eb260bf105b87162faad07d9b3517af67" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ - "base64", - "byteorder", - "colorous", - "flate2", - "itertools 0.13.0", - "katex-doc", - "log", - "mapproj", - "num", "num-traits", - "png", - "serde", - "thiserror 1.0.69", ] [[package]] -name = "cfg-if" -version = "1.0.3" +name = "ar_archive_writer" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] [[package]] -name = "ciborium" -version = "0.2.2" +name = "argminmax" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", + "num-traits", ] [[package]] -name = "ciborium-io" -version = "0.2.2" +name = "array-init-cursor" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" +checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3" [[package]] -name = "ciborium-ll" -version = "0.2.2" +name = "arraydeque" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] -name = "clap" -version = "4.5.48" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "arrow" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4754a624e5ae42081f464514be454b39711daae0458906dacde5f4c632f33a8" dependencies = [ - "clap_builder", + "arrow-arith", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-cast 57.3.0", + "arrow-csv", + "arrow-data 57.3.0", + "arrow-ipc 57.3.0", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "arrow-string", ] [[package]] -name = "clap_builder" -version = "4.5.48" +name = "arrow-arith" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "f7b3141e0ec5145a22d8694ea8b6d6f69305971c4fa1c1a13ef0195aef2d678b" dependencies = [ - "anstyle", - "clap_lex", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "num-traits", ] [[package]] -name = "clap_lex" -version = "0.7.5" +name = "arrow-array" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a12fcdb3f1d03f69d3ec26ac67645a8fe3f878d77b5ebb0b15d64a116c212985" +dependencies = [ + "ahash", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "chrono", + "half", + "hashbrown 0.15.5", + "num", +] [[package]] -name = "colorous" -version = "1.0.16" +name = "arrow-array" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e18bf7a165bf7028fde98609a0f1e8f7498d762a212598e6c891f6893556ec" +checksum = "4c8955af33b25f3b175ee10af580577280b4bd01f7e823d94c7cdef7cf8c9aef" +dependencies = [ + "ahash", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "chrono-tz", + "half", + "hashbrown 0.16.1", + "num-complex", + "num-integer", + "num-traits", +] [[package]] -name = "console" -version = "0.16.1" +name = "arrow-buffer" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +checksum = "263f4801ff1839ef53ebd06f99a56cecd1dbaf314ec893d93168e2e860e0291c" dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys", + "bytes", + "half", + "num", ] [[package]] -name = "crc32fast" -version = "1.5.0" +name = "arrow-buffer" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "c697ddca96183182f35b3a18e50b9110b11e916d7b7799cbfd4d34662f2c56c2" dependencies = [ - "cfg-if", + "bytes", + "half", + "num-bigint", + "num-traits", ] [[package]] -name = "criterion" -version = "0.7.0" +name = "arrow-cast" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "ede6175fbc039dfc946a61c1b6d42fd682fcecf5ab5d148fbe7667705798cac9" dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "itertools 0.13.0", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_json", - "tinytemplate", - "walkdir", + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.3.1", + "atoi", + "base64 0.22.1", + "chrono", + "half", + "lexical-core", + "num", + "ryu", ] [[package]] -name = "criterion-plot" -version = "0.6.0" +name = "arrow-cast" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "646bbb821e86fd57189c10b4fcdaa941deaf4181924917b0daa92735baa6ada5" dependencies = [ - "cast", - "itertools 0.13.0", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-ord", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "atoi", + "base64 0.22.1", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num-traits", + "ryu", ] [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "arrow-csv" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "8da746f4180004e3ce7b83c977daf6394d768332349d3d913998b10a120b790a" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "arrow-array 57.3.0", + "arrow-cast 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "csv", + "csv-core", + "regex", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "arrow-data" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "61cfdd7d99b4ff618f167e548b2411e5dd2c98c0ddebedd7df433d34c20a4429" dependencies = [ - "crossbeam-utils", + "arrow-buffer 54.3.1", + "arrow-schema 54.3.1", + "half", + "num", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "arrow-data" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "1fdd994a9d28e6365aa78e15da3f3950c0fdcea6b963a12fa1c391afb637b304" +dependencies = [ + "arrow-buffer 57.3.0", + "arrow-schema 57.3.0", + "half", + "num-integer", + "num-traits", +] [[package]] -name = "crunchy" -version = "0.2.4" +name = "arrow-ipc" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "62ff528658b521e33905334723b795ee56b393dbe9cf76c8b1f64b648c65a60c" +dependencies = [ + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "flatbuffers 24.12.23", +] [[package]] -name = "either" -version = "1.15.0" +name = "arrow-ipc" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "abf7df950701ab528bf7c0cf7eeadc0445d03ef5d6ffc151eaae6b38a58feff1" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "flatbuffers 25.12.19", + "lz4_flex 0.12.0", + "zstd", +] [[package]] -name = "encode_unicode" -version = "1.0.0" +name = "arrow-json" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "0ff8357658bedc49792b13e2e862b80df908171275f8e6e075c460da5ee4bf86" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-cast 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "half", + "indexmap", + "itoa", + "lexical-core", + "memchr", + "num-traits", + "ryu", + "serde_core", + "serde_json", + "simdutf8", +] [[package]] -name = "equivalent" -version = "1.0.2" +name = "arrow-ord" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "f7d8f1870e03d4cbed632959498bcc84083b5a24bded52905ae1695bd29da45b" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", +] [[package]] -name = "errno" -version = "0.3.14" +name = "arrow-row" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "18228633bad92bff92a95746bbeb16e5fc318e8382b75619dec26db79e4de4c0" dependencies = [ - "libc", - "windows-sys", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "half", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "arrow-schema" +version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "39cfaf5e440be44db5413b75b72c2a87c1f8f0627117d110264048f2969b99e9" [[package]] -name = "fdeflate" -version = "0.3.7" +name = "arrow-schema" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" dependencies = [ - "simd-adler32", + "serde_core", + "serde_json", ] [[package]] -name = "fink-fat" -version = "1.0.0" +name = "arrow-select" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69efcd706420e52cd44f5c4358d279801993846d1c2a8e52111853d61d55a619" dependencies = [ "ahash", - "camino", - "cdshealpix", - "criterion", - "husky-rs", - "indicatif", - "itertools 0.14.0", - "numpy", - "proptest", - "pyo3", - "rand", - "serde", - "tempfile", - "thiserror 2.0.17", - "toml", + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "num", ] [[package]] -name = "flate2" -version = "1.1.2" +name = "arrow-select" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "68bf3e3efbd1278f770d67e5dc410257300b161b93baedb3aae836144edcaf4b" dependencies = [ - "crc32fast", - "miniz_oxide", + "ahash", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "num-traits", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "arrow-string" +version = "57.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "85e968097061b3c0e9fe3079cf2e703e487890700546b5b0647f60fca1b5a8d8" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "memchr", + "num-traits", + "regex", + "regex-syntax", +] [[package]] -name = "getrandom" -version = "0.3.3" +name = "assert_cmd" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" dependencies = [ - "cfg-if", + "anstyle", + "bstr", "libc", - "r-efi", - "wasi", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", ] [[package]] -name = "half" -version = "2.6.0" +name = "async-compression" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d67d43201f4d20c78bcda740c142ca52482d81da80681533d33bf3f0596c8e2" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atoi_simd" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a49e05797ca52e312a0c658938b7d00693ef037799ef7187678f212d7684cf" +dependencies = [ + "debug_unsafe", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ + "addr2line", "cfg-if", - "crunchy", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", ] [[package]] -name = "hashbrown" -version = "0.16.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "heck" -version = "0.5.0" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "husky-rs" -version = "0.1.5" +name = "base64ct" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8595e3e777338ccc8360c4eb89924f8d7e55a5ff831d057e1c65892c220da28f" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "indexmap" -version = "2.11.4" +name = "bigdecimal" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" dependencies = [ - "equivalent", - "hashbrown", + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] -name = "indicatif" -version = "0.18.0" +name = "bit-set" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "console", - "portable-atomic", - "unicode-width", - "unit-prefix", - "web-time", + "bit-vec", ] [[package]] -name = "indoc" -version = "2.0.6" +name = "bit-vec" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "itertools" -version = "0.13.0" +name = "bitcode" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "0a6ed1b54d8dc333e7be604d00fa9262f4635485ffea923647b6521a5fff045d" dependencies = [ - "either", + "arrayvec", + "bitcode_derive", + "bytemuck", + "glam", + "serde", ] [[package]] -name = "itertools" -version = "0.14.0" +name = "bitcode_derive" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "238b90427dfad9da4a9abd60f3ec1cdee6b80454bde49ed37f1781dd8e9dc7f9" dependencies = [ - "either", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "js-sys" -version = "0.3.81" +name = "bitflags" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ - "once_cell", - "wasm-bindgen", + "serde_core", ] [[package]] -name = "katex-doc" -version = "0.1.0" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5b80bdbfb9176d293875db5dbf05eeeb1d4e423891c5c0520da7bd467440b9" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "blake3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] [[package]] -name = "libc" -version = "0.2.176" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] -name = "linux-raw-sys" -version = "0.11.0" +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] [[package]] -name = "log" -version = "0.4.28" +name = "brotli" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 4.0.3", +] [[package]] -name = "mapproj" -version = "0.3.0" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c713291f52f6d5d1054476236228037131db3d05cc64ce5f58f020b7b99223" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 5.0.0", +] [[package]] -name = "matrixmultiply" -version = "0.3.10" +name = "brotli-decompressor" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" dependencies = [ - "autocfg", - "rawpointer", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] -name = "memchr" -version = "2.7.6" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] [[package]] -name = "memoffset" -version = "0.9.1" +name = "bstr" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ - "autocfg", + "memchr", + "regex-automata", + "serde", ] [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "bumpalo" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ - "adler2", - "simd-adler32", + "bytemuck_derive", ] [[package]] -name = "ndarray" -version = "0.16.1" +name = "bytemuck_derive" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rawpointer", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "num" -version = "0.4.3" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "serde", ] [[package]] -name = "num-bigint" -version = "0.4.6" +name = "bzip2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "num-integer", - "num-traits", + "libbz2-rs-sys", ] [[package]] -name = "num-complex" -version = "0.4.6" +name = "camino" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ - "num-traits", + "rustversion", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cdshealpix" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c857a9ddf0d44d0a5724ebcd53e299eb260bf105b87162faad07d9b3517af67" +dependencies = [ + "base64 0.21.7", + "byteorder", + "colorous", + "flate2", + "itertools 0.13.0", + "katex-doc", + "log", + "mapproj", + "num", + "num-traits", + "png", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colorous" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e18bf7a165bf7028fde98609a0f1e8f7498d762a212598e6c891f6893556ec" + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "liblzma", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "config" +version = "0.15.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde-untagged", + "serde_core", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.1", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation 0.9.4", + "core-graphics", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.11.0", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "datafusion" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d12ee9fdc6cdb5898c7691bb994f0ba606c4acc93a2258d78bb9f26ff8158bb3" +dependencies = [ + "arrow", + "arrow-schema 57.3.0", + "async-trait", + "bytes", + "bzip2", + "chrono", + "datafusion-catalog", + "datafusion-catalog-listing", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-datasource-arrow", + "datafusion-datasource-csv", + "datafusion-datasource-json", + "datafusion-datasource-parquet", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-nested", + "datafusion-functions-table", + "datafusion-functions-window", + "datafusion-optimizer", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-session", + "datafusion-sql", + "flate2", + "futures", + "itertools 0.14.0", + "liblzma", + "log", + "object_store", + "parking_lot", + "parquet 57.3.0", + "rand 0.9.2", + "regex", + "sqlparser 0.59.0", + "tempfile", + "tokio", + "url", + "uuid", + "zstd", +] + +[[package]] +name = "datafusion-catalog" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462dc9ef45e5d688aeaae49a7e310587e81b6016b9d03bace5626ad0043e5a9e" +dependencies = [ + "arrow", + "async-trait", + "dashmap", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot", + "tokio", +] + +[[package]] +name = "datafusion-catalog-listing" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b96dbf1d728fc321817b744eb5080cdd75312faa6980b338817f68f3caa4208" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "futures", + "itertools 0.14.0", + "log", + "object_store", +] + +[[package]] +name = "datafusion-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3237a6ff0d2149af4631290074289cae548c9863c885d821315d54c6673a074a" +dependencies = [ + "ahash", + "arrow", + "arrow-ipc 57.3.0", + "chrono", + "half", + "hashbrown 0.16.1", + "indexmap", + "libc", + "log", + "object_store", + "parquet 57.3.0", + "paste", + "recursive", + "sqlparser 0.59.0", + "tokio", + "web-time", +] + +[[package]] +name = "datafusion-common-runtime" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b5e34026af55a1bfccb1ef0a763cf1f64e77c696ffcf5a128a278c31236528" +dependencies = [ + "futures", + "log", + "tokio", +] + +[[package]] +name = "datafusion-datasource" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b2a6be734cc3785e18bbf2a7f2b22537f6b9fb960d79617775a51568c281842" +dependencies = [ + "arrow", + "async-compression", + "async-trait", + "bytes", + "bzip2", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "flate2", + "futures", + "glob", + "itertools 0.14.0", + "liblzma", + "log", + "object_store", + "rand 0.9.2", + "tokio", + "tokio-util", + "url", + "zstd", +] + +[[package]] +name = "datafusion-datasource-arrow" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1739b9b07c9236389e09c74f770e88aff7055250774e9def7d3f4f56b3dcc7be" +dependencies = [ + "arrow", + "arrow-ipc 57.3.0", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "itertools 0.14.0", + "object_store", + "tokio", +] + +[[package]] +name = "datafusion-datasource-csv" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c73bc54b518bbba7c7650299d07d58730293cfba4356f6f428cc94c20b7600" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "regex", + "tokio", +] + +[[package]] +name = "datafusion-datasource-json" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37812c8494c698c4d889374ecfabbff780f1f26d9ec095dd1bddfc2a8ca12559" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "tokio", +] + +[[package]] +name = "datafusion-datasource-parquet" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210937ecd9f0e824c397e73f4b5385c97cd1aff43ab2b5836fcfd2d321523fb" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-pruning", + "datafusion-session", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot", + "parquet 57.3.0", + "tokio", +] + +[[package]] +name = "datafusion-doc" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c825f969126bc2ef6a6a02d94b3c07abff871acf4d6dd759ce1255edb7923ce" + +[[package]] +name = "datafusion-execution" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa03ef05a2c2f90dd6c743e3e111078e322f4b395d20d4b4d431a245d79521ae" +dependencies = [ + "arrow", + "async-trait", + "chrono", + "dashmap", + "datafusion-common", + "datafusion-expr", + "futures", + "log", + "object_store", + "parking_lot", + "rand 0.9.2", + "tempfile", + "url", +] + +[[package]] +name = "datafusion-expr" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef33934c1f98ee695cc51192cc5f9ed3a8febee84fdbcd9131bf9d3a9a78276f" +dependencies = [ + "arrow", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-doc", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr-common", + "indexmap", + "itertools 0.14.0", + "paste", + "recursive", + "serde_json", + "sqlparser 0.59.0", +] + +[[package]] +name = "datafusion-expr-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000c98206e3dd47d2939a94b6c67af4bfa6732dd668ac4fafdbde408fd9134ea" +dependencies = [ + "arrow", + "datafusion-common", + "indexmap", + "itertools 0.14.0", + "paste", +] + +[[package]] +name = "datafusion-functions" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379b01418ab95ca947014066248c22139fe9af9289354de10b445bd000d5d276" +dependencies = [ + "arrow", + "arrow-buffer 57.3.0", + "base64 0.22.1", + "blake2", + "blake3", + "chrono", + "chrono-tz", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-macros", + "hex", + "itertools 0.14.0", + "log", + "md-5", + "num-traits", + "rand 0.9.2", + "regex", + "sha2", + "unicode-segmentation", + "uuid", +] + +[[package]] +name = "datafusion-functions-aggregate" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd00d5454ba4c3f8ebbd04bd6a6a9dc7ced7c56d883f70f2076c188be8459e4c" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "half", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-aggregate-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec06b380729a87210a4e11f555ec2d729a328142253f8d557b87593622ecc9f" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-functions-nested" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904f48d45e0f1eb7d0eb5c0f80f2b5c6046a85454364a6b16a2e0b46f62e7dff" +dependencies = [ + "arrow", + "arrow-ord", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr-common", + "itertools 0.14.0", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-table" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a0d20e2b887e11bee24f7734d780a2588b925796ac741c3118dd06d5aa77f0" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot", + "paste", +] + +[[package]] +name = "datafusion-functions-window" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3414b0a07e39b6979fe3a69c7aa79a9f1369f1d5c8e52146e66058be1b285ee" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-doc", + "datafusion-expr", + "datafusion-functions-window-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-window-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf2feae63cd4754e31add64ce75cae07d015bce4bb41cd09872f93add32523a" +dependencies = [ + "datafusion-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-macros" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fe888aeb6a095c4bcbe8ac1874c4b9a4c7ffa2ba849db7922683ba20875aaf" +dependencies = [ + "datafusion-doc", + "quote", + "syn", +] + +[[package]] +name = "datafusion-optimizer" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6527c063ae305c11be397a86d8193936f4b84d137fe40bd706dfc178cf733c" +dependencies = [ + "arrow", + "chrono", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "indexmap", + "itertools 0.14.0", + "log", + "recursive", + "regex", + "regex-syntax", +] + +[[package]] +name = "datafusion-physical-expr" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb028323dd4efd049dd8a78d78fe81b2b969447b39c51424167f973ac5811d9" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-physical-expr-common", + "half", + "hashbrown 0.16.1", + "indexmap", + "itertools 0.14.0", + "parking_lot", + "paste", + "petgraph", + "recursive", + "tokio", +] + +[[package]] +name = "datafusion-physical-expr-adapter" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78fe0826aef7eab6b4b61533d811234a7a9e5e458331ebbf94152a51fc8ab433" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-expr", + "datafusion-functions", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "itertools 0.14.0", +] + +[[package]] +name = "datafusion-physical-expr-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfccd388620734c661bd8b7ca93c44cdd59fecc9b550eea416a78ffcbb29475f" +dependencies = [ + "ahash", + "arrow", + "chrono", + "datafusion-common", + "datafusion-expr-common", + "hashbrown 0.16.1", + "indexmap", + "itertools 0.14.0", + "parking_lot", +] + +[[package]] +name = "datafusion-physical-optimizer" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5fa10e73259a03b705d5fddc136516814ab5f441b939525618a4070f5a059" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-pruning", + "itertools 0.14.0", + "recursive", +] + +[[package]] +name = "datafusion-physical-plan" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1098760fb29127c24cc9ade3277051dc73c9ed0ac0131bd7bcd742e0ad7470" +dependencies = [ + "ahash", + "arrow", + "arrow-ord", + "arrow-schema 57.3.0", + "async-trait", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "futures", + "half", + "hashbrown 0.16.1", + "indexmap", + "itertools 0.14.0", + "log", + "parking_lot", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "datafusion-pruning" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d0fef4201777b52951edec086c21a5b246f3c82621569ddb4a26f488bc38a9" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-datasource", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "itertools 0.14.0", + "log", +] + +[[package]] +name = "datafusion-session" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71f1e39e8f2acbf1c63b0e93756c2e970a64729dab70ac789587d6237c4fde0" +dependencies = [ + "async-trait", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot", +] + +[[package]] +name = "datafusion-sql" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44693cfcaeb7a9f12d71d1c576c3a6dc025a12cef209375fa2d16fb3b5670ee" +dependencies = [ + "arrow", + "bigdecimal", + "chrono", + "datafusion-common", + "datafusion-expr", + "indexmap", + "log", + "recursive", + "regex", + "sqlparser 0.59.0", +] + +[[package]] +name = "debug_unsafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.1", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dns-lookup" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5597a4b7fe5275fc9dcf88ce26326bc8e4cb87d0130f33752d4c5f717793cf" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.60.2", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.1", +] + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fink-fat" +version = "1.0.0" +dependencies = [ + "assert_cmd", + "camino", + "chrono", + "clap", + "datafusion", + "fink-fat-engine", + "husky-rs", + "indicatif", + "tempfile", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "fink-fat-engine" +version = "1.0.0" +dependencies = [ + "ahash", + "approx", + "arrow-array 57.3.0", + "bitcode", + "camino", + "cdshealpix", + "chrono", + "config", + "criterion", + "datafusion", + "flate2", + "hdfs-native-object-store", + "lz4_flex 0.11.5", + "ndarray", + "object_store", + "once_cell", + "ort", + "outfit", + "proptest", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "serde_yaml", + "smallvec", + "tempfile", + "thiserror 2.0.17", + "thread_local", + "tokio", + "tracing", + "url", + "zstd", +] + +[[package]] +name = "fink-fat-eval" +version = "1.0.0" +dependencies = [ + "ahash", + "anyhow", + "approx", + "camino", + "chrono", + "clap", + "fink-fat-engine", + "indicatif", + "ndarray", + "plotters", + "polars", + "prop-test", + "serde", + "serde_yaml", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" +dependencies = [ + "bitflags 2.11.0", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-kit" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" +dependencies = [ + "bitflags 2.11.0", + "byteorder", + "core-foundation 0.9.4", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "g2gen" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a7e0eb46f83a20260b850117d204366674e85d3a908d90865c78df9a6b1dfc" +dependencies = [ + "g2poly", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "g2p" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539e2644c030d3bf4cd208cb842d2ce2f80e82e6e8472390bcef83ceba0d80ad" +dependencies = [ + "g2gen", + "g2poly", +] + +[[package]] +name = "g2poly" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312d2295c7302019c395cfb90dacd00a82a2eabd700429bba9c7a3f38dbbe11b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glam" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34627c5158214743a374170fed714833fdf4e4b0cbcc1ea98417866a4c5d4441" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "rayon", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", + "rayon", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "hdfs-native" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08603b51f970930b0025b92d3f6c0ba39a0a6d0dfb4b3f527af58768adc2f3b2" +dependencies = [ + "aes", + "base64 0.22.1", + "bitflags 2.11.0", + "bumpalo", + "bytes", + "cbc", + "chrono", + "cipher", + "crc", + "ctr", + "des", + "dns-lookup", + "futures", + "g2p", + "hex", + "hmac", + "libc", + "libloading", + "log", + "md-5", + "num-traits", + "once_cell", + "prost", + "prost-types", + "rand 0.9.2", + "regex", + "roxmltree", + "socket2", + "thiserror 2.0.17", + "tokio", + "url", + "uuid", + "whoami", +] + +[[package]] +name = "hdfs-native-object-store" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5495a763df493ea3883271e727914e83a0ce188a32e230ca820e3cb5e188d2" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "hdfs-native", + "object_store", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hifitime" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd986e90b53308b583668349332fcb508cdecb183d18e0170abe3c01e6aeeb60" +dependencies = [ + "js-sys", + "lexical-core", + "num-traits", + "openssl", + "serde", + "serde_derive", + "snafu", + "tabled", + "ureq", + "wasm-bindgen", + "web-sys", + "web-time", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "husky-rs" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8595e3e777338ccc8360c4eb89924f8d7e55a5ff831d057e1c65892c220da28f" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.1", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "katex-doc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e5b80bdbfb9176d293875db5dbf05eeeb1d4e423891c5c0520da7bd467440b9" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "liblzma" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6033b77c21d1f56deeae8014eb9fbe7bdf1765185a6c508b5ca82eeaed7f899" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.11.0", + "libc", + "redox_syscall 0.7.1", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +dependencies = [ + "twox-hash 2.1.2", +] + +[[package]] +name = "lz4_flex" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e" +dependencies = [ + "twox-hash 2.1.2", +] + +[[package]] +name = "lzma-rust2" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" + +[[package]] +name = "mapproj" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c713291f52f6d5d1054476236228037131db3d05cc64ce5f58f020b7b99223" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.1", +] + +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "now" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0" +dependencies = [ + "chrono", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfbfff40aeccab00ec8a910b57ca8ecf4319b335c542f2edcd19dd25a1e2a00" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http", + "http-body-util", + "humantime", + "hyper", + "itertools 0.14.0", + "parking_lot", + "percent-encoding", + "quick-xml 0.38.4", + "rand 0.9.2", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "ort" +version = "2.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c" +dependencies = [ + "ndarray", + "ort-sys", + "smallvec", + "tracing", + "ureq", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b" +dependencies = [ + "hmac-sha256", + "lzma-rust2", + "ureq", +] + +[[package]] +name = "outfit" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c17e18f9a380770298fe76af1b637e49bab78381c408e0b515ea556117a602" +dependencies = [ + "aberth", + "ahash", + "arrow-array 54.3.1", + "camino", + "comfy-table", + "directories", + "hifitime", + "itertools 0.14.0", + "nalgebra", + "nom", + "once_cell", + "ordered-float 5.1.0", + "parquet 54.3.1", + "quick-xml 0.37.5", + "rand 0.9.2", + "rand_distr 0.5.1", + "rayon", + "reqwest", + "roots", + "serde", + "smallvec", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "ureq", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "papergrid" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parquet" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb15796ac6f56b429fd99e33ba133783ad75b27c36b4b5ce06f1f82cc97754e" +dependencies = [ + "ahash", + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-cast 54.3.1", + "arrow-data 54.3.1", + "arrow-ipc 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.3.1", + "base64 0.22.1", + "bytes", + "chrono", + "half", + "hashbrown 0.15.5", + "num", + "num-bigint", + "paste", + "seq-macro", + "snap", + "thrift", + "twox-hash 1.6.3", +] + +[[package]] +name = "parquet" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee96b29972a257b855ff2341b37e61af5f12d6af1158b6dcdb5b31ea07bb3cb" +dependencies = [ + "ahash", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-cast 57.3.0", + "arrow-data 57.3.0", + "arrow-ipc 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "base64 0.22.1", + "brotli 8.0.2", + "bytes", + "chrono", + "flate2", + "futures", + "half", + "hashbrown 0.16.1", + "lz4_flex 0.12.0", + "num-bigint", + "num-integer", + "num-traits", + "object_store", + "paste", + "seq-macro", + "simdutf8", + "snap", + "thrift", + "tokio", + "twox-hash 2.1.2", + "zstd", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "planus" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f" +dependencies = [ + "array-init-cursor", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-bitmap" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polars" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72571dde488ecccbe799798bf99ab7308ebdb7cf5d95bcc498dbd5a132f0da4d" +dependencies = [ + "getrandom 0.2.17", + "polars-arrow", + "polars-core", + "polars-error", + "polars-io", + "polars-lazy", + "polars-ops", + "polars-parquet", + "polars-sql", + "polars-time", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-arrow" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6611c758d52e799761cc25900666b71552e6c929d88052811bc9daad4b3321a8" +dependencies = [ + "ahash", + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "dyn-clone", + "either", + "ethnum", + "getrandom 0.2.17", + "hashbrown 0.15.5", + "itoa", + "lz4", + "num-traits", + "parking_lot", + "polars-arrow-format", + "polars-error", + "polars-schema", + "polars-utils", + "simdutf8", + "streaming-iterator", + "strength_reduce", + "strum_macros", + "version_check", + "zstd", +] + +[[package]] +name = "polars-arrow-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b0ef2474af9396b19025b189d96e992311e6a47f90c53cd998b36c4c64b84c" +dependencies = [ + "planus", + "serde", +] + +[[package]] +name = "polars-compute" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f2547dbb27599a8ffe68e56159f5996ba03d1dad0382ccb62c109ceacdeb6" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "either", + "fast-float2", + "itoa", + "num-traits", + "polars-arrow", + "polars-error", + "polars-utils", + "ryu", + "strength_reduce", + "version_check", +] + +[[package]] +name = "polars-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796d06eae7e6e74ed28ea54a8fccc584ebac84e6cf0e1e9ba41ffc807b169a01" +dependencies = [ + "ahash", + "bitflags 2.11.0", + "bytemuck", + "chrono", + "chrono-tz", + "comfy-table", + "either", + "hashbrown 0.14.5", + "hashbrown 0.15.5", + "indexmap", + "itoa", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-row", + "polars-schema", + "polars-utils", + "rand 0.8.5", + "rand_distr 0.4.3", + "rayon", + "regex", + "strum_macros", + "thiserror 2.0.17", + "version_check", + "xxhash-rust", +] + +[[package]] +name = "polars-error" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d6529cae0d1db5ed690e47de41fac9b35ae0c26d476830c2079f130887b847" +dependencies = [ + "polars-arrow-format", + "regex", + "simdutf8", + "thiserror 2.0.17", +] + +[[package]] +name = "polars-expr" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e639991a8ad4fb12880ab44bcc3cf44a5703df003142334d9caf86d77d77e7" +dependencies = [ + "ahash", + "bitflags 2.11.0", + "hashbrown 0.15.5", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-time", + "polars-utils", + "rand 0.8.5", + "rayon", +] + +[[package]] +name = "polars-io" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719a77e94480f6be090512da196e378cbcbeb3584c6fe1134c600aee906e38ab" +dependencies = [ + "ahash", + "async-trait", + "atoi_simd", + "bytes", + "chrono", + "fast-float2", + "futures", + "glob", + "hashbrown 0.15.5", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "once_cell", + "percent-encoding", + "polars-arrow", + "polars-core", + "polars-error", + "polars-parquet", + "polars-schema", + "polars-time", + "polars-utils", + "rayon", + "regex", + "ryu", + "simdutf8", + "tokio", + "tokio-util", +] + +[[package]] +name = "polars-lazy" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a731a672dfc8ac38c1f73c9a4b2ae38d2fc8ac363bfb64c5f3a3e072ffc5ad" +dependencies = [ + "ahash", + "bitflags 2.11.0", + "chrono", + "memchr", + "once_cell", + "polars-arrow", + "polars-core", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-pipe", + "polars-plan", + "polars-stream", + "polars-time", + "polars-utils", + "rayon", + "version_check", +] + +[[package]] +name = "polars-mem-engine" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33442189bcbf2e2559aa7914db3835429030a13f4f18e43af5fba9d1b018cf12" +dependencies = [ + "memmap2", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", +] + +[[package]] +name = "polars-ops" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb83218b0c216104f0076cd1a005128be078f958125f3d59b094ee73d78c18e" +dependencies = [ + "ahash", + "argminmax", + "base64 0.22.1", + "bytemuck", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.5", + "hex", + "indexmap", + "memchr", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-schema", + "polars-utils", + "rayon", + "regex", + "regex-syntax", + "strum_macros", + "unicode-normalization", + "unicode-reverse", + "version_check", +] + +[[package]] +name = "polars-parquet" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c60ee85535590a38db6c703a21be4cb25342e40f573f070d1e16f9d84a53ac7" +dependencies = [ + "ahash", + "async-stream", + "base64 0.22.1", + "brotli 7.0.0", + "bytemuck", + "ethnum", + "flate2", + "futures", + "hashbrown 0.15.5", + "lz4", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-parquet-format", + "polars-utils", + "simdutf8", + "snap", + "streaming-decompression", + "zstd", +] + +[[package]] +name = "polars-parquet-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c025243dcfe8dbc57e94d9f82eb3bef10b565ab180d5b99bed87fd8aea319ce1" +dependencies = [ + "async-trait", + "futures", +] + +[[package]] +name = "polars-pipe" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d238fb76698f56e51ddfa89b135e4eda56a4767c6e8859eed0ab78386fcd52" +dependencies = [ + "crossbeam-channel", + "crossbeam-queue", + "enum_dispatch", + "futures", + "hashbrown 0.15.5", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-utils", + "rayon", + "uuid", + "version_check", +] + +[[package]] +name = "polars-plan" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f03533a93aa66127fcb909a87153a3c7cfee6f0ae59f497e73d7736208da54c" +dependencies = [ + "ahash", + "bitflags 2.11.0", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.5", + "memmap2", + "num-traits", + "once_cell", + "percent-encoding", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-parquet", + "polars-time", + "polars-utils", + "rayon", + "recursive", + "regex", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf47f7409f8e75328d7d034be390842924eb276716d0458607be0bddb8cc839" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-schema" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416621ae82b84466cf4ff36838a9b0aeb4a67e76bd3065edc8c9cb7da19b1bc7" +dependencies = [ + "indexmap", + "polars-error", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-sql" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaab553b90aa4d6743bb538978e1982368acb58a94408d7dd3299cad49c7083" +dependencies = [ + "hex", + "polars-core", + "polars-error", + "polars-lazy", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rand 0.8.5", + "regex", + "serde", + "sqlparser 0.53.0", +] + +[[package]] +name = "polars-stream" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498997b656c779610c1496b3d96a59fe569ef22a5b81ccfe5325cb3df8dff2fd" +dependencies = [ + "atomic-waker", + "crossbeam-deque", + "crossbeam-utils", + "futures", + "memmap2", + "parking_lot", + "pin-project-lite", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-utils", + "rand 0.8.5", + "rayon", + "recursive", + "slotmap", + "tokio", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d192efbdab516d28b3fab1709a969e3385bd5cda050b7c9aa9e2502a01fda879" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "now", + "num-traits", + "once_cell", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-ops", + "polars-utils", + "rayon", + "regex", + "strum_macros", +] + +[[package]] +name = "polars-utils" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f6c8166a4a7fbc15b87c81645ed9e1f0651ff2e8c96cafc40ac5bf43441a10" +dependencies = [ + "ahash", + "bytemuck", + "bytes", + "compact_str", + "hashbrown 0.15.5", + "indexmap", + "libc", + "memmap2", + "num-traits", + "once_cell", + "polars-error", + "rand 0.8.5", + "raw-cpuid", + "rayon", + "stacker", + "sysinfo", + "version_check", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prop-test" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b4e7a33f0129d12482a31f19249f22cad61916dd6798f9281ec6bae92fff771" +dependencies = [ + "proptest", +] + +[[package]] +name = "proptest" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.11.0", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags 2.11.0", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "roots" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082f11ffa03bbef6c2c6ea6bea1acafaade2fd9050ae0234ab44a2153742b058" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.1", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "backtrace", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + +[[package]] +name = "sqlparser" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8" +dependencies = [ + "log", +] + +[[package]] +name = "sqlparser" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4591acadbcf52f0af60eafbb2c003232b2b4cd8de5f0e9437cb8b1b59046cc0f" +dependencies = [ + "log", + "recursive", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-decompression" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3" +dependencies = [ + "fallible-streaming-iterator", +] + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "windows", +] + +[[package]] +name = "tabled" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" +dependencies = [ + "papergrid", + "tabled_derive", + "testing_table", +] + +[[package]] +name = "tabled_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846" +dependencies = [ + "heck", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.1", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "thrift" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" +dependencies = [ + "byteorder", + "integer-encoding", + "ordered-float 2.10.1", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.1", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.17", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-reverse" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6f4888ebc23094adfb574fdca9fdc891826287a6397d2cd28802ffd6f20c76" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +dependencies = [ + "base64 0.22.1", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls", + "rustls-pki-types", + "socks", + "ureq-proto", + "utf-8", + "webpki-root-certs", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "getrandom 0.4.1", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "num-integer" -version = "0.1.46" +name = "wasm-bindgen-macro" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ - "num-traits", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "num-iter" -version = "0.1.45" +name = "wasm-bindgen-macro-support" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] -name = "num-rational" -version = "0.4.2" +name = "wasm-bindgen-shared" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ - "num-bigint", - "num-integer", - "num-traits", + "unicode-ident", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "autocfg", + "leb128fmt", + "wasmparser", ] [[package]] -name = "numpy" -version = "0.26.0" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2dba356160b54f5371b550575b78130a54718b4c6e46b3f33a6da74a27e78b" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "libc", - "ndarray", - "num-complex", - "num-integer", - "num-traits", - "pyo3", - "pyo3-build-config", - "rustc-hash", + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "wasm-streams" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] -name = "oorandom" -version = "11.1.5" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] [[package]] -name = "plotters" -version = "0.3.7" +name = "web-sys" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", + "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] -name = "plotters-backend" -version = "0.3.7" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] -name = "plotters-svg" -version = "0.3.7" +name = "webpki-root-certs" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ - "plotters-backend", + "rustls-pki-types", ] [[package]] -name = "png" -version = "0.17.16" +name = "webpki-roots" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "rustls-pki-types", ] [[package]] -name = "portable-atomic" -version = "1.11.1" +name = "weezl" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] -name = "portable-atomic-util" -version = "0.2.4" +name = "whoami" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "portable-atomic", + "libredox", + "wasite", + "web-sys", ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "wide" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" dependencies = [ - "zerocopy", + "bytemuck", + "safe_arch", ] [[package]] -name = "proc-macro2" -version = "1.0.101" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "unicode-ident", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "proptest" -version = "1.8.0" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.9.4", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", + "windows-sys 0.61.1", ] [[package]] -name = "pyo3" -version = "0.26.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "indoc", - "libc", - "memoffset", - "once_cell", - "portable-atomic", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", + "windows-core 0.57.0", + "windows-targets 0.52.6", ] [[package]] -name = "pyo3-build-config" -version = "0.26.0" +name = "windows-core" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "target-lexicon", + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", ] [[package]] -name = "pyo3-ffi" -version = "0.26.0" +name = "windows-core" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ - "libc", - "pyo3-build-config", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.0", + "windows-strings", ] [[package]] -name = "pyo3-macros" -version = "0.26.0" +name = "windows-implement" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", - "pyo3-macros-backend", "quote", "syn", ] [[package]] -name = "pyo3-macros-backend" -version = "0.26.0" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ - "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "windows-interface" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "quote" -version = "1.0.41" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", + "quote", + "syn", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "windows-link" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] -name = "rand" -version = "0.9.2" +name = "windows-result" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "rand_chacha", - "rand_core", + "windows-targets 0.52.6", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "windows-result" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "ppv-lite86", - "rand_core", + "windows-link", ] [[package]] -name = "rand_core" -version = "0.9.3" +name = "windows-strings" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "getrandom", + "windows-link", ] [[package]] -name = "rand_xorshift" -version = "0.4.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "rand_core", + "windows-targets 0.52.6", ] [[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.11.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "either", - "rayon-core", + "windows-targets 0.52.6", ] [[package]] -name = "rayon-core" -version = "1.13.0" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "windows-targets 0.53.4", ] [[package]] -name = "regex" -version = "1.11.3" +name = "windows-sys" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "windows-link", ] [[package]] -name = "regex-automata" -version = "0.4.11" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "regex-syntax" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "1.1.2" +name = "windows-targets" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "rusty-fork" -version = "0.3.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "ryu" -version = "1.0.20" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] -name = "same-file" -version = "1.0.6" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] -name = "serde_core" -version = "1.0.228" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "serde_derive" -version = "1.0.228" +name = "windows_i686_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] -name = "serde_json" -version = "1.0.145" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "serde_spanned" -version = "1.0.3" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" -dependencies = [ - "serde_core", -] +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] -name = "simd-adler32" -version = "0.3.7" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "syn" -version = "2.0.106" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] -name = "target-lexicon" -version = "0.13.3" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "tempfile" -version = "3.23.0" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", -] +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] -name = "thiserror" -version = "1.0.69" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "thiserror" -version = "2.0.17" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "thiserror-impl" -version = "2.0.17" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "tinytemplate" -version = "1.2.1" +name = "winnow" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ - "serde", - "serde_json", + "memchr", ] [[package]] -name = "toml" -version = "0.9.8" +name = "wio" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", + "winapi", ] [[package]] -name = "toml_datetime" -version = "0.7.3" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" -dependencies = [ - "serde_core", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] -name = "toml_parser" -version = "1.0.4" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "winnow", + "wit-bindgen-rust-macro", ] [[package]] -name = "toml_writer" -version = "1.0.4" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] [[package]] -name = "unarray" -version = "0.1.4" +name = "wit-bindgen-rust" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] [[package]] -name = "unicode-ident" -version = "1.0.19" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] [[package]] -name = "unicode-width" -version = "0.2.1" +name = "wit-component" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] [[package]] -name = "unindent" -version = "0.2.4" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] -name = "unit-prefix" -version = "0.5.1" +name = "writeable" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] -name = "version_check" -version = "0.9.5" +name = "xxhash-rust" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] -name = "wait-timeout" -version = "0.2.1" +name = "yaml-rust2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" dependencies = [ - "libc", + "arraydeque", + "encoding_rs", + "hashlink", ] [[package]] -name = "walkdir" -version = "2.5.0" +name = "yeslogic-fontconfig-sys" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" dependencies = [ - "same-file", - "winapi-util", + "dlib", + "once_cell", + "pkg-config", ] [[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" +name = "yoke" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "wasip2", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +name = "yoke-derive" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ - "wit-bindgen", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "wasm-bindgen" -version = "0.2.104" +name = "zerocopy" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", + "zerocopy-derive", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" +name = "zerocopy-derive" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ - "bumpalo", - "log", "proc-macro2", "quote", "syn", - "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.104" +name = "zerofrom" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "zerofrom-derive", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.104" +name = "zerofrom-derive" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "synstructure", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.104" +name = "zeroize" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" -dependencies = [ - "unicode-ident", -] +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] -name = "web-sys" -version = "0.3.81" +name = "zerotrie" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ - "js-sys", - "wasm-bindgen", + "displaydoc", + "yoke", + "zerofrom", ] [[package]] -name = "web-time" -version = "1.1.0" +name = "zerovec" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ - "js-sys", - "wasm-bindgen", + "yoke", + "zerofrom", + "zerovec-derive", ] [[package]] -name = "winapi-util" -version = "0.1.11" +name = "zerovec-derive" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ - "windows-sys", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "windows-link" -version = "0.2.0" +name = "zlib-rs" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" [[package]] -name = "windows-sys" -version = "0.61.1" +name = "zstd" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "windows-link", + "zstd-safe", ] [[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "zerocopy" -version = "0.8.27" +name = "zstd-safe" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ - "zerocopy-derive", + "zstd-sys", ] [[package]] -name = "zerocopy-derive" -version = "0.8.27" +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 9aa0e47d..9847c8a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,45 +1,83 @@ +[workspace.package] +edition = "2024" +version = "1.0.0" +authors = ["Roman Le Montagner "] +categories = ["science"] +keywords = ["astronomy", "asteroids", "tracking", "alerts"] +repository = "https://github.com/FusRoman/fink-fat" +homepage = "https://github.com/FusRoman/fink-fat" +documentation = "https://docs.rs/fink-fat" +license-file = "LICENSE" + [package] name = "fink-fat" -version = "1.0.0" -edition = "2021" +readme = "README.md" +description = "FINK-FAT: Fink Asteroid Tracker, a software to track asteroids in an alert stream such as Fink's.It provides a graph-based representation of the linked alerts and implements algorithms to predict potential asteroid detections and track them over time." +edition.workspace = true +version.workspace = true +authors.workspace = true +categories.workspace = true +keywords.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +license-file.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "fink_fat" -crate-type = ["rlib", "cdylib"] +[workspace] +members = ["crates/fink-fat-engine", "crates/fink-fat-eval"] +resolver = "2" -[dependencies] -ahash = "0.8.12" -camino = "1.2.1" -cdshealpix = "0.7.3" -indicatif = "0.18.0" -itertools = "0.14.0" -numpy = "0.26.0" -pyo3 = { version = "0.26", features = ["extension-module"] } +[workspace.dependencies] +rand = "0.9.2" serde = { version = "1.0.228", features = ["derive"] } +ahash = { version = "0.8.12", features = ["serde"] } thiserror = "2.0.17" -toml = "0.9.8" - -[dev-dependencies] -criterion = "0.7.0" -husky-rs = "0.1.5" -proptest = "1.8.0" -rand = "0.9.2" +camino = "1.2.1" tempfile = "3.23.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } +tracing-appender = "0.2" +clap = { version = "4.5.60", features = ["cargo", "derive"] } +chrono = "0.4" +fink-fat-engine = { path = "crates/fink-fat-engine", version = "1.0.0" } +indicatif = "0.18.0" +serde_yaml = "0.9.34" +ndarray = "0.17.2" -[features] -python-extension = ["pyo3/extension-module"] - -# make sure default features don't include it -default = [] +[dependencies] +indicatif = { workspace = true } +camino = { workspace = true } +chrono = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +tracing-appender = { workspace = true } +fink-fat-engine = { workspace = true } +clap = { workspace = true } +[dev-dependencies] +husky-rs = "0.1.5" +assert_cmd = "2.0.16" +tempfile = { workspace = true } +datafusion = "52.1.0" [profile.release] -debug = true # conserve les symboles -strip = "none" lto = "thin" codegen-units = 1 -[[bench]] -name = "track_registry_update" -harness = false +[profile.bench] +codegen-units = 1 +debug = true + +[profile.evaluation] +inherits = "release" +lto = false +codegen-units = 128 +overflow-checks = true +debug-assertions = true +incremental = true + +[profile.evaluation.package."*"] +codegen-units = 32 +overflow-checks = true +debug-assertions = true +incremental = false diff --git a/Changelog.md b/Changelog.md index 9ad9496e..ef519fcc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,31 +1,43 @@ # Changelog -All notable changes to this project are tracked here. Dates use the ISO-8601 format (YYYY-MM-DD). +All notable changes to this project will be documented in this file. -## [1.0.0] - 2024-??-?? +This project follows the principles of [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and uses [Semantic Versioning](https://semver.org/). -This release marks the first stable version of the fully rewritten `fink-fat` stack. The project now combines a Rust core for heavy computations with Python bindings for ergonomics and interoperability. The previous pure-Python implementation has been retired. +## [Unreleased] ### Added -- **Rust core crate (`src/`)** implementing alert ingestion, seeding, propagation, scoring, and conflict resolution. The codebase is now organized around dedicated modules such as `alerts`, `propagation`, `track_registry`, and `params` to clarify responsibilities and make the Rust API usable on its own. -- **Python bindings via PyO3 (`python/fink_fat`)** that expose the Rust functionality as a compiled extension module. The bindings include `.pyi` stub files for editors/type checkers and re-export the Rust classes (`Alert`, `AlertStore`, `RollingLinkState`, `DetectConflictPolicy`, etc.) directly to Python callers. -- **Alert data model refresh**: The new `Alert` struct carries all astrometry, photometry, and metadata with explicit units in docstrings. `AlertStore` now supports zero-copy construction from NumPy arrays, deterministic identifiers, rich string representations, and helpers to build link identifiers. -- **Configuration bridge with `FinkFatParams`**: A builder pattern mirrors the strongly typed Rust configuration structs, ensuring Python users can tweak binning, seeding, and propagation tolerances while benefiting from validation performed on the Rust side. -- **Propagation pipeline enhancements**: Introduction of `RollingLinkState`, improved solver/scoring modules, and a clearer conflict policy system (`DetectConflictPolicy`). These are geared toward nightly link creation with better control over heuristics and tie-breaking. -- **Expanded testing and benchmarking**: Criterion benches exercise critical update paths (`benches/track_registry_update`). Property-based tests cover edge cases in seeding and propagation (`tests/`, `proptest-regressions/`). Pytest suites validate the Python API contract (`python/tests/`), including determinism around time-bin behaviour and parameter toggles. -- **Modern project scaffolding**: Added `pyproject.toml` and `Cargo.toml` definitions tuned for building both an `rlib` and `cdylib`, ensuring the crate can serve standalone Rust consumers and the Python wheel. Release builds now use `lto = "thin"` with preserved debug symbols to aid profiling while keeping artifacts production ready. -- **Graph-based tracking architecture**: The linkage pipeline now models the alert stream as a multi-stage graph. Alerts feed into spatial-temporal buckets whose adjacency is controlled by configurable radii and time windows. Pair and triplet generators emit `SeedNode` structures enriched with kinematic features (angular velocity, acceleration proxies, residuals) to populate an intermediate graph layer. `NightSnapshot` snapshots capture the evolving edge set, while the propagation engine scores paths, prunes conflicts, and maintains consistency via `track_registry`. This modular graph decomposition makes it easier to swap heuristics—e.g., custom feature extractors or scoring strategies—without touching upstream ingestion. +- Nothing yet. + +### Changed +- Nothing yet. + +### Removed +- Nothing yet. +## [1.0.0] - 2026-03-19 + +### Added +- Rust workspace reorganisation with three crates: `fink-fat` (binary entrypoint), `fink-fat-engine` (core engine), and `fink-fat-eval` (evaluation and diagnostics). +- Core engine modules in Rust for alert ingestion, seed generation, inter-night edge construction, solver routing, persistence, and configuration loading. +- Command-line runtime components for typed CLI parsing, progress bars, structured logging, and pipeline orchestration. +- Evaluation tooling in Rust for seeding, edge, solver, and model evaluation, plus feature export and diagnostic plots. +- Documentation overhaul with module-level rustdoc, a new project README, and crate-level README files. +- Rust-focused CI, packaging metadata, and documentation generation with KaTeX support. +- Integration tests and Criterion benchmarks covering seeding, edge generation, solver behaviour, and persistence primitives. ### Changed -- **Pipeline implementation**: The entire linkage workflow has been reimplemented in Rust. Compared with the legacy Python pipeline, this delivers large gains in throughput, predictable memory usage, and easier parallelisation. Python now delegates computational hotspots to Rust while keeping the scripting ergonomics intact. -- **Public API surface**: Exported classes and functions have been renamed and regrouped around the new Rust types. Existing Python callers need to adopt `AlertStore.from_numpy(...)`, the new parameter builder, and the updated return types (`Pairs`, `Triplets`, `RollingLinkState`). -- **Build and packaging process**: Building wheels now compiles the Rust crate through `maturin`/`pyo3` conventions. Continuous integration uses the Rust toolchain and runs both `cargo` and `pytest` suites to guarantee parity across languages. -- **Documentation and developer workflow**: README, inline module docs, and type hints now reflect the Rust + Python hybrid architecture, making onboarding smoother and clarifying performance assumptions (units, error budgets, binning strategies). +- The detection-linking workflow is now implemented natively in Rust instead of Python. +- Runtime behaviour now includes first-class logging and progress reporting via `tracing` and `indicatif`. +- The project now uses validated Cargo manifests, workspace dependencies, and a documented engine configuration model instead of the previous Python packaging flow. +- The root README and crate-level documentation now describe the workspace in terms of user-facing workflows, command-line usage, and crate responsibilities. ### Removed -- **Legacy Python-only backend**: All pure-Python implementations of the alert store, seeding, and propagation logic have been removed in favour of the Rust rewrite. This includes bespoke data structures, NumPy-centric loops, and glue scripts that are now superseded by the compiled extension. -- **Ad-hoc configuration scripts**: Older configuration helpers and JSON/TOML loaders are deprecated; the builder pattern centralises configuration flow and ensures validation happens before a pipeline run. +- The legacy pure-Python `fink_fat/` package, `setup.py` packaging flow, and Python-only linkage implementation. +- Standalone CLI scripts, notebook-driven analysis workflow, and legacy configuration helpers from the primary release path. +- Python linting/test workflows and release-time shell scripts in favour of Rust-oriented CI jobs and Cargo-based packaging. + +[Unreleased]: https://github.com/FusRoman/fink-fat/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/FusRoman/fink-fat/compare/v0.17.0...v1.0.0 -## [Unreleased] -- No additional changes recorded. diff --git a/LICENSE b/LICENSE index b4abd379..d6eb151e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,517 @@ - 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. - - Copyright [2022] [Le Montagner Roman] - - 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. + +CeCILL-C FREE SOFTWARE LICENSE AGREEMENT + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to + users, + * secondly, the election of a governing law, French law, with which + it is conformant, both as regards the law of torts and + intellectual property law, and the protection that it offers to + both authors and holders of the economic rights over software. + +The authors of the CeCILL-C (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'Energie Atomique - CEA, a public scientific, technical +and industrial research establishment, having its principal place of +business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +The purpose of this Free Software license agreement is to grant users +the right to modify and re-use the software governed by this license. + +The exercising of this right is conditional upon the obligation to make +available to the community the modifications made to the source code of +the software so as to contribute to its evolution. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Integrated Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Integrated +Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Integrated Contribution: means any or all modifications, corrections, +translations, adaptations and/or new functions integrated into the +Source Code by any or all Contributors. + +Related Module: means a set of sources files including their +documentation that, without modification to the Source Code, enables +supplementary functions or services in addition to those offered by the +Software. + +Derivative Software: means any combination of the Software, modified or +not, and of a Related Module. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 hereinafter for the whole term of the +protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical + medium; + * (ii) the first time the Licensee exercises any of the rights + granted hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +hereinabove, and the Licensee hereby acknowledges that it has read and +understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or + all medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission + or storage operation as regards the Software, that it is entitled + to carry out hereunder. + + + 5.2 RIGHT OF MODIFICATION + +The right of modification includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. It includes, in particular, the +right to create a Derivative Software. + +The Licensee is authorized to make any or all modification to the +Software provided that it includes an explicit notice that it is the +author of said modification and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows effective access to the full Source +Code of the Software at a minimum during the entire period of its +distribution of the Software, it being understood that the additional +cost of acquiring the Source Code shall not exceed the cost of +transferring the data. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +When the Licensee makes an Integrated Contribution to the Software, the +terms and conditions for the distribution of the resulting Modified +Software become subject to all the provisions of this Agreement. + +The Licensee is authorized to distribute the Modified Software, in +source code or object code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the object code of the Modified +Software is redistributed, the Licensee allows effective access to the +full source code of the Modified Software at a minimum during the entire +period of its distribution of the Modified Software, it being understood +that the additional cost of acquiring the source code shall not exceed +the cost of transferring the data. + + + 5.3.3 DISTRIBUTION OF DERIVATIVE SOFTWARE + +When the Licensee creates Derivative Software, this Derivative Software +may be distributed under a license agreement other than this Agreement, +subject to compliance with the requirement to include a notice +concerning the rights over the Software as defined in Article 6.4. +In the event the creation of the Derivative Software required modification +of the Source Code, the Licensee undertakes that: + + 1. the resulting Modified Software will be governed by this Agreement, + 2. the Integrated Contributions in the resulting Modified Software + will be clearly identified and documented, + 3. the Licensee will allow effective access to the source code of the + Modified Software, at a minimum during the entire period of + distribution of the Derivative Software, such that such + modifications may be carried over in a subsequent version of the + Software; it being understood that the additional cost of + purchasing the source code of the Modified Software shall not + exceed the cost of transferring the data. + + + 5.3.4 COMPATIBILITY WITH THE CeCILL LICENSE + +When a Modified Software contains an Integrated Contribution subject to +the CeCILL license agreement, or when a Derivative Software contains a +Related Module subject to the CeCILL license agreement, the provisions +set forth in the third item of Article 6.4 are optional. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2. + + + 6.2 OVER THE INTEGRATED CONTRIBUTIONS + +The Licensee who develops an Integrated Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE RELATED MODULES + +The Licensee who develops a Related Module is the owner of the +intellectual property rights over this Related Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution under the conditions defined in Article 5.3.3. + + + 6.4 NOTICE OF RIGHTS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies + of the Software modified or not; + + 3. to ensure that use of the Software, its intellectual property + notices and the fact that it is governed by the Agreement is + indicated in a text that is easily accessible, specifically from + the interface of any Derivative Software. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights of the Holder and/or Contributors on the +Software and to take, where applicable, vis-à-vis its staff, any and all +measures required to ensure respect of said intellectual property rights +of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 and, in particular, without any warranty +as to its commercial value, its secured, safe, innovative or relevant +nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +assistance for its defense. Such technical and legal assistance shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. + + +Version 1.0 dated 2006-09-05. diff --git a/README.md b/README.md index 6d430ba8..01d0a4a3 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,208 @@ -# FAT : FINK Asteroids Tracker +# Fink-FAT -[![Sentinel](https://github.com/FusRoman/fink-fat/workflows/Sentinel/badge.svg)](https://github.com/FusRoman/Asteroids_and_Associations/actions?query=workflow%3ASentinel) -[![PEP8](https://github.com/FusRoman/fink-fat/workflows/PEP8/badge.svg)](https://github.com/FusRoman/Asteroids_and_Associations/actions?query=workflow%3APEP8) -[![codecov](https://codecov.io/gh/FusRoman/fink-fat/branch/main/graph/badge.svg)](https://app.codecov.io/gh/FusRoman/Asteroids_and_Associations) +![Rust](https://img.shields.io/badge/Rust-000000?style=flat&logo=rust&logoColor=white) +![Version](https://img.shields.io/github/v/release/FusRoman/fink-fat) +![Licence](https://img.shields.io/github/license/FusRoman/fink-fat) -This project is aim to detect asteroids with alerts from the ZTF. It use the fink borker to get the alert tag as Solar System Candidates and apply a linkage -algorithm to detect new asteroid trajectories. +Fink-FAT is a Rust workspace for asteroid detection and trajectory reconstruction in alert streams. It ingests photometric alerts from surveys such as ZTF and Vera Rubin, builds intra-night seeds, links them across nights, solves candidate trajectories, and can optionally fit preliminary orbits. -## Installation +The repository also contains evaluation tools for measuring seed, edge, and solver quality on labelled datasets, as well as utilities for exporting edge features and generating diagnostic plots. -```console -pip install fink-fat +## Highlights + +- Ingest nightly or multi-night alert batches from configurable URIs. +- Build intra-night seeds from pairs and triplets of detections. +- Construct inter-night edges with configurable scoring and optional ONNX ranking. +- Solve connected components into candidate trajectories. +- Optionally run orbit fitting on selected tracks. +- Evaluate seeding, edge, solver, and model quality on labelled data. +- Persist intermediate state to disk for incremental processing. +- Provide progress bars and structured logs for long runs. + +## Workspace layout + +| Path | Role | +| --- | --- | +| `src/` | Binary entrypoint, CLI parsing, logging, progress hooks, and runtime orchestration. | +| `crates/fink-fat-engine/` | Core engine library: alerts, seeding, edges, solver, persistence, and configuration. | +| `crates/fink-fat-eval/` | Evaluation binary: seeding, edge, solver, and model evaluation modes. | +| `docs/` | Design notes and analysis documents. | +| `katex-header.html` | KaTeX header used when generating Rust documentation. | + +## Requirements + +- A recent stable Rust toolchain. +- Access to the survey alert files you want to process. +- A validated engine configuration file. +- Optional: an ONNX edge ranking model, if you enable model-based edge filtering. + +For engine-specific runtime and configuration details, see [crates/fink-fat-engine/README.md](crates/fink-fat-engine/README.md). + +## Quick start + +Build the workspace: + +```bash +cargo build --workspace +``` + +Run the main binary help: + +```bash +cargo run -- --help +``` + +Run the automated tests: + +```bash +cargo test --workspace +``` + +Generate the documentation: + +```bash +RUSTDOCFLAGS="--html-in-header $(pwd)/katex-header.html" cargo doc --workspace --open +``` + +## Running the main pipeline + +The `fink-fat` binary expects two required arguments: + +- `--alerts` - URI of the alert file to process. +- `--config` - path to the engine configuration file. + +Optional runtime flags: + +- `--progress` enables `indicatif` progress bars. +- `--logs` enables terminal and file logging. + +Example: + +```bash +cargo run -- \ + --alerts file:///path/to/alerts.parquet \ + --config /path/to/config.yml \ + --progress \ + --logs ``` -Installation of the external dependencies: follow this [link](https://github.com/FusRoman/fink-fat/wiki/Installation) +The pipeline runtime typically follows this sequence: + +1. Load and validate the engine configuration. +2. Open or create the persistence layout. +3. Configure progress reporting and logging. +4. Ingest alerts. +5. Build seeds. +6. Build inter-night edges. +7. Solve connected components. +8. Fit orbits when enabled. +9. Persist the updated state. + +Generated logs are written under the configured storage root, typically in a `logs/` subdirectory. -## Latest News +## Evaluation workflows -* 2023/11/29: Release [0.16.0](https://github.com/FusRoman/fink-fat/releases/tag/v0.16.0) -* 2023/07/11: Release [0.15.0](https://github.com/FusRoman/fink-fat/releases/tag/v0.15.0) -* 2023/07/11: Release [0.14.0](https://github.com/FusRoman/fink-fat/releases/tag/v0.14.0) (Paper version: https://arxiv.org/abs/2305.01123) -* 2022/06/23: Release [0.8.0](https://github.com/FusRoman/fink-fat/releases/tag/0.8.0_beta) -* 2022/03/22: Release [0.6.0](https://github.com/FusRoman/fink-fat/releases/tag/0.6.0_beta) -* 2021/11/25: Release [0.1](https://github.com/FusRoman/fink-fat/releases/tag/v0.1.0-alpha) +The `fink-fat-eval` crate provides four evaluation modes. + +### Seeding evaluation + +Measures seed purity and recall. + +```bash +cargo run --profile evaluation -p fink-fat-eval -- seeding-eval \ + --alerts file:///path/to/sso_dataset_eval.parquet \ + --config crates/fink-fat-eval/eval_config.yml +``` + +### Edge evaluation + +Measures edge purity and recall, and can export the edge-feature dataset used for model training. + +```bash +cargo run --profile evaluation -p fink-fat-eval -- edge-eval \ + --alerts file:///path/to/sso_dataset_eval.parquet \ + --config crates/fink-fat-eval/eval_config.yml \ + --plot-dir edge_plots +``` + +Export features for machine learning: + +```bash +cargo run --profile evaluation -p fink-fat-eval -- edge-eval \ + --alerts file:///path/to/sso_dataset_eval.parquet \ + --config crates/fink-fat-eval/eval_config.yml \ + --export-features crates/fink-fat-eval/src/bin/edge_ml_prediction/edge_features.parquet +``` + +### Solver evaluation + +Measures trajectory-level reconstruction quality. + +```bash +cargo run --profile evaluation -p fink-fat-eval -- solver-eval \ + --alerts file:///path/to/sso_dataset_eval.parquet \ + --config crates/fink-fat-eval/eval_config.yml \ + --plot-dir solver_plots +``` + +### Model evaluation + +Evaluates an ONNX classifier against a labelled feature file. + +```bash +cargo run --profile evaluation -p fink-fat-eval -- model-eval \ + -f crates/fink-fat-eval/src/bin/edge_ml_prediction/edge_features.parquet \ + -x crates/fink-fat-eval/src/bin/edge_ml_prediction/xgb_params.yml \ + -p model_eval_plots +``` + +Use `-t` if you want to override the number of ONNX inference threads. + +## Configuration + +The engine uses a validated YAML configuration file to control: + +- pair and triplet generation, +- edge construction and ranking, +- solver policy and routing, +- persistence layout, +- logging level, +- optional ONNX model paths. + +The evaluation crate ships with a reference configuration at [crates/fink-fat-eval/eval_config.yml](crates/fink-fat-eval/eval_config.yml). + +For a detailed explanation of the engine configuration schema, see [crates/fink-fat-engine/README.md](crates/fink-fat-engine/README.md). + +## Documentation and design notes + +- Rust API documentation is generated from the crate-level docs in `src/` and `crates/fink-fat-engine/src/`. +- High-level design notes are available in `docs/`. +- The workspace uses `katex-header.html` when generating docs so mathematical expressions render correctly. + +## Testing and benchmarks + +Common commands: + +```bash +cargo test --workspace +cargo bench -p fink-fat-engine +``` + +The engine crate also provides dedicated Criterion benchmarks under `crates/fink-fat-engine/benches/`. + +## Repository structure + +```text +fink-fat/ +├── src/ # binary entrypoint and runtime glue +├── crates/ +│ ├── fink-fat-engine/ # core engine library +│ └── fink-fat-eval/ # evaluation binary and ML helper code +├── docs/ # analysis and architecture notes +├── katex-header.html # rustdoc KaTeX header +└── README.md # project overview and usage +``` -## Documentation +## License -All documentation for this project is hosted [here](https://github.com/FusRoman/fink-fat/wiki) +See [LICENSE](LICENSE) for licensing information. diff --git a/benches/track_registry_update.rs b/benches/track_registry_update.rs deleted file mode 100644 index e12f40aa..00000000 --- a/benches/track_registry_update.rs +++ /dev/null @@ -1,296 +0,0 @@ -//! # Benchmark — `TrackRegistry::update_from_link` -//! -//! ## Goal -//! Measure the cost of the **hot loop** inside `update_from_link`: -//! iterating over `Assignment`s (left→right), **merging trajectories** with DSU, -//! and **assigning all member detections** for both seeds under a given -//! `DetectConflictPolicy`. -//! -//! In short, this benchmark approximates the per-night **link-and-grow** -//! trajectory step at LSST scale (millions of detections, ~10⁵ seeds per night). -//! -//! ## What is measured -//! - DSU operations (`find`, `union_keep_min`) when merging `TrajectoryId`s. -//! - Detection assignments in the hot path (hash map/set insertions). -//! - Policy-dependent behavior: -//! - `KeepFirst`: branch that avoids extra inserts when a key is already set. -//! - `Overwrite`: replace-by-singleton fast path. -//! - `Combinatorial`: multi-id accumulation with dedupe. -//! -//! > Note: This benchmark **does not** read from `AlertStore.alerts` in the loop; -//! > membership comes from `SeedNode.members` only (synthetic). -//! -//! ## Scaling knobs (environment variables) -//! - `N_SEEDS_LEFT` — number of seeds on the left night (default: `50_000`) -//! - `N_SEEDS_RIGHT` — number of seeds on the right night (default: `50_000`) -//! - `MEMBERS_PER_SEED` — number of detections per seed (default: `3`) -//! - `N_LINKS` — number of 1-to-1 assignments (default: `min(left,right)`) -//! - `POLICY` — `KeepFirst | Overwrite | Combinatorial` (default: `KeepFirst`) -//! -//! Examples: -//! -//! ```bash -//! cargo bench --bench track_registry_update -//! -//! N_SEEDS_LEFT=200000 N_SEEDS_RIGHT=200000 MEMBERS_PER_SEED=4 N_LINKS=180000 POLICY=Overwrite \ -//! cargo bench --bench track_registry_update -//! ``` -//! -//! ## Synthetic data model -//! `make_snapshot(night_id, n_seeds, members_per_seed)` builds a `NightSnapshot` where: -//! - each `SeedNode` has `members_per_seed` **contiguous** `AlertId`s, -//! - kinematics/photometry are plausible but irrelevant to the loop, -//! - seeds are index-aligned so `Assignment { from: i, to: i }` hits valid pairs. -//! -//! `make_assignments(n_links, ...)` generates `from i → to i` for `i < n_links`. -//! -//! ## Reproducible runs (Criterion) -//! Criterion uses warm-up and statistical sampling. For stable results: -//! - **Disable logs** inside the code under test (see *Noisy I/O* below). -//! - Pin to a CPU / performance governor: -//! ```bash -//! # Linux examples -//! sudo cpupower frequency-set -g performance -//! taskset -c 2 cargo bench --bench track_registry_update -//! ``` -//! - Run multiple times and compare medians. -//! -//! ## Profiling recipes -//! ### Linux `perf` (cycles, branches, cache) -//! ```bash -//! RUSTFLAGS='-C debuginfo=2 -C target-cpu=native' \ -//! perf record -g --call-graph dwarf -- \ -//! cargo bench --bench track_registry_update -//! -//! perf report # TUI -//! perf script > out.perf # Raw stack dump -//! ``` -//! -//! ### Firefox Profiler (interactive flamegraph from `perf`) -//! ```bash -//! perf script > linux-perf.txt -//! # Ouvrir https://profiler.firefox.com/ et importer linux-perf.txt -//! ``` -//! -//! ### Flamegraph (static SVG) -//! ```bash -//! cargo install flamegraph -//! RUSTFLAGS='-C debuginfo=2 -C target-cpu=native' \ -//! cargo flamegraph --bench track_registry_update -//! ``` -//! -//! ## Interpreting results -//! - If **DSU** dominates: check `dsu_find` path compression efficiency and -//! union-by-size balance. Hot paths should show shallow trees. -//! - If **assignments** dominate: expect hotspots in `assign_seed_members` / -//! `assign_detection` (hash lookup/insert, policy branches). -//! - `KeepFirst` should hit the **early-return** path frequently once filled. -//! - `Overwrite` should show **replace-by-singleton** writes (no `clear()`). -//! - `Combinatorial` will scale with the **distinct #traj/DetectKey**. -//! -//! **Throughput heuristics** (ballpark): -//! - Total detection assignments ≈ `N_LINKS × (members_left + members_right)`. -//! - Memory grows with the number of distinct `(DetectKey → TrajectoryId)` pairs -//! plus DSU maps (one entry per trajectory ever created). -//! -//! ## Noisy I/O (disable prints) -//! `println!` inside `update_from_link` will distort timings. Prefer a feature flag: -//! -//! ```rust -//! // in the library code -//! #[cfg(feature = "fat-logs")] -//! println!("Updating from link: ..."); -//! ``` -//! -//! puis bench sans logs : -//! ```bash -//! cargo bench --bench track_registry_update --no-default-features -//! ``` -//! -//! ## Advanced: system counters -//! ```bash -//! perf stat -d -d -d \ -//! env N_SEEDS_LEFT=200000 N_SEEDS_RIGHT=200000 MEMBERS_PER_SEED=6 POLICY=Combinatorial \ -//! cargo bench --bench track_registry_update -//! # Look at: instructions, cycles, IPC, branches-misses, LLC-load-misses -//! ``` -//! -//! ## Common pitfalls -//! - Mixed builds (debug symbols off) make stacks unusable → set `-C debuginfo=2`. -//! - Frequency scaling / thermal throttling skews results → pin CPU & governor. -//! - Changing `POLICY` changes algorithmic shape; compare policies at the same scale. -//! -//! --- - -use std::{cmp::min, env, sync::Arc}; - -use std::hint::black_box; - -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; - -use fink_fat::{ - alerts::{Alert, AlertStore}, - propagation::{ - engine::LinkResult, - features::{SeedId, SeedNode}, - linking::NightSnapshot, - solver::Assignment, - }, - track_registry::{DetectConflictPolicy, TrackRegistry}, - AlertId, NightId, -}; - -fn env_usize(key: &str, default: usize) -> usize { - env::var(key) - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(default) -} - -fn env_policy() -> DetectConflictPolicy { - match env::var("POLICY") - .unwrap_or_else(|_| "KeepFirst".to_string()) - .as_str() - { - "KeepFirst" => DetectConflictPolicy::KeepFirst, - "Overwrite" => DetectConflictPolicy::Overwrite, - "Combinatorial" => DetectConflictPolicy::Combinatorial, - _ => DetectConflictPolicy::KeepFirst, - } -} - -fn make_snapshot(night_id: NightId, n_seeds: usize, members_per_seed: usize) -> NightSnapshot { - let mut seeds = Vec::with_capacity(n_seeds); - let mut next_alert_idx: usize = 0; - - for sid in 0..n_seeds { - let mut members = Vec::with_capacity(members_per_seed); - for _ in 0..members_per_seed { - members.push(AlertId::from(next_alert_idx)); - next_alert_idx += 1; - } - - let seed = SeedNode { - seed_id: sid as u64, - night_id, - epoch_mid: 60_000.0, // MJD TT - pos_xy: [0.0, 0.0], // rad - vel_xy: [1e-4, -1e-4], // rad/day - cov_pos: [[(1e-6_f64).powi(2), 0.0], [0.0, (1e-6_f64).powi(2)]], - cov_vel: [[(1e-6_f64).powi(2), 0.0], [0.0, (1e-6_f64).powi(2)]], - acc_xy: None, - flux_mean: 1000.0, - flux_std: 50.0, - band: 2, - n_obs: members_per_seed as u16, - members, - center_ra: 1.0, // rad - center_dec: 0.1, // rad - ra_mid: 1.0, // rad - dec_mid: 0.1, // rad - }; - seeds.push(seed); - } - - NightSnapshot { - night_id, - pairs: Vec::new(), - triplets: Vec::new(), - seeds, - } -} - -fn make_assignments(n_links: usize, max_left: usize, max_right: usize) -> Vec { - let n = min(n_links, min(max_left, max_right)); - let mut v = Vec::with_capacity(n); - for i in 0..n { - let from: SeedId = i as SeedId; - let to: SeedId = i as SeedId; - v.push(Assignment { - from, - to, - cost: 0.0, - }); - } - v -} - -fn make_dummy_store() -> AlertStore { - AlertStore { - start_mjd: 60_000.0, - alerts: Vec::::new(), - } -} - -fn make_link_result( - night_left: NightId, - night_right: NightId, - matches: Vec, -) -> LinkResult { - LinkResult { - night_left, - night_right, - matches, - edges_kept: 0, - } -} - -pub fn bench_update_from_link(c: &mut Criterion) { - let n_left = env_usize("N_SEEDS_LEFT", 200_000); - let n_right = env_usize("N_SEEDS_RIGHT", 200_000); - let members_per_seed = env_usize("MEMBERS_PER_SEED", 6); - let policy = env_policy(); - - let n_links_req = env_usize("N_LINKS", usize::MAX); - let n_links = min(n_links_req, min(n_left, n_right)); - - let left_id: NightId = 3156; - let right_id: NightId = 3157; - - let left_snap = make_snapshot(left_id, n_left, members_per_seed); - let right_snap = make_snapshot(right_id, n_right, members_per_seed); - let link = make_link_result( - left_id, - right_id, - make_assignments(n_links, n_left, n_right), - ); - - let left_store = Arc::new(make_dummy_store()); - let right_store = Arc::new(make_dummy_store()); - - let group_name = format!( - "update_from_link/{:?}/links={}/mps={}", - policy, n_links, members_per_seed - ); - let mut group = c.benchmark_group(group_name); - - group.bench_function("call", |b| { - b.iter_batched( - || { - let reg = TrackRegistry::new(policy); - ( - reg, - &left_snap, - &right_snap, - &link, - &*left_store, - &*right_store, - ) - }, - |(mut reg, l, r, link, ls, rs)| { - let l = black_box(l); - let r = black_box(r); - let link = black_box(link); - - reg.update_from_link(l, r, link, ls, rs); - black_box(reg); - }, - BatchSize::SmallInput, - ) - }); - - group.finish(); -} - -criterion_group!(benches, bench_update_from_link); -criterion_main!(benches); diff --git a/crates/fink-fat-engine/.gitignore b/crates/fink-fat-engine/.gitignore new file mode 100644 index 00000000..f8eac584 --- /dev/null +++ b/crates/fink-fat-engine/.gitignore @@ -0,0 +1,5 @@ +target/ +proptest-regressions/ +api_description.md +ml_model/ +run_bench.sh diff --git a/crates/fink-fat-engine/Cargo.lock b/crates/fink-fat-engine/Cargo.lock new file mode 100644 index 00000000..66fa7d17 --- /dev/null +++ b/crates/fink-fat-engine/Cargo.lock @@ -0,0 +1,5570 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aberth" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb783fafbd88179a8f541eddc7d23edb991791ff3ed4f86098dd64a418626fb" +dependencies = [ + "arrayvec", + "num-complex", + "num-traits", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "arrow" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4754a624e5ae42081f464514be454b39711daae0458906dacde5f4c632f33a8" +dependencies = [ + "arrow-arith", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-cast 57.3.0", + "arrow-csv", + "arrow-data 57.3.0", + "arrow-ipc 57.3.0", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b3141e0ec5145a22d8694ea8b6d6f69305971c4fa1c1a13ef0195aef2d678b" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "num-traits", +] + +[[package]] +name = "arrow-array" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12fcdb3f1d03f69d3ec26ac67645a8fe3f878d77b5ebb0b15d64a116c212985" +dependencies = [ + "ahash", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "chrono", + "half", + "hashbrown 0.15.5", + "num", +] + +[[package]] +name = "arrow-array" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8955af33b25f3b175ee10af580577280b4bd01f7e823d94c7cdef7cf8c9aef" +dependencies = [ + "ahash", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "chrono-tz", + "half", + "hashbrown 0.16.1", + "num-complex", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-buffer" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263f4801ff1839ef53ebd06f99a56cecd1dbaf314ec893d93168e2e860e0291c" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c697ddca96183182f35b3a18e50b9110b11e916d7b7799cbfd4d34662f2c56c2" +dependencies = [ + "bytes", + "half", + "num-bigint", + "num-traits", +] + +[[package]] +name = "arrow-cast" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede6175fbc039dfc946a61c1b6d42fd682fcecf5ab5d148fbe7667705798cac9" +dependencies = [ + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.3.1", + "atoi", + "base64 0.22.1", + "chrono", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-cast" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "646bbb821e86fd57189c10b4fcdaa941deaf4181924917b0daa92735baa6ada5" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-ord", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "atoi", + "base64 0.22.1", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num-traits", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da746f4180004e3ce7b83c977daf6394d768332349d3d913998b10a120b790a" +dependencies = [ + "arrow-array 57.3.0", + "arrow-cast 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "csv", + "csv-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cfdd7d99b4ff618f167e548b2411e5dd2c98c0ddebedd7df433d34c20a4429" +dependencies = [ + "arrow-buffer 54.3.1", + "arrow-schema 54.3.1", + "half", + "num", +] + +[[package]] +name = "arrow-data" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fdd994a9d28e6365aa78e15da3f3950c0fdcea6b963a12fa1c391afb637b304" +dependencies = [ + "arrow-buffer 57.3.0", + "arrow-schema 57.3.0", + "half", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-ipc" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ff528658b521e33905334723b795ee56b393dbe9cf76c8b1f64b648c65a60c" +dependencies = [ + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "flatbuffers 24.12.23", +] + +[[package]] +name = "arrow-ipc" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf7df950701ab528bf7c0cf7eeadc0445d03ef5d6ffc151eaae6b38a58feff1" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "flatbuffers 25.12.19", + "lz4_flex", + "zstd", +] + +[[package]] +name = "arrow-json" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ff8357658bedc49792b13e2e862b80df908171275f8e6e075c460da5ee4bf86" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-cast 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "chrono", + "half", + "indexmap", + "itoa", + "lexical-core", + "memchr", + "num-traits", + "ryu", + "serde_core", + "serde_json", + "simdutf8", +] + +[[package]] +name = "arrow-ord" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d8f1870e03d4cbed632959498bcc84083b5a24bded52905ae1695bd29da45b" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", +] + +[[package]] +name = "arrow-row" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18228633bad92bff92a95746bbeb16e5fc318e8382b75619dec26db79e4de4c0" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "half", +] + +[[package]] +name = "arrow-schema" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cfaf5e440be44db5413b75b72c2a87c1f8f0627117d110264048f2969b99e9" + +[[package]] +name = "arrow-schema" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" +dependencies = [ + "serde_core", + "serde_json", +] + +[[package]] +name = "arrow-select" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69efcd706420e52cd44f5c4358d279801993846d1c2a8e52111853d61d55a619" +dependencies = [ + "ahash", + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "num", +] + +[[package]] +name = "arrow-select" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bf3e3efbd1278f770d67e5dc410257300b161b93baedb3aae836144edcaf4b" +dependencies = [ + "ahash", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "num-traits", +] + +[[package]] +name = "arrow-string" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e968097061b3c0e9fe3079cf2e703e487890700546b5b0647f60fca1b5a8d8" +dependencies = [ + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-data 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "memchr", + "num-traits", + "regex", + "regex-syntax", +] + +[[package]] +name = "async-compression" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcode" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6ed1b54d8dc333e7be604d00fa9262f4635485ffea923647b6521a5fff045d" +dependencies = [ + "arrayvec", + "bitcode_derive", + "bytemuck", + "glam", + "serde", +] + +[[package]] +name = "bitcode_derive" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238b90427dfad9da4a9abd60f3ec1cdee6b80454bde49ed37f1781dd8e9dc7f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cdshealpix" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c857a9ddf0d44d0a5724ebcd53e299eb260bf105b87162faad07d9b3517af67" +dependencies = [ + "base64 0.21.7", + "byteorder", + "colorous", + "flate2", + "itertools 0.13.0", + "katex-doc", + "log", + "mapproj", + "num", + "num-traits", + "png", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "colorous" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e18bf7a165bf7028fde98609a0f1e8f7498d762a212598e6c891f6893556ec" + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "liblzma", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "config" +version = "0.15.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde-untagged", + "serde_core", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "datafusion" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d12ee9fdc6cdb5898c7691bb994f0ba606c4acc93a2258d78bb9f26ff8158bb3" +dependencies = [ + "arrow", + "arrow-schema 57.3.0", + "async-trait", + "bytes", + "bzip2", + "chrono", + "datafusion-catalog", + "datafusion-catalog-listing", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-datasource-arrow", + "datafusion-datasource-csv", + "datafusion-datasource-json", + "datafusion-datasource-parquet", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-nested", + "datafusion-functions-table", + "datafusion-functions-window", + "datafusion-optimizer", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-session", + "datafusion-sql", + "flate2", + "futures", + "itertools 0.14.0", + "liblzma", + "log", + "object_store", + "parking_lot", + "parquet 57.3.0", + "rand", + "regex", + "sqlparser", + "tempfile", + "tokio", + "url", + "uuid", + "zstd", +] + +[[package]] +name = "datafusion-catalog" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462dc9ef45e5d688aeaae49a7e310587e81b6016b9d03bace5626ad0043e5a9e" +dependencies = [ + "arrow", + "async-trait", + "dashmap", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot", + "tokio", +] + +[[package]] +name = "datafusion-catalog-listing" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b96dbf1d728fc321817b744eb5080cdd75312faa6980b338817f68f3caa4208" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "futures", + "itertools 0.14.0", + "log", + "object_store", +] + +[[package]] +name = "datafusion-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3237a6ff0d2149af4631290074289cae548c9863c885d821315d54c6673a074a" +dependencies = [ + "ahash", + "arrow", + "arrow-ipc 57.3.0", + "chrono", + "half", + "hashbrown 0.16.1", + "indexmap", + "libc", + "log", + "object_store", + "parquet 57.3.0", + "paste", + "recursive", + "sqlparser", + "tokio", + "web-time", +] + +[[package]] +name = "datafusion-common-runtime" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b5e34026af55a1bfccb1ef0a763cf1f64e77c696ffcf5a128a278c31236528" +dependencies = [ + "futures", + "log", + "tokio", +] + +[[package]] +name = "datafusion-datasource" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b2a6be734cc3785e18bbf2a7f2b22537f6b9fb960d79617775a51568c281842" +dependencies = [ + "arrow", + "async-compression", + "async-trait", + "bytes", + "bzip2", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "flate2", + "futures", + "glob", + "itertools 0.14.0", + "liblzma", + "log", + "object_store", + "rand", + "tokio", + "tokio-util", + "url", + "zstd", +] + +[[package]] +name = "datafusion-datasource-arrow" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1739b9b07c9236389e09c74f770e88aff7055250774e9def7d3f4f56b3dcc7be" +dependencies = [ + "arrow", + "arrow-ipc 57.3.0", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "itertools 0.14.0", + "object_store", + "tokio", +] + +[[package]] +name = "datafusion-datasource-csv" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c73bc54b518bbba7c7650299d07d58730293cfba4356f6f428cc94c20b7600" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "regex", + "tokio", +] + +[[package]] +name = "datafusion-datasource-json" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37812c8494c698c4d889374ecfabbff780f1f26d9ec095dd1bddfc2a8ca12559" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "tokio", +] + +[[package]] +name = "datafusion-datasource-parquet" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210937ecd9f0e824c397e73f4b5385c97cd1aff43ab2b5836fcfd2d321523fb" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-pruning", + "datafusion-session", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot", + "parquet 57.3.0", + "tokio", +] + +[[package]] +name = "datafusion-doc" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c825f969126bc2ef6a6a02d94b3c07abff871acf4d6dd759ce1255edb7923ce" + +[[package]] +name = "datafusion-execution" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa03ef05a2c2f90dd6c743e3e111078e322f4b395d20d4b4d431a245d79521ae" +dependencies = [ + "arrow", + "async-trait", + "chrono", + "dashmap", + "datafusion-common", + "datafusion-expr", + "futures", + "log", + "object_store", + "parking_lot", + "rand", + "tempfile", + "url", +] + +[[package]] +name = "datafusion-expr" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef33934c1f98ee695cc51192cc5f9ed3a8febee84fdbcd9131bf9d3a9a78276f" +dependencies = [ + "arrow", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-doc", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr-common", + "indexmap", + "itertools 0.14.0", + "paste", + "recursive", + "serde_json", + "sqlparser", +] + +[[package]] +name = "datafusion-expr-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000c98206e3dd47d2939a94b6c67af4bfa6732dd668ac4fafdbde408fd9134ea" +dependencies = [ + "arrow", + "datafusion-common", + "indexmap", + "itertools 0.14.0", + "paste", +] + +[[package]] +name = "datafusion-functions" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379b01418ab95ca947014066248c22139fe9af9289354de10b445bd000d5d276" +dependencies = [ + "arrow", + "arrow-buffer 57.3.0", + "base64 0.22.1", + "blake2", + "blake3", + "chrono", + "chrono-tz", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-macros", + "hex", + "itertools 0.14.0", + "log", + "md-5", + "num-traits", + "rand", + "regex", + "sha2", + "unicode-segmentation", + "uuid", +] + +[[package]] +name = "datafusion-functions-aggregate" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd00d5454ba4c3f8ebbd04bd6a6a9dc7ced7c56d883f70f2076c188be8459e4c" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "half", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-aggregate-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec06b380729a87210a4e11f555ec2d729a328142253f8d557b87593622ecc9f" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-functions-nested" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904f48d45e0f1eb7d0eb5c0f80f2b5c6046a85454364a6b16a2e0b46f62e7dff" +dependencies = [ + "arrow", + "arrow-ord", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr-common", + "itertools 0.14.0", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-table" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a0d20e2b887e11bee24f7734d780a2588b925796ac741c3118dd06d5aa77f0" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot", + "paste", +] + +[[package]] +name = "datafusion-functions-window" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3414b0a07e39b6979fe3a69c7aa79a9f1369f1d5c8e52146e66058be1b285ee" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-doc", + "datafusion-expr", + "datafusion-functions-window-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-window-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf2feae63cd4754e31add64ce75cae07d015bce4bb41cd09872f93add32523a" +dependencies = [ + "datafusion-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-macros" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fe888aeb6a095c4bcbe8ac1874c4b9a4c7ffa2ba849db7922683ba20875aaf" +dependencies = [ + "datafusion-doc", + "quote", + "syn", +] + +[[package]] +name = "datafusion-optimizer" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6527c063ae305c11be397a86d8193936f4b84d137fe40bd706dfc178cf733c" +dependencies = [ + "arrow", + "chrono", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "indexmap", + "itertools 0.14.0", + "log", + "recursive", + "regex", + "regex-syntax", +] + +[[package]] +name = "datafusion-physical-expr" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb028323dd4efd049dd8a78d78fe81b2b969447b39c51424167f973ac5811d9" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-physical-expr-common", + "half", + "hashbrown 0.16.1", + "indexmap", + "itertools 0.14.0", + "parking_lot", + "paste", + "petgraph", + "recursive", + "tokio", +] + +[[package]] +name = "datafusion-physical-expr-adapter" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78fe0826aef7eab6b4b61533d811234a7a9e5e458331ebbf94152a51fc8ab433" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-expr", + "datafusion-functions", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "itertools 0.14.0", +] + +[[package]] +name = "datafusion-physical-expr-common" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfccd388620734c661bd8b7ca93c44cdd59fecc9b550eea416a78ffcbb29475f" +dependencies = [ + "ahash", + "arrow", + "chrono", + "datafusion-common", + "datafusion-expr-common", + "hashbrown 0.16.1", + "indexmap", + "itertools 0.14.0", + "parking_lot", +] + +[[package]] +name = "datafusion-physical-optimizer" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5fa10e73259a03b705d5fddc136516814ab5f441b939525618a4070f5a059" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-pruning", + "itertools 0.14.0", + "recursive", +] + +[[package]] +name = "datafusion-physical-plan" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1098760fb29127c24cc9ade3277051dc73c9ed0ac0131bd7bcd742e0ad7470" +dependencies = [ + "ahash", + "arrow", + "arrow-ord", + "arrow-schema 57.3.0", + "async-trait", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "futures", + "half", + "hashbrown 0.16.1", + "indexmap", + "itertools 0.14.0", + "log", + "parking_lot", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "datafusion-pruning" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d0fef4201777b52951edec086c21a5b246f3c82621569ddb4a26f488bc38a9" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-datasource", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "itertools 0.14.0", + "log", +] + +[[package]] +name = "datafusion-session" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71f1e39e8f2acbf1c63b0e93756c2e970a64729dab70ac789587d6237c4fde0" +dependencies = [ + "async-trait", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot", +] + +[[package]] +name = "datafusion-sql" +version = "52.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44693cfcaeb7a9f12d71d1c576c3a6dc025a12cef209375fa2d16fb3b5670ee" +dependencies = [ + "arrow", + "bigdecimal", + "chrono", + "datafusion-common", + "datafusion-expr", + "indexmap", + "log", + "recursive", + "regex", + "sqlparser", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dns-lookup" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5597a4b7fe5275fc9dcf88ce26326bc8e4cb87d0130f33752d4c5f717793cf" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.60.2", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fink-fat-engine" +version = "0.1.0" +dependencies = [ + "ahash", + "approx", + "arrow-array 57.3.0", + "bitcode", + "camino", + "cdshealpix", + "chrono", + "config", + "criterion", + "datafusion", + "hdfs-native-object-store", + "ndarray", + "object_store", + "once_cell", + "ort", + "outfit", + "proptest", + "rand", + "rayon", + "serde", + "serde_json", + "serde_yaml", + "smallvec", + "tempfile", + "thiserror 2.0.17", + "thread_local", + "tokio", + "url", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" +dependencies = [ + "bitflags 2.10.0", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "g2gen" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a7e0eb46f83a20260b850117d204366674e85d3a908d90865c78df9a6b1dfc" +dependencies = [ + "g2poly", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "g2p" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539e2644c030d3bf4cd208cb842d2ce2f80e82e6e8472390bcef83ceba0d80ad" +dependencies = [ + "g2gen", + "g2poly", +] + +[[package]] +name = "g2poly" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312d2295c7302019c395cfb90dacd00a82a2eabd700429bba9c7a3f38dbbe11b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glam" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a4d85559e2637d3d839438b5b3d75c31e655276f9544d72475c36b92fabbed" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "hdfs-native" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08603b51f970930b0025b92d3f6c0ba39a0a6d0dfb4b3f527af58768adc2f3b2" +dependencies = [ + "aes", + "base64 0.22.1", + "bitflags 2.10.0", + "bumpalo", + "bytes", + "cbc", + "chrono", + "cipher", + "crc", + "ctr", + "des", + "dns-lookup", + "futures", + "g2p", + "hex", + "hmac", + "libc", + "libloading", + "log", + "md-5", + "num-traits", + "once_cell", + "prost", + "prost-types", + "rand", + "regex", + "roxmltree", + "socket2", + "thiserror 2.0.17", + "tokio", + "url", + "uuid", + "whoami", +] + +[[package]] +name = "hdfs-native-object-store" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5495a763df493ea3883271e727914e83a0ce188a32e230ca820e3cb5e188d2" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "hdfs-native", + "object_store", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hifitime" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd986e90b53308b583668349332fcb508cdecb183d18e0170abe3c01e6aeeb60" +dependencies = [ + "js-sys", + "lexical-core", + "num-traits", + "openssl", + "serde", + "serde_derive", + "snafu", + "tabled", + "ureq", + "wasm-bindgen", + "web-sys", + "web-time", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f0ae375a85536cac3a243e3a9cda80a47910348abdea7e2c22f8ec556d586d" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "katex-doc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e5b80bdbfb9176d293875db5dbf05eeeb1d4e423891c5c0520da7bd467440b9" + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "liblzma" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c36d08cad03a3fbe2c4e7bb3a9e84c57e4ee4135ed0b065cade3d98480c648" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.7.1", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e" +dependencies = [ + "twox-hash 2.1.2", +] + +[[package]] +name = "lzma-rust2" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" + +[[package]] +name = "mapproj" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c713291f52f6d5d1054476236228037131db3d05cc64ce5f58f020b7b99223" + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe 0.1.6", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfbfff40aeccab00ec8a910b57ca8ecf4319b335c542f2edcd19dd25a1e2a00" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http", + "http-body-util", + "humantime", + "hyper", + "itertools 0.14.0", + "parking_lot", + "percent-encoding", + "quick-xml 0.38.4", + "rand", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "ort" +version = "2.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c" +dependencies = [ + "ndarray", + "ort-sys", + "smallvec", + "tracing", + "ureq", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b" +dependencies = [ + "hmac-sha256", + "lzma-rust2", + "ureq", +] + +[[package]] +name = "outfit" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c17e18f9a380770298fe76af1b637e49bab78381c408e0b515ea556117a602" +dependencies = [ + "aberth", + "ahash", + "arrow-array 54.3.1", + "camino", + "comfy-table", + "directories", + "hifitime", + "itertools 0.14.0", + "nalgebra", + "nom", + "once_cell", + "ordered-float 5.1.0", + "parquet 54.3.1", + "quick-xml 0.37.5", + "rand", + "rand_distr", + "roots", + "serde", + "smallvec", + "thiserror 2.0.17", + "ureq", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "papergrid" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parquet" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb15796ac6f56b429fd99e33ba133783ad75b27c36b4b5ce06f1f82cc97754e" +dependencies = [ + "ahash", + "arrow-array 54.3.1", + "arrow-buffer 54.3.1", + "arrow-cast 54.3.1", + "arrow-data 54.3.1", + "arrow-ipc 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.3.1", + "base64 0.22.1", + "bytes", + "chrono", + "half", + "hashbrown 0.15.5", + "num", + "num-bigint", + "paste", + "seq-macro", + "snap", + "thrift", + "twox-hash 1.6.3", +] + +[[package]] +name = "parquet" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee96b29972a257b855ff2341b37e61af5f12d6af1158b6dcdb5b31ea07bb3cb" +dependencies = [ + "ahash", + "arrow-array 57.3.0", + "arrow-buffer 57.3.0", + "arrow-cast 57.3.0", + "arrow-data 57.3.0", + "arrow-ipc 57.3.0", + "arrow-schema 57.3.0", + "arrow-select 57.3.0", + "base64 0.22.1", + "brotli", + "bytes", + "chrono", + "flate2", + "futures", + "half", + "hashbrown 0.16.1", + "lz4_flex", + "num-bigint", + "num-integer", + "num-traits", + "object_store", + "paste", + "seq-macro", + "simdutf8", + "snap", + "thrift", + "tokio", + "twox-hash 2.1.2", + "zstd", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags 2.10.0", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "roots" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082f11ffa03bbef6c2c6ea6bea1acafaade2fd9050ae0234ab44a2153742b058" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "backtrace", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + +[[package]] +name = "sqlparser" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4591acadbcf52f0af60eafbb2c003232b2b4cd8de5f0e9437cb8b1b59046cc0f" +dependencies = [ + "log", + "recursive", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tabled" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" +dependencies = [ + "papergrid", + "tabled_derive", + "testing_table", +] + +[[package]] +name = "tabled_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846" +dependencies = [ + "heck", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "thrift" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" +dependencies = [ + "byteorder", + "integer-encoding", + "ordered-float 2.10.1", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64 0.22.1", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls", + "rustls-pki-types", + "socks", + "ureq-proto", + "utf-8", + "webpki-root-certs", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/crates/fink-fat-engine/Cargo.toml b/crates/fink-fat-engine/Cargo.toml new file mode 100644 index 00000000..f9643a3e --- /dev/null +++ b/crates/fink-fat-engine/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "fink-fat-engine" +version.workspace = true +edition.workspace = true +authors.workspace = true +categories.workspace = true +keywords.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +license-file.workspace = true +readme = "README.md" +description = "Core engine for the Fink-FAT asteroid detection pipeline." + +[dependencies] +rand = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +camino = { workspace = true } +tracing = { workspace = true } +ahash = { workspace = true } +arrow-array = "57.3.0" +bitcode = { version = "0.6.9", features = ["serde"] } +cdshealpix = "0.7.3" +chrono = "0.4.42" +config = "0.15.19" +datafusion = { version = "52.1.0" } +object_store = { version = "=0.12.5", features = ["http"] } +hdfs-native-object-store = "0.15.0" +ndarray = { workspace = true } +once_cell = "1.21.3" +ort = { version = "2.0.0-rc.11", features = ["ndarray"] } +outfit = { version = "2.1.0", features = ["parallel", "jpl-download"] } +rayon = "1.11.0" +serde_json = "1.0.145" +zstd = { version = "0.13", default-features = false } +lz4_flex = { version = "0.11", default-features = false, features = ["frame"] } +flate2 = { version = "1.0", default-features = false, features = ["rust_backend"] } +smallvec = "1.15.1" +thread_local = "1.1.9" +url = "2.5.8" +tokio = { version = "=1.49.0", features = ["rt-multi-thread"] } + +[dev-dependencies] +approx = "0.5.1" +criterion = "0.8.1" +proptest = "1.9.0" +serde_yaml = { workspace = true } +tempfile = { workspace = true } + +[[bench]] +name = "generate_topk_edges" +harness = false + +[[bench]] +name = "edge_build_real" +harness = false diff --git a/crates/fink-fat-engine/README.md b/crates/fink-fat-engine/README.md new file mode 100644 index 00000000..878882b5 --- /dev/null +++ b/crates/fink-fat-engine/README.md @@ -0,0 +1,440 @@ +# fink-fat-engine + +Core detection-linking library for the Fink-FAT asteroid detection pipeline. +This crate provides the complete computational engine: from raw photometric +alerts to multi-night trajectory hypotheses and preliminary orbit fitting. + +--- + +## Table of contents + +1. [Overview](#overview) +2. [Pipeline stages](#pipeline-stages) +3. [Data model](#data-model) +4. [Configuration](#configuration) +5. [Module map](#module-map) +6. [Key types](#key-types) +7. [Persistence](#persistence) +8. [Orbit fitting](#orbit-fitting) +9. [Running benchmarks](#running-benchmarks) +10. [Development notes](#development-notes) + +--- + +## Overview + +`fink-fat-engine` implements a multi-night moving-object linking pipeline +designed for large-scale photometric surveys (ZTF, Vera Rubin Observatory). +The pipeline ingests nightly alert batches, builds **seeds** (intra-night +kinematic models from pairs or triplets of detections), connects seeds across +nights into a **directed graph**, solves each connected component for +trajectory hypotheses, and optionally fits a preliminary orbit. + +The crate is a pure library. All I/O, CLI logic, and evaluation tooling live +in the adjacent `fink-fat` and `fink-fat-eval` crates. + +--- + +## Pipeline stages + +The engine runs as an ordered sequence of stages, orchestrated by +`PipelineRunner`. Each stage is independent and can be individually +enabled or disabled in the `PipelinePlan`. + +``` +LoadPersistedData + │ + ▼ +IngestNights ← parse and store alert Parquet batches + │ + ▼ +BuildSeeds ← form pairs/triplets → SeedNode (intra-night) + │ + ▼ +BuildEdges ← inter-night kinematic linking → AlertLinkageDAG + │ + ▼ +Solve ← connected components → TrackHypothesis + │ + ▼ +FitOrbit ← preliminary orbit determination (outfit / find_orb) + │ + ▼ +SavePersistedData ← flush alerts, seeds, edge journal, state +``` + +### Stage details + +| Stage | Key input | Key output | +|---|---|---| +| `IngestNights` | Parquet alert files | `AlertStore` | +| `BuildSeeds` | `AlertStore` | `SeedStore`, `SeedSpatialIndex` | +| `BuildEdges` | `SeedStore` + spatial index | `AlertLinkageDAG` | +| `Solve` | `AlertLinkageDAG` components | `Vec` | +| `FitOrbit` | `TrackHypothesis` list | `FullOrbitResult` | +| `SavePersistedData` | Runtime state | incremental journal on disk | + +Progress is reported through the `PipelineHooks` trait, which can be backed +by any progress-bar or logging implementation. + +--- + +## Data model + +### Alert + +An `Alert` represents a single photometric detection: + +| Field | Type | Unit | +|---|---|---| +| `ra`, `dec` | `f64` | radians, ICRS J2000 | +| `ra_err`, `dec_err` | `f64` | radians, 1σ | +| `mjd_tt` | `f64` | MJD TT (days) | +| `flux`, `flux_err` | `f64` | PSF difference flux (upstream-dependent) | +| `band` | `u8` | photometric band code (LSST: u=0 … y=5) | +| `dia_source_id` | `u64` | upstream unique detection identifier | + +`Alert` implements total ordering (primary key: `mjd_tt`), bit-exact `Eq`/`Hash`, +and `Serialize`/`Deserialize`. + +### SeedNode + +A `SeedNode` is an intra-night kinematic model built from two or three alerts: + +- **Pair** → linear motion model on a gnomonic tangent plane. +- **Triplet** → quadratic motion model (includes acceleration). + +Each seed stores its `NightId`, a `TangentPlaneModel` (position, velocity, +covariance), photometric aggregates (`Photometry`), and member alert keys. + +Seeds are the atomic units fed into the inter-night graph. + +### Edge + +A directed inter-night link from an earlier `SeedNode` (`from`) to a later +one (`to`). Each edge carries: + +- `cost` — strictly positive scalar solver weight, +- `dt_days` — time gap between seeds, +- `active` flag — used for deactivation after track selection. + +### TrackHypothesis + +An ordered chain of `SeedNode`s connected by `Edge`s. The key fields are: + +- `nodes: Vec` — time-ordered seeds, +- `edges: Vec` — connecting edges, +- `cost: f64` — additive solver cost (lower is better), +- `night_span: u32` — number of nights spanned. + +--- + +## Configuration + +The engine is configured via a single YAML file loaded by +`load_engine_config_validated`. Unknown keys are rejected; missing fields +fall back to Rust defaults. + +### Precedence (later overrides earlier) + +1. Rust `Default` implementations. +2. YAML file at the specified path. +3. Environment variables with prefix `FINK_FAT` and separator `__`. + +```bash +# Example environment overrides +export FINK_FAT__MAX_GAP_NIGHTS=4 +export FINK_FAT__EDGES__TOP_K_PER_LEFT=64 +``` + +### Top-level structure + +```yaml +version: 1 +max_gap_nights: 2 # maximum inter-night gap considered for linking +storage_path: "storage/" # root for on-disk persistence + +pairs: + max_dt: "86.4 min" + max_angular_speed: "35 arcmin/day" + max_flux_difference: 2.5 + +triplets: + max_dt_between: "30 min" + max_pair_sep: "10 arcmin" + max_predicted_residual: "5 arcmin" + max_flux_difference: 2.5 + +edges: + top_k_per_left: 32 + parallel_left_batches: true + parallel_left_batch_size: 512 + predictor_config: + k_sigma: 3.5 + noise: { variance_floor: 1.0e-12, drift_per_day: 0.0, curvature_per_day2: 5.0e-14 } + pad_cell_radius: true + time_bin_dt: 0.021 + v_slack: 0.0 + cost: + variant: singer_cwna + sigma_q: 1.0e-3 + +solver: + policy: + # routing thresholds (see SolverPolicy) + bounded_beam: + beam_width: 8 + max_tracks: 3 + max_tracks_per_source: 2 + max_expansions: 2048 + max_out_per_node: 4 +``` + +### Edge operational modes + +Three modes are available, controlled by `top_k_per_left` and `ml_post_filter`: + +| Mode | `top_k_per_left` | `ml_post_filter` | Use-case | +|---|---|---|---| +| Emit-all | `~` (null) | `false` | Debug / dataset generation | +| Cost-based Top-K | `Some(k)` | `false` | Production without ONNX | +| Top-K + ML post-filter | `Some(k)` | `true` | Production with ONNX model | + +When `ml_post_filter: true`, the engine scores the entire retained edge set +**once** with an ONNX binary classifier and discards edges below +`ml_post_filter_threshold`. This is cheaper than per-seed inference because +ONNX runs on the already-pruned set. + +```yaml +edges: + ml_post_filter: true + ml_post_filter_threshold: 0.5 + edge_ranking_model_path: "edge_ranker.onnx" + onnx_batch_size: 128 + onnx_intra_threads: 4 # optional; None = ORT auto-select + top_k_per_left: 32 +``` + +### Cost function + +The scalar edge cost drives both Top-K candidate selection and graph solvers. +It is computed by +[`EdgeFeatures::compute_cost`](https://docs.rs/fink-fat-engine/latest/fink_fat_engine/graph/edge/edge_features/struct.EdgeFeatures.html#method.compute_cost) +in three independent steps: + +$$c = c\_{\mathrm{kin}}(\chi^2\_{\mathrm{pos}},\, \chi^2\_{\mathrm{vel}}) + c\_{\mathrm{phot}}$$ + +#### Step 1 — χ² extraction + +The positional innovation uses the **spherical great-circle residual** $d$ +(Vincenty formula): + +$$\chi^2\_{\mathrm{pos}} = \frac{d^2}{S\_{\mathrm{pos}}}$$ + +where $S\_{\mathrm{pos}} = \mathrm{tr}(\mathbf{S}\_{\mathrm{pos}}) / 2$ is the +mean diagonal of the innovation covariance. The velocity innovation uses the +full 2×2 Mahalanobis: + +$$\chi^2\_{\mathrm{vel}} = \delta\mathbf{v}^{\top} \mathbf{S}\_{\mathrm{vel}}^{-1} \delta\mathbf{v}$$ + +#### Step 2 — optional CWNA covariance inflation (Singer model) + +When `sigma_q > 0`, both covariances are inflated by a **Continuous White +Noise Acceleration (CWNA)** diagonal term before computing χ² (Singer 1970): + +$$\mathbf{S}\_{\mathrm{pos}} \leftarrow \mathbf{S}\_{\mathrm{pos}} + \sigma\_q^2 \frac{\Delta t^3}{3}\,\mathbf{I}$$ + +$$\mathbf{S}\_{\mathrm{vel}} \leftarrow \mathbf{S}\_{\mathrm{vel}} + \sigma\_q^2 \Delta t\,\mathbf{I}$$ + +This keeps $\chi^2 \sim O(1)$ for all inter-night gaps when $\sigma\_q$ is +well-calibrated (~$10^{-3}$ rad·day$^{-3/2}$), removing the $\Delta t^2$ +growth that plagues the pure Gaussian model. + +#### Step 3 — kinematic loss variant + +| Variant | $c\_{\mathrm{kin}}$ | CWNA | Notes | +|---|---|---|---| +| `gaussian_chi2` | $\frac{1}{2}(\chi^2\_\mathrm{pos} + \chi^2\_\mathrm{vel})$ | no | Standard NLL for constant-velocity model | +| `kinematic_log_likelihood` | same | no | Backward-compat alias for `gaussian_chi2` | +| `singer_cwna` | $\frac{1}{2}(\chi^2\_\mathrm{pos} + \chi^2\_\mathrm{vel})$ | **yes** | Same Gaussian NLL on CWNA-inflated covariances (**recommended**) | +| `robust_cauchy` | $\ln\!\bigl(1 + \chi^2\_\mathrm{pos}/\sigma\bigr) + \ln\!\bigl(1 + \chi^2\_\mathrm{vel}/\sigma\bigr)$ | optional | Saturates logarithmically; $\sigma$ = `cauchy_scale` | +| `robust_student_t` | $\frac{\nu+1}{2}\Bigl[\ln\!\bigl(1+\chi^2\_\mathrm{pos}/\nu\bigr) + \ln\!\bigl(1+\chi^2\_\mathrm{vel}/\nu\bigr)\Bigr]$ | optional | $\nu=1$: Cauchy; $\nu\!\to\!\infty$: Gaussian; $\nu$ = `student_nu` | + +#### Step 4 — photometry penalty + +Added unconditionally regardless of the kinematic variant: + +$$c\_{\mathrm{phot}} = \frac{1}{2} z\_{\mathrm{flux}}^{2} + \frac{1}{2}\bigl[\ln(|r\_{\sigma}| + \varepsilon)\bigr]^{2} + b\_{\mathrm{band}}$$ + +where $z\_{\mathrm{flux}}$ is the flux z-score between the two seeds, +$r\_{\sigma}$ is the ratio of their flux standard deviations, and +$b\_{\mathrm{band}} = 0$ when both seeds share a photometric band, +$b\_{\mathrm{band}} \approx 6.9$ otherwise. + +For full implementation details see +[`edge_features`](https://docs.rs/fink-fat-engine/latest/fink_fat_engine/graph/edge/edge_features/index.html) +and +[`edge_config`](https://docs.rs/fink-fat-engine/latest/fink_fat_engine/engine_config/edge_config/index.html). + +--- + +## Module map + +``` +fink-fat-engine/src/ +├── alerts/ Alert data model, AlertStore (per-night indexed store) +├── astro_math.rs Spherical geometry, tangent-plane projections, linear algebra +├── display_format.rs Indented debug formatting utilities +├── engine_config/ Root EngineConfig + sub-configs: +│ ├── pair_config.rs PairConfig (intra-night pair pre-filter) +│ ├── triplet_config.rs TripletConfig (intra-night triplet constraints) +│ ├── edge_config.rs EdgeConfig (inter-night edge building + ML) +│ ├── propagator_config.rs PredictorParams (cone prediction for retrieval) +│ ├── solver_config/ SolverConfig + BoundedBeamConfig + SolverPolicy +│ └── pipeline_policy.rs PersistPolicy +├── error.rs Top-level EngineError / FinkFatError +├── graph/ +│ ├── mod.rs AlertLinkageDAG (directed edge store) +│ └── edge/ +│ ├── mod.rs Edge construction, build_edges entry-point +│ ├── edge_features.rs EdgeFeatures (17 kinematic + photometry features) +│ ├── edge_prediction.rs EdgeRankingModel / EdgeRankingModelPool (ONNX) +│ └── ranking_topk.rs rank_topk_edges_for_left_by_cost (cost-based Top-K) +├── night_id.rs NightId (integer night identifier + PairingMode) +├── persistence/ PersistenceManager, layouts, edge journal, compression +├── pipeline/ +│ ├── mod.rs PipelineRunner, PipelinePlan, PipelineContext +│ ├── hooks.rs PipelineHooks + StageProgress traits +│ └── stages/ One file per stage (alert_inputs, seed_builder, …) +├── seeding/ +│ ├── mod.rs SeedNode, from_pair / from_triplet constructors +│ ├── pairs.rs Intra-night pair generation +│ ├── triplets.rs Intra-night triplet generation +│ ├── tangent_plane.rs TangentPlaneModel (local kinematic model) +│ ├── photometry.rs Photometry aggregates +│ ├── seed_spatial_index.rs SeedSpatialIndex (HEALPix + time-bin spatial index) +│ └── store.rs SeedStore (per-night seed storage) +├── solver/ +│ ├── mod.rs Shared solver API: SolverOutput, TrackHypothesis, SolverDiagnostics +│ ├── components/ ConnectedComponents (component-restricted adjacency) +│ ├── bounded_beam.rs BoundedBeamSolver (beam-search path enumeration) +│ ├── min_cost_flow/ Min-cost flow solver (for larger components) +│ └── solver_manager.rs SolverManager (routing + dispatch) +├── spacetime_bucket/ HEALPix spatial binner + time binner (bucket indexing) +├── trajectory/ TrackHypothesis, TrackId, orbit-fitting I/O +``` + +--- + +## Key types + +| Type | Module | Role | +|---|---|---| +| `Alert` | `alerts` | Single photometric detection | +| `AlertStore` | `alerts::store` | Per-night alert storage | +| `SeedNode` | `seeding` | Intra-night kinematic seed (pair or triplet) | +| `SeedSpatialIndex` | `seeding::seed_spatial_index` | HEALPix + time spatial index over seeds | +| `TangentPlaneModel` | `seeding::tangent_plane` | Local gnomonic kinematic model | +| `Edge` | `graph::edge` | Directed inter-night seed link | +| `EdgeFeatures` | `graph::edge::edge_features` | 17-dimensional feature vector for ML + cost | +| `EdgeRankingModelPool` | `graph::edge::edge_prediction` | Thread-local lazy ONNX session pool | +| `AlertLinkageDAG` | `graph` | Inter-night directed edge store | +| `TrackHypothesis` | `trajectory` | Ordered multi-night seed chain | +| `PipelineRunner` | `pipeline` | Stage orchestrator | +| `EngineConfig` | `engine_config` | Root configuration container | +| `PersistenceManager` | `persistence` | On-disk state orchestrator | + +--- + +## Persistence + +State is persisted incrementally using an **edge journal** (delta-based) and +snapshot files for alerts and seeds. The layout is managed by +`PersistenceLayout` under the `storage_path` directory: + +``` +storage_path/ +├── manifest.json +├── alerts/ +│ └── /alerts.bin.zst +├── seeds/ +│ └── /seeds.bin.zst +├── edges/ +│ └── journal_.bin.zst ← EdgeOp deltas +└── state.bin +``` + +Supported compression codecs: `zstd`, `lz4`, `gzip`, `none`. +Serialisation uses `bitcode` with `serde` for compact binary encoding. + +--- + +## Orbit fitting + +The `FitOrbit` stage uses the [`outfit`](https://crates.io/crates/outfit) crate +([source](https://github.com/FusRoman/Outfit)), a Rust library for initial orbit +determination (IOD) that wraps the Horizons ephemeris system (DE440) and the +FCCT14 error model. + +The stage: + +1. Converts track hypotheses to observation batches (`ObservationBatch`). +2. Initialises an `Outfit` environment (`DE440` + `FCCT14`). +3. Resolves observers from MPC observatory codes. +4. Builds a `TrajectorySet` from all observation groups. +5. Runs `estimate_all_orbits_in_batches_parallel` with configurable IOD + parameters (noise realisations, triplet sampling, etc.). +6. Stores results in `RuntimeState::orbit_results` (`FullOrbitResult`). +7. Deactivates graph edges belonging to trajectories that received a confirmed + orbit (persisted as `EdgeOp::Upsert { active: false }` in the edge journal). + +Orbit fitting is optional: the stage can be omitted from the `PipelinePlan` +when running linking-only benchmarks. + +--- + +## Running benchmarks + +Two Criterion benchmark suites are provided: + +```bash +# Cost-based Top-K edge generation (synthetic data) +cargo bench -p fink-fat-engine --bench generate_topk_edges + +# Full edge build on real/realistic data +cargo bench -p fink-fat-engine --bench edge_build_real +``` + +--- + +## Development notes + +### Coordinate conventions + +- All angles: **radians**, ICRS J2000. +- All times: **MJD TT** (days). +- Tangent-plane velocities: **rad/day**. +- Costs: strictly positive, dimensionless. + +### Thread safety + +- `AlertStore`, `SeedStore`, `AlertLinkageDAG`: single-threaded (stage-owned). +- `EdgeRankingModelPool`: thread-safe via `thread_local::ThreadLocal` + `RefCell`. + Each Rayon thread gets its own lazily-initialized ONNX session. +- `PipelineHooks`: `Send + Sync` required. + +### Testing + +Unit tests live alongside each module (`#[cfg(test)]` blocks). + +```bash +cargo test -p fink-fat-engine +``` + +### Documentation + +API documentation with KaTeX-rendered math: + +```bash +RUSTDOCFLAGS="--html-in-header $(pwd)/katex-header.html" cargo doc --workspace --open +``` diff --git a/crates/fink-fat-engine/benches/edge_build_real.rs b/crates/fink-fat-engine/benches/edge_build_real.rs new file mode 100644 index 00000000..1034e247 --- /dev/null +++ b/crates/fink-fat-engine/benches/edge_build_real.rs @@ -0,0 +1,178 @@ +//! Benchmark replicating the `BuildEdges` pipeline stage with real alert data. +//! +//! Designed for profiling with `perf` + Firefox Profiler or `samply`. +//! +//! # Quick start +//! +//! ```bash +//! # Option A – samply (output opens directly in Firefox Profiler) +//! cargo install samply +//! samply record cargo bench --profile evaluation -p fink-fat-engine --bench edge_build_real -- --profile-time 30 +//! +//! # Option B – perf + manual upload +//! cargo build --profile evaluation -p fink-fat-engine --benches +//! BENCH=$(ls target/evaluation/deps/edge_build_real-* | grep -v '\.d' | head -1) +//! perf record -F 997 --call-graph dwarf -g -- "$BENCH" --bench --profile-time 30 +//! perf script | gzip > edge_build_profile.gz +//! # then upload edge_build_profile.gz to https://profiler.firefox.com +//! ``` +//! +//! # Environment variables +//! +//! | Variable | Default | +//! |---------------------------|------------------------------------------------------| +//! | `FINK_FAT_ALERTS_PARQUET` | `test_exp/sso_dataset_eval.parquet` | +//! | `FINK_FAT_CONFIG` | `crates/fink-fat-eval/eval_config_best.yml` | +//! +//! The paths are resolved relative to the **workspace root** (run the bench +//! from there, or set the variables to absolute paths). + +use std::hint::black_box; +use std::time::{Duration, Instant}; + +use camino::Utf8PathBuf; +use criterion::{Criterion, criterion_group, criterion_main}; + +use fink_fat_engine::{ + engine_config::{load_engine_config_validated, pipeline_policy::PersistPolicy}, + graph::{AlertLinkageDAG, edge::edge_prediction::EdgeRankingModelPool}, + persistence::{PersistenceManager, runtime_state::RuntimeState}, + pipeline::{ + PipelineContext, PipelineInputs, PipelinePlan, + hooks::NoopHooks, + stages::{PipelineStage, alert_inputs::input_uri::InputUri}, + }, + solver::solver_manager::SolverManager, +}; + +const DEFAULT_ALERTS: &str = "test_exp/sso_dataset_eval.parquet"; +const DEFAULT_CONFIG: &str = "crates/fink-fat-eval/eval_config_best.yml"; + +fn bench_build_edges_real(c: &mut Criterion) { + // ── 1. Resolve paths ───────────────────────────────────────────────────── + let alerts_path = + std::env::var("FINK_FAT_ALERTS_PARQUET").unwrap_or_else(|_| DEFAULT_ALERTS.to_string()); + let config_path = + std::env::var("FINK_FAT_CONFIG").unwrap_or_else(|_| DEFAULT_CONFIG.to_string()); + + // ── 2. Load engine config ───────────────────────────────────────────────── + let engine_config = load_engine_config_validated(camino::Utf8Path::new(&config_path)) + .unwrap_or_else(|e| panic!("failed to load engine config from {config_path}: {e}")); + + // ── 3. Build model pool (None when ml_post_filter is false) ─────────────── + let model_pool: Option = engine_config + .edges + .edge_ranking_model_path + .as_deref() + .map(|p| EdgeRankingModelPool::new(camino::Utf8Path::new(p))); + + // ── 4. Minimal persistence (tempdir, never written to disk) ─────────────── + let tmpdir = tempfile::tempdir().expect("failed to create tempdir"); + let tmpdir_utf8 = Utf8PathBuf::from_path_buf(tmpdir.path().to_path_buf()) + .expect("tempdir path is not valid UTF-8"); + let persistence = + PersistenceManager::open_or_create(tmpdir_utf8).expect("failed to open persistence"); + + // ── 5. Solver manager ───────────────────────────────────────────────────── + let solver_manager = SolverManager { + policy: engine_config.solver_config.solver_policy, + bounded_beam_config: engine_config.solver_config.bounded_beam.clone(), + }; + + // ── 6. Build pipeline plan ──────────────────────────────────────────────── + let alerts_uri = format!( + "file://{}", + std::fs::canonicalize(&alerts_path) + .unwrap_or_else(|e| panic!("cannot canonicalize {alerts_path}: {e}")) + .display() + ) + .parse::() + .expect("invalid alerts URI"); + + let plan = PipelinePlan { + stages: vec![ + PipelineStage::IngestNights, + PipelineStage::BuildSeeds, + PipelineStage::BuildEdges, + ], + persist: PersistPolicy::None, + inputs: PipelineInputs { alerts_uri }, + }; + + // ── 7. One-time setup: load alerts and build seeds ──────────────────────── + let mut runtime_state = RuntimeState::new(); + { + let mut ctx = PipelineContext { + plan: &plan, + persistence: &persistence, + runtime_state: &mut runtime_state, + engine_config: &engine_config, + edge_models: &model_pool, + solver_manager: &solver_manager, + }; + PipelineStage::IngestNights + .run(&mut ctx, &NoopHooks) + .expect("IngestNights failed"); + PipelineStage::BuildSeeds + .run(&mut ctx, &NoopHooks) + .expect("BuildSeeds failed"); + } + + let n_nights = runtime_state.seed_store.nights().count(); + let n_seeds: usize = runtime_state + .seed_store + .nights() + .filter_map(|id| runtime_state.seed_store.get(id)) + .map(|s| s.len()) + .sum(); + eprintln!("Setup: {n_nights} nights, {n_seeds} seeds total"); + + // ── 8. Benchmark: repeated BuildEdges (graph reset between iterations) ──── + let mut group = c.benchmark_group("build_edges_real"); + // Keep wall-clock time short: 10 samples (criterion minimum) with a brief + // warm-up. Each BuildEdges call can be several seconds, so this is enough + // to get a stable estimate without waiting for the default 100 samples. + // Override from the CLI if needed: + // -- --sample-size 10 --warm-up-time 1 --measurement-time 5 + // For profiling use `--profile-time 30` instead (single sample): + // samply record cargo bench --profile evaluation --bench edge_build_real -- --profile-time 30 + group + .sample_size(10) + .warm_up_time(Duration::from_secs(1)) + .measurement_time(Duration::from_secs(30)); + + group.bench_function("all_pairs", |b| { + b.iter_custom(|iters| { + let mut total = std::time::Duration::ZERO; + for _ in 0..iters { + // Reset the graph so each iteration starts from an empty DAG. + // This is nearly free (just drops a HashMap) and matches what + // fink-fat-eval does between calls in a streaming pipeline. + runtime_state.graph = AlertLinkageDAG::new(); + + let mut ctx = PipelineContext { + plan: &plan, + persistence: &persistence, + runtime_state: &mut runtime_state, + engine_config: &engine_config, + edge_models: &model_pool, + solver_manager: &solver_manager, + }; + + let t = Instant::now(); + black_box( + PipelineStage::BuildEdges + .run(&mut ctx, &NoopHooks) + .expect("BuildEdges failed"), + ); + total += t.elapsed(); + } + total + }) + }); + + group.finish(); +} + +criterion_group!(benches, bench_build_edges_real); +criterion_main!(benches); diff --git a/crates/fink-fat-engine/benches/generate_topk_edges.rs b/crates/fink-fat-engine/benches/generate_topk_edges.rs new file mode 100644 index 00000000..98ee0567 --- /dev/null +++ b/crates/fink-fat-engine/benches/generate_topk_edges.rs @@ -0,0 +1,588 @@ +use std::hint::black_box; +use std::sync::Arc; +use std::time::Duration; + +use camino::Utf8Path; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use fink_fat_engine::AlertKey; +use fink_fat_engine::graph::edge::edge_features::EdgeFeatures; +use fink_fat_engine::graph::edge::edge_prediction::EdgeRankingModelPool; +use fink_fat_engine::graph::edge::ranking_topk::rank_topk_edges_for_left_by_cost; +use fink_fat_engine::pipeline::hooks::NoopProgress; +use fink_fat_engine::seeding::SeedNode; +use fink_fat_engine::seeding::seed_spatial_index::SeedSpatialIndex; +use fink_fat_engine::seeding::store::SeedStore; +use fink_fat_engine::spacetime_bucket::healpix_binner::HealpixBinner; +use fink_fat_engine::spacetime_bucket::uniform_time_binner::UniformTimeBinner; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +use fink_fat_engine::{ + Alert, engine_config::edge_config::EdgeConfig, graph::edge::Edge, night_id::NightId, +}; +use smallvec::SmallVec; + +// ----------------------------------------------------------------------------- +// Helpers: synthetic data generation +// ----------------------------------------------------------------------------- + +/// Build a minimal [`Alert`] suitable for creating seeds in benchmarks. +/// +/// Notes +/// ----- +/// - This is **not** intended to be physically accurate: the goal is to generate +/// stable, deterministic inputs that exercise the linking code paths. +/// - RA/Dec errors are fixed to a small constant to keep the seed model stable. +/// - Flux errors are a simple proportional rule-of-thumb (never below 1). +fn make_alert( + dia_source_id: u64, + ra_rad: f64, + dec_rad: f64, + mjd_tt: f64, + band: u8, + flux: f64, +) -> Alert { + Alert { + key: AlertKey { + night_id: NightId(0), + dia_source_id: dia_source_id, + }, + ra: ra_rad, + ra_err: 1.0e-6, // ~0.2 arcsec in radians + dec: dec_rad, + dec_err: 1.0e-6, + mjd_tt, + flux, + flux_err: (0.1 * flux.abs()).max(1.0), + band, + observer_mpc_code: Arc::new("I41".to_string()), + } +} + +/// Build `num_seeds` [`SeedNode`] values for a single night using a pair-based model. +/// +/// The intent is to create a *pseudo track* (monotone time, slowly varying RA/Dec) +/// so that the spatial/time prefiltering in `score_edge_candidates` is exercised, +/// but without requiring real alert streams. +/// +/// Parameters +/// ---------- +/// rng : &mut StdRng +/// RNG used only to add small, deterministic jitter. +/// night_id : NightId +/// Night identifier assigned to all seeds. +/// seed_id_base : u64 +/// Base value for generating unique [`SeedId`] values. +/// num_seeds : usize +/// Number of seeds to generate. +/// start_mjd_tt : f64 +/// Start time (MJD TT) for the first seed. +/// seed_time_step_days : f64 +/// Time step (days) between successive seeds (controls density along time). +/// start_ra_rad : f64 +/// RA of the first seed (radians). +/// start_dec_rad : f64 +/// Dec of the first seed (radians). +/// ra_drift_rad_per_seed : f64 +/// Linear RA drift applied per seed (radians/seed). +/// dec_drift_rad_per_seed : f64 +/// Linear Dec drift applied per seed (radians/seed). +/// max_speed_rad_per_day : Option +/// Optional speed filter passed to [`SeedNode::from_pair`]. +/// +/// Returns +/// ------- +/// Vec +/// Seeds time-sorted by `plane.epoch_mid` to satisfy the invariant required +/// by `SeedNode::score_edge_candidates`. +fn make_seeds_pair_model( + rng: &mut StdRng, + night_id: NightId, + num_seeds: usize, + start_mjd_tt: f64, + seed_time_step_days: f64, + start_ra_rad: f64, + start_dec_rad: f64, + ra_drift_rad_per_seed: f64, + dec_drift_rad_per_seed: f64, + max_speed_rad_per_day: Option, +) -> Vec { + let mut seed_store: SeedStore = SeedStore::new(); + + // NOTE (bench-only): + // We leak the alerts to obtain &'static Alert references. + // This avoids lifetime issues because SeedNode::from_pair stores references. + // Criterion benchmarks build inputs once, so this is a pragmatic solution. + for seed_index in 0..num_seeds { + let time_alert_a = start_mjd_tt + (seed_index as f64) * seed_time_step_days; + let time_alert_b = time_alert_a + (seed_time_step_days * 0.5).max(1e-6); + + let ra_jitter = (rng.random::() - 0.5) * 1e-4; + let dec_jitter = (rng.random::() - 0.5) * 1e-4; + + let ra_a = start_ra_rad + (seed_index as f64) * ra_drift_rad_per_seed + ra_jitter; + let dec_a = start_dec_rad + (seed_index as f64) * dec_drift_rad_per_seed + dec_jitter; + + let ra_b = ra_a + ra_drift_rad_per_seed * 0.5; + let dec_b = dec_a + dec_drift_rad_per_seed * 0.5; + + let band_a = (seed_index % 2) as u8; + let band_b = ((seed_index + 1) % 2) as u8; + + let flux_a = 1000.0 + (rng.random::() - 0.5) * 50.0; + let flux_b = flux_a + (rng.random::() - 0.5) * 20.0; + + let dia_source_id = 1_000_000 + seed_index as u64; + + let alert_a: &'static Alert = Box::leak(Box::new(make_alert( + dia_source_id, + ra_a, + dec_a, + time_alert_a, + band_a, + flux_a, + ))); + let alert_b: &'static Alert = Box::leak(Box::new(make_alert( + dia_source_id, + ra_b, + dec_b, + time_alert_b, + band_b, + flux_b, + ))); + + let seed_node = SeedNode::from_pair( + &mut seed_store, + night_id, + alert_a, + alert_b, + max_speed_rad_per_day, + ) + .expect("SeedNode::from_pair failed (speed filter too strict?)"); + + seed_store.insert_seed(night_id, seed_node); + } + + seed_store.sort_night(night_id); + seed_store.get(&night_id).unwrap_or_default().to_vec() +} + +/// Construct the spatial + time binners used by `SeedNode::score_edge_candidates`. +/// +/// Parameters +/// ---------- +/// t0 : f64 +/// Origin (MJD TT) for uniform time binning. A good choice is the minimum +/// `epoch_mid` of the right-hand seeds so time-bin indices remain small and +/// stable in logs/diagnostics. +/// +/// Notes +/// ----- +/// - The Healpix resolution (here `10`) and the uniform bin width are key +/// knobs that strongly affect runtime by changing candidate fan-out. +/// - Keep them stable while profiling; sweep them later if needed. +fn make_binners(t0: f64) -> (HealpixBinner, UniformTimeBinner) { + let spatial_binner = HealpixBinner::new(10); + + // 5 minutes in days. Smaller bins -> more bins (more indices), but tighter + // time consistency; larger bins -> fewer indices, but potentially more + // spatial candidates per bin. + let bin_width_days = 5.0 / 1440.0; + + let time_binner = UniformTimeBinner::new(t0, bin_width_days); + (spatial_binner, time_binner) +} + +// ----------------------------------------------------------------------------- +// Benchmarks +// ----------------------------------------------------------------------------- + +/// End-to-end benchmark for [`Edge::generate_topk_edges`]. +/// +/// This measures the full pipeline cost: +/// - per-left candidate generation + scoring, +/// - per-left Top-K extraction, +/// - conversion to final `Edge` objects, +/// - optional global truncation. +fn bench_generate_topk_edges_end_to_end(c: &mut Criterion) { + let model_path = std::env::var("FINK_FAT_EDGE_ONNX") + .expect("Missing env var FINK_FAT_EDGE_ONNX (path to .onnx model)"); + let model_path = Utf8Path::new(&model_path); + + let mut group = c.benchmark_group("generate_topk_edges/e2e"); + + // Typical scaling cases. + // L = number of left seeds, R = number of right seeds, K = top-k per left. + let cases = [ + (32usize, 512usize, 8usize), + (64usize, 1024usize, 8usize), + (128usize, 2048usize, 8usize), + ]; + + let mut rng = StdRng::seed_from_u64(42); + let mut rng2 = rng.clone(); + + for (num_left_seeds, num_right_seeds, top_k_per_left) in cases { + let left_night = NightId(100); + let right_night = NightId(101); + + // Synthetic inputs: coherent pseudo-track + deterministic jitter. + let left_seeds = make_seeds_pair_model( + &mut rng, + left_night, + num_left_seeds, + 60000.0, + 2.0 / 1440.0, // 2-minute spacing + 1.0, + 0.5, + 5e-5, + 2e-5, + None, + ); + + let right_seeds = make_seeds_pair_model( + &mut rng2, + right_night, + num_right_seeds, + 60001.0, + 2.0 / 1440.0, + 1.01, + 0.51, + 5e-5, + 2e-5, + None, + ); + + // Use the minimum right epoch as time origin so bin indices stay small. + let time_origin = right_seeds + .first() + .map(|seed| seed.plane.epoch_mid) + .unwrap_or(60001.0); + + let (spatial_binner, _) = make_binners(time_origin); + + // Base configuration for the tested function. + let mut edge_config = EdgeConfig::default(); + edge_config.top_k_per_left = Some(top_k_per_left); + + group.throughput(Throughput::Elements( + (num_left_seeds * top_k_per_left) as u64, + )); + + group.bench_with_input( + BenchmarkId::new( + "no_cap", + format!("L{num_left_seeds}_R{num_right_seeds}_K{top_k_per_left}"), + ), + &(num_left_seeds, num_right_seeds, top_k_per_left), + |b, _| { + let pool = EdgeRankingModelPool::new(model_path); + + b.iter(|| { + let edges = Edge::build_edges( + black_box(&left_seeds), + black_box(&right_seeds), + black_box(&edge_config), + black_box(&spatial_binner), + black_box(time_origin), + black_box(Some(&pool)), + black_box(&NoopProgress {}), + ) + .expect("generate_topk_edges failed"); + + black_box(edges.len()) + }) + }, + ); + + // Variant with a global edge cap: adds an extra selection + partial sort step. + let edge_config_capped = edge_config.clone(); + + group.bench_with_input( + BenchmarkId::new( + "global_cap", + format!("L{num_left_seeds}_R{num_right_seeds}_K{top_k_per_left}"), + ), + &(num_left_seeds, num_right_seeds, top_k_per_left), + |b, _| { + let pool = EdgeRankingModelPool::new(model_path); + + b.iter(|| { + let edges = Edge::build_edges( + black_box(&left_seeds), + black_box(&right_seeds), + black_box(&edge_config_capped), + black_box(&spatial_binner), + black_box(time_origin), + black_box(Some(&pool)), + black_box(&NoopProgress {}), + ) + .expect("generate_topk_edges failed"); + + black_box(edges.len()) + }) + }, + ); + } + + group.finish(); +} + +/// Component benchmarks to isolate major contributors. +/// +/// This helps decide whether optimizations should target: +/// - candidate generation (bin slicing + index build + cone query), +/// - exact scoring, +/// - Top-K selection and sorting, +/// - fixed overheads per call. +fn bench_generate_topk_edges_components(c: &mut Criterion) { + let model_path = std::env::var("FINK_FAT_EDGE_ONNX") + .expect("Missing env var FINK_FAT_EDGE_ONNX (path to .onnx model)"); + let model_path = Utf8Path::new(&model_path); + + let mut group = c.benchmark_group("generate_topk_edges/components"); + + // ----------------------------------------------------------------------------- + // Component benchmarks should be *cheap enough* to sample. + // + // The previous "total" version (scoring all left seeds for a large (L,R)) + // is effectively: + // cost ~ O(L * score_edge_candidates(right)) + // which can easily become minutes per sample and makes Criterion unusable. + // + // Here we instead benchmark: + // - score_edge_candidates for a *single* left seed (per_left_1) + // - score_edge_candidates for a small batch of left seeds (per_left_16) + // and keep (L,R) at a moderate size by default. + // ----------------------------------------------------------------------------- + + // Moderate size for decomposition (keep it close to e2e cases). + let num_left_seeds: usize = 1_024; + let num_right_seeds: usize = 4_096; + let top_k_per_left: usize = 8; + + let mut rng = StdRng::seed_from_u64(7); + + let left_night = NightId(200); + let right_night = NightId(201); + + let mut rng1 = rng.clone(); + let mut rng2 = rng.clone(); + let left_seeds = make_seeds_pair_model( + &mut rng1, + left_night, + num_left_seeds, + 61000.0, + 2.0 / 1440.0, + 2.0, + 0.3, + 6e-5, + 3e-5, + None, + ); + + let right_seeds = make_seeds_pair_model( + &mut rng, + right_night, + num_right_seeds, + 61001.0, + 2.0 / 1440.0, + 2.01, + 0.31, + 6e-5, + 3e-5, + None, + ); + + // Use the minimum right epoch as time origin so time-bin indices stay small. + let time_origin = right_seeds + .first() + .map(|seed| seed.plane.epoch_mid) + .unwrap_or(61001.0); + + let (spatial_binner, time_binner) = make_binners(time_origin); + let right_index = SeedSpatialIndex::build(&right_seeds, &spatial_binner, &time_binner); + + let mut edge_config = EdgeConfig::default(); + edge_config.top_k_per_left = Some(top_k_per_left); + + // ----------------------------------------------------------------------------- + // 1) Candidate generation + scoring per single left seed. + // + // This is the primary bottleneck in most configurations (bin slicing, + // per-bin index build, cone queries, and exact scoring). + // ----------------------------------------------------------------------------- + group.bench_function("seed_edge_candidates/per_left_1", |b| { + let left_seed = &left_seeds[0]; + b.iter(|| { + let mut n: usize = 0; + for _to in + left_seed.seed_edge_candidates(black_box(&right_index), black_box(&edge_config)) + { + n += 1; + } + black_box(n) + }) + }); + + group.bench_function("seed_edge_candidates_plus_features/per_left_1", |b| { + let left_seed = &left_seeds[0]; + b.iter(|| { + let mut n: usize = 0; + for to in + left_seed.seed_edge_candidates(black_box(&right_index), black_box(&edge_config)) + { + // measure the "feature extraction" cost (CPU) + let f = EdgeFeatures::compute_features(left_seed, to); + black_box(f); + n += 1; + } + black_box(n) + }) + }); + + group.bench_function("rank_topk_edges_for_left_by_cost/per_left_1", |b| { + let left_seed = &left_seeds[0]; + + // Reusable output buffer + let mut out: SmallVec<[(&SeedNode, f64); 32]> = SmallVec::new(); + + b.iter(|| { + rank_topk_edges_for_left_by_cost( + black_box(left_seed), + black_box(&right_index), + black_box(&edge_config), + black_box(top_k_per_left), + black_box(&mut out), + ); + + black_box(out.len()) + }) + }); + + // ----------------------------------------------------------------------------- + // 1b) Candidate generation + scoring for a small batch of left seeds. + // + // This reduces measurement noise (amortizes per-call jitter) and gives a + // number you can extrapolate to e2e time as ~ (L / batch) * cost(batch). + // ----------------------------------------------------------------------------- + group.bench_function("seed_edge_candidates/per_left_16", |b| { + let left_batch: &[SeedNode] = &left_seeds[..16.min(left_seeds.len())]; + b.iter(|| { + let mut total: usize = 0; + for left_seed in left_batch { + for _to in + left_seed.seed_edge_candidates(black_box(&right_index), black_box(&edge_config)) + { + total += 1; + } + } + black_box(total) + }) + }); + + group.bench_function("seed_edge_candidates_plus_features/per_left_16", |b| { + let left_batch: &[SeedNode] = &left_seeds[..16.min(left_seeds.len())]; + b.iter(|| { + let mut total: usize = 0; + for left_seed in left_batch { + for to in + left_seed.seed_edge_candidates(black_box(&right_index), black_box(&edge_config)) + { + let f = EdgeFeatures::compute_features(left_seed, to); + black_box(f); + total += 1; + } + } + black_box(total) + }) + }); + // ----------------------------------------------------------------------------- + // 2) Top-K selection cost only (pure selection on synthetic scalar costs). + // + // This isolates the cost of: + // - select_nth_unstable + // - sorting the top-k prefix + // - truncation + // + // It does *not* include scoring or allocations done upstream. + // ----------------------------------------------------------------------------- + group.bench_function("topk_select_only", |b| { + b.iter(|| { + // NOTE: If you want to isolate selection further, pre-generate a vector + // and clone it here. This version includes RNG cost but is still useful + // as an order-of-magnitude indicator. + let mut synthetic_costs: Vec = (0..2048).map(|_| rng2.random::()).collect(); + + let k = top_k_per_left.min(synthetic_costs.len()); + if k > 0 { + let _ = synthetic_costs.select_nth_unstable_by(k - 1, |a, b| a.total_cmp(b)); + synthetic_costs[..k].sort_by(|a, b| a.total_cmp(b)); + synthetic_costs.truncate(k); + } + + black_box(synthetic_costs.len()) + }) + }); + + // ----------------------------------------------------------------------------- + // 3) End-to-end with a small left slice to highlight fixed per-call overhead. + // + // This includes map construction, per-left loop overhead, etc., but keeps the + // number of left seeds small to remain sample-friendly. + // ----------------------------------------------------------------------------- + group.bench_function("e2e_small_left", |b| { + let left_small: &[SeedNode] = &left_seeds[..32.min(left_seeds.len())]; + let pool = EdgeRankingModelPool::new(model_path); + + b.iter(|| { + let edges = Edge::build_edges( + black_box(left_small), + black_box(&right_seeds), + black_box(&edge_config), + black_box(&spatial_binner), + black_box(time_origin), + black_box(Some(&pool)), + black_box(&NoopProgress {}), + ) + .expect("generate_topk_edges failed"); + + black_box(edges.len()) + }) + }); + + group.finish(); +} + +// ----------------------------------------------------------------------------- +// Criterion configuration +// ----------------------------------------------------------------------------- + +/// Criterion configuration tuned for "expensive" end-to-end benchmarks. +/// +/// The default Criterion configuration targets ~100 samples and can become +/// impractically slow when each iteration is expensive (e.g. many scoring calls). +/// +/// We reduce: +/// - sample count, +/// - warmup time, +/// - measurement time, +/// and slightly increase tolerated noise to get actionable numbers quickly. +fn criterion_config() -> Criterion { + Criterion::default() + .with_plots() + .sample_size(10) + .warm_up_time(Duration::from_secs(1)) + .measurement_time(Duration::from_secs(4)) + .noise_threshold(0.05) +} + +criterion_group! { + name = benches; + config = criterion_config(); + targets = + bench_generate_topk_edges_end_to_end, + bench_generate_topk_edges_components +} + +criterion_main!(benches); diff --git a/crates/fink-fat-engine/src/alerts/error.rs b/crates/fink-fat-engine/src/alerts/error.rs new file mode 100644 index 00000000..bbd7cdc8 --- /dev/null +++ b/crates/fink-fat-engine/src/alerts/error.rs @@ -0,0 +1,68 @@ +//! Error types for alert insertion into the [`AlertStore`](crate::alerts::store::AlertStore). +//! +//! These errors are raised when an alert cannot be inserted because it +//! violates a uniqueness or consistency constraint enforced by the store. + +use thiserror::Error; + +use crate::{ + alerts::{AlertKey, DiaSourceId}, + night_id::NightId, +}; + +/// Error returned when inserting an alert into the [`AlertStore`](crate::alerts::store::AlertStore) fails. +/// +/// The store enforces two invariants on insertion: +/// +/// - **Night consistency**: all alerts appended to a given night must carry a +/// matching [`NightId`]. +/// - **Identifier uniqueness**: no two alerts may share the same +/// [`AlertKey`] or [`DiaSourceId`]. +/// +/// Variants +/// -------- +/// - [`NightMismatch`](Self::NightMismatch) – the alert's night does not match +/// the target night collection. +/// - [`DuplicateKey`](Self::DuplicateKey) – an alert with the same composite +/// key `(NightId, DiaSourceId)` already exists. +/// - [`DuplicateId`](Self::DuplicateId) – an alert with the same +/// `dia_source_id` already exists (globally unique constraint). +#[derive(Debug, Error)] +pub enum InsertError { + /// The alert's [`NightId`] does not match the night it is being inserted into. + /// + /// This typically indicates a bug in the ingestion or rekeying logic + /// rather than bad input data, since alerts are grouped by night before + /// insertion. + #[error( + "Night mismatch: expected {expected:?}, found {found:?} at index {index} (dia_source_id: {dia_source_id})" + )] + NightMismatch { + /// Night the store expected for this insertion batch. + expected: NightId, + /// Night actually found in the alert's key. + found: NightId, + /// The `dia_source_id` of the offending alert (for diagnostics). + dia_source_id: u64, + /// Position of the alert in the insertion batch. + index: usize, + }, + + /// An alert with the same composite key `(NightId, DiaSourceId)` is already + /// present in the store. + #[error("Duplicate alert key: {0:?}")] + DuplicateKey(AlertKey), + + /// An alert with the same `dia_source_id` is already present in the store. + /// + /// `dia_source_id` is expected to be globally unique across all nights. + #[error("Duplicate dia_source_id: {0}")] + DuplicateId(DiaSourceId), +} + +#[derive(Debug, Error)] +pub enum AlertStoreError { + /// The requested night ID was not found in the store. + #[error("Night ID not found: {0}")] + GetError(NightId), +} diff --git a/crates/fink-fat-engine/src/alerts/mod.rs b/crates/fink-fat-engine/src/alerts/mod.rs new file mode 100644 index 00000000..898881c2 --- /dev/null +++ b/crates/fink-fat-engine/src/alerts/mod.rs @@ -0,0 +1,349 @@ +//! Alert data model and storage for the Fink-FAT engine. +//! +//! This module defines the core detection record ([`Alert`]) and its supporting +//! types ([`AlertKey`], [`DiaSourceId`]) used throughout the engine pipeline: +//! ingestion, pairing, seeding, graph construction, ML ranking, and trajectory +//! reconstruction. +//! +//! Main types +//! ---------- +//! - [`Alert`] – a single photometric detection on the sky. +//! - [`AlertKey`] – composite identifier `(NightId, DiaSourceId)` uniquely +//! addressing an alert within the store. +//! - [`AlertSlice`] – extension trait on `&[Alert]` providing persistence +//! and time-origin helpers. +//! +//! Sub-modules +//! ----------- +//! - [`store`] – [`AlertStore`](crate::alerts::store::AlertStore): per-night +//! indexed collection with uniqueness constraints. +//! - [`error`] – [`InsertError`](crate::alerts::error::InsertError): errors +//! raised when an insertion violates store invariants. +//! +//! Units & conventions +//! ------------------- +//! | Field | Unit / convention | +//! |------------------|------------------------------------------------| +//! | `ra`, `dec` | **radians**, ICRS / J2000 | +//! | `ra_err`, `dec_err` | **radians**, 1σ positional uncertainty | +//! | `mjd_tt` | **MJD TT** (days), Terrestrial Time | +//! | `flux` | PSF difference flux (upstream-dependent, e.g. nJy) | +//! | `flux_err` | 1σ flux uncertainty (same units as `flux`) | +//! | `band` | `u8` photometric band code (LSST: u=0 … y=5) | +//! +//! Ordering, hashing, and determinism +//! ---------------------------------- +//! Many downstream stages rely on deterministic iteration order: +//! - bucket members are sorted by time, +//! - candidate enumeration is reproducible, +//! - benchmarks and tests do not depend on hash-map iteration order. +//! +//! To support this, [`Alert`] implements: +//! - [`Ord`] / [`PartialOrd`] – total ordering with `mjd_tt` as primary key +//! and `dia_source_id`, `band`, `ra`, `dec`, … as tie-breakers. +//! - [`Hash`], [`Eq`], [`PartialEq`] – using bitwise `to_bits()` representations +//! for all floating-point fields. +//! +//! Float equality & hashing +//! ------------------------ +//! Floating-point fields are compared and hashed using their raw bit patterns +//! (`f64::to_bits()`), **not** epsilon-based approximate equality. This: +//! - makes `Eq` / `Hash` **sound** and deterministic, +//! - allows using alerts as keys in hash sets / maps, +//! - avoids surprising behavior from floating-point rounding tolerance. +//! +//! Consequences: +//! - Values that are numerically “close” but not bit-identical are **not equal**. +//! - Distinct NaN payloads are treated as **different** values. +//! +//! See also +//! -------- +//! - [`crate::spacetime_bucket::bucket`] – bucket index relies on [`Ord`] +//! to sort alert members by time. +//! - [`crate::seeding::pairs`] – deduplicates pairs by alert pointer identity. +//! - [`crate::seeding::SeedNode`] – seeds reference alerts via +//! [`AlertKey`] members. +pub mod error; +pub mod store; + +use std::{ + cmp::Ordering, + fmt::{Display, Formatter, Result as FmtResult}, + hash::{Hash, Hasher}, + sync::Arc, +}; + +use camino::Utf8PathBuf; +use serde::{Deserialize, Serialize}; + +use crate::{ + MJDTT, Radian, + night_id::NightId, + persistence::{ + ALERT_STORE_SCHEMA_VERSION, compression::Compression, envelope::DiskEnvelope, + error::PersistenceIoError, layout::PersistenceLayout, manifest::Manifest, + }, +}; + +/// Unique identifier for a single detection in the alert stream. +/// +/// Corresponds to the LSST `diaSourceId`: a stable 64-bit integer assigned +/// by the upstream alert production system. This value is expected to be +/// **globally unique** across all nights and all surveys processed by the +/// engine. +pub type DiaSourceId = u64; + +/// Composite key uniquely identifying an [`Alert`] within the +/// [`AlertStore`](crate::alerts::store::AlertStore). +/// +/// The key pairs a [`NightId`] with a [`DiaSourceId`], enabling O(1) +/// lookups by night and efficient per-night iteration. +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct AlertKey { + /// Night this alert belongs to. + pub night_id: NightId, + /// Globally unique detection identifier (LSST `diaSourceId`). + pub dia_source_id: DiaSourceId, +} + +/// Single photometric detection in the alert stream. +/// +/// This record is intentionally compact and `Clone`-able so it can be moved +/// across threads and used as a building block for every pipeline stage: +/// ingestion, pairing, seeding, graph construction, and trajectory +/// reconstruction. +/// +/// Identification +/// -------------- +/// Each alert is addressed by its [`AlertKey`] (`key` field), which pairs +/// the observation night ([`NightId`]) with a globally unique +/// [`DiaSourceId`]. The key is assigned during ingestion and must remain +/// stable for the lifetime of the alert in the store. +/// +/// Notes +/// ----- +/// - The struct does not encode provenance metadata (visit, detector, +/// exposure, etc.) by design. Those may exist in the upstream alert +/// packet but are not required for the core linking logic. +/// - The engine frequently borrows `&Alert` references in bucket indices +/// and seeds rather than copying field values repeatedly. +/// - [`Ord`] is implemented with `mjd_tt` as primary key so that sorting +/// a slice of alerts yields chronological order (see module-level doc). +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Alert { + /// Composite key `(NightId, DiaSourceId)` uniquely identifying this + /// alert in the [`AlertStore`](crate::alerts::store::AlertStore). + pub key: AlertKey, + /// Right ascension (radians). + pub ra: Radian, + /// 1σ uncertainty on RA (radians). + pub ra_err: Radian, + /// Declination (radians). + pub dec: Radian, + /// 1σ uncertainty on Dec (radians). + pub dec_err: Radian, + /// Detection epoch (MJD TT, days). + pub mjd_tt: MJDTT, + /// PSF difference flux (units depend on upstream, e.g. nJy). + pub flux: f64, + /// 1σ uncertainty on flux (same units as `flux`). + pub flux_err: f64, + /// Photometric band code. + pub band: u8, + /// Observer reference + pub observer_mpc_code: Arc, +} + +/* ------------------------ Equality / Ordering ------------------------- */ + +/// Bitwise equality over all fields. +/// +/// Floating-point fields are compared via `to_bits()` so that the +/// implementation is consistent with [`Hash`] and [`Ord`], and `Eq` is +/// sound (no epsilon tolerance). +impl PartialEq for Alert { + fn eq(&self, other: &Self) -> bool { + self.key.dia_source_id == other.key.dia_source_id + && self.band == other.band + && self.mjd_tt.to_bits() == other.mjd_tt.to_bits() + && self.ra.to_bits() == other.ra.to_bits() + && self.dec.to_bits() == other.dec.to_bits() + && self.ra_err.to_bits() == other.ra_err.to_bits() + && self.dec_err.to_bits() == other.dec_err.to_bits() + && self.flux.to_bits() == other.flux.to_bits() + && self.flux_err.to_bits() == other.flux_err.to_bits() + } +} + +impl Eq for Alert {} + +impl PartialOrd for Alert { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Total ordering for [`Alert`]. +/// +/// Sorting order +/// ------------- +/// 1. `mjd_tt` (observation time) – primary key. +/// 2. `dia_source_id` – first tie-breaker. +/// 3. `band`, `ra`, `dec`, `ra_err`, `dec_err`, `flux`, `flux_err` – +/// subsequent tie-breakers ensuring a unique position for every +/// distinct alert. +/// +/// All floating-point comparisons use [`f64::total_cmp`] to guarantee +/// a consistent total order (including NaN positioning). +impl Ord for Alert { + fn cmp(&self, other: &Self) -> Ordering { + self.mjd_tt + .total_cmp(&other.mjd_tt) + // Deterministic tie-breakers. + .then_with(|| self.key.dia_source_id.cmp(&other.key.dia_source_id)) + .then_with(|| self.band.cmp(&other.band)) + .then_with(|| self.ra.total_cmp(&other.ra)) + .then_with(|| self.dec.total_cmp(&other.dec)) + .then_with(|| self.ra_err.total_cmp(&other.ra_err)) + .then_with(|| self.dec_err.total_cmp(&other.dec_err)) + .then_with(|| self.flux.total_cmp(&other.flux)) + .then_with(|| self.flux_err.total_cmp(&other.flux_err)) + } +} + +/* ----------------------------- Hash ---------------------------------- */ + +/// Bitwise hash over all fields. +/// +/// Float fields are hashed via `to_bits()` to stay consistent with the +/// bitwise [`PartialEq`] implementation. The field order matches `Eq`. +impl Hash for Alert { + fn hash(&self, state: &mut H) { + self.key.dia_source_id.hash(state); + self.band.hash(state); + self.mjd_tt.to_bits().hash(state); + self.ra.to_bits().hash(state); + self.dec.to_bits().hash(state); + self.ra_err.to_bits().hash(state); + self.dec_err.to_bits().hash(state); + + self.flux.to_bits().hash(state); + self.flux_err.to_bits().hash(state); + } +} + +/* ------------------------ Display ------------------------------------ */ + +impl Display for Alert { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + // Compact single-line rendering for logs and debugging output. + write!( + f, + "Alert(dia_source_id={}, ra={:.6} rad, dec={:.6} rad, mjd_tt={:.5}, \ + flux={:.3}±{:.3}, band={})", + self.key.dia_source_id, + self.ra, + self.dec, + self.mjd_tt, + self.flux, + self.flux_err, + self.band + ) + } +} + +/* ------------------------ Alert Slice trait ------------------------------------ */ + +/// Extension trait on `&[Alert]` providing persistence and time-origin helpers. +/// +/// This trait is implemented for `&[Alert]` so that any borrowed slice of +/// alerts (typically obtained from the +/// [`AlertStore`](crate::alerts::store::AlertStore)) can be saved to disk +/// or queried for its earliest epoch without owning the data. +pub trait AlertSlice { + /// Persist the alerts of a single night to disk. + /// + /// Arguments + /// --------- + /// * `layout` – Persistence directory layout. + /// * `manifest` – Current run manifest (used for timestamps). + /// * `night_id` – Night identifier for the target file path. + /// * `compression` – Binary compression algorithm to apply. + /// + /// Return + /// ------ + /// * `Ok(Utf8PathBuf)` – Absolute path of the written file. + /// * `Err(PersistenceIoError)` – On I/O or envelope serialization failure. + fn save_alerts_night( + &self, + layout: &PersistenceLayout, + manifest: &Manifest, + night_id: NightId, + compression: Compression, + ) -> Result; + + /// Return the epoch of the first alert in the slice. + /// + /// This is used as `t0` for per-night time binning in the + /// [`BuildSeeds`](crate::pipeline::stages::seed_builder) stage. + /// + /// Return + /// ------ + /// * `Some(mjd_tt)` – Epoch of the first alert. + /// * `None` – If the slice is empty. + fn get_t0(&self) -> Option; +} + +impl AlertSlice for &[Alert] { + /// Write the alerts of a single night to disk inside a + /// [`DiskEnvelope`]. + /// + /// The file is written to the path determined by + /// [`PersistenceLayout::alerts_night_path`](crate::persistence::layout::PersistenceLayout::alerts_night_path). + /// + /// Arguments + /// --------- + /// * `layout` – Directory layout for persistence artifacts. + /// * `manifest` – Current manifest (provides `created_unix_s` for the envelope). + /// * `night_id` – Night whose alerts are being persisted. + /// + /// Return + /// ------ + /// * `Ok(Utf8PathBuf)` – Absolute path of the written file. + /// * `Err(PersistenceIoError)` – On I/O or serialization failure. + fn save_alerts_night( + &self, + layout: &PersistenceLayout, + manifest: &Manifest, + night_id: NightId, + compression: Compression, + ) -> Result { + let abs_path = layout.alerts_night_path(night_id); + + // Write payload (enveloped). + let env = DiskEnvelope::new( + self.to_vec(), + ALERT_STORE_SCHEMA_VERSION, + manifest.created_unix_s, + compression, + ); + env.save_enveloped(&abs_path)?; + Ok(abs_path) + } + + /// Return the `mjd_tt` of the first alert in the slice. + /// + /// This assumes that alerts within a night are **sorted by time** + /// (guaranteed after + /// [`AlertStore::sort_each_night_and_rekey`](crate::alerts::store::AlertStore::sort_each_night_and_rekey)), + /// so the first element is the earliest observation. + /// + /// Return + /// ------ + /// * `Some(mjd_tt)` – Epoch of the first alert. + /// * `None` – The slice is empty. + fn get_t0(&self) -> Option { + self.first().map(|alert| alert.mjd_tt) + } +} diff --git a/crates/fink-fat-engine/src/alerts/store.rs b/crates/fink-fat-engine/src/alerts/store.rs new file mode 100644 index 00000000..29fff07f --- /dev/null +++ b/crates/fink-fat-engine/src/alerts/store.rs @@ -0,0 +1,1838 @@ +//! Alert store: in-memory grouping of alerts by night, plus persistence helpers. +//! +//! Overview +//! -------- +//! This module defines [`AlertStore`], a lightweight container that groups +//! [`Alert`] values by [`NightId`] +//! using an [`AHashMap`]. +//! +//! The structure is optimized for the Fink-FAT pipeline common access patterns: +//! - **batch processing per night** (seeding is typically intra-night), +//! - **contiguous iteration** within a night (`Vec`), +//! - **fast key lookup** by `(night_id, dia_source_id)` via [`AlertKey`], +//! - **O(1) reverse lookup** from [`DiaSourceId`] to vector position +//! via an internal `id_to_location` index, +//! - **cheap merging** of partial stores without cloning via `Vec::append`. +//! +//! Data model +//! ---------- +//! - The store maps each `night_id` to a `Vec`. +//! - The position within the vector is the *in-night* positional index. +//! - An [`AlertKey`] `(night_id, dia_source_id)` can be used +//! to retrieve a specific alert via the internal reverse index. +//! +//! Invariants and conventions +//! -------------------------- +//! This type is intentionally small and does not enforce strong invariants on its own, +//! but the pipeline typically relies on the following conventions: +//! +//! - **Stable reverse index:** the internal `id_to_location` map caches each alert's +//! vector position. Any reordering or removal of alerts within a `Vec` will +//! invalidate this index unless a rebuilding method (such as +//! [`AlertStore::sort_each_night_and_rekey`]) is invoked afterwards. +//! - **Identifier uniqueness:** each [`DiaSourceId`] must +//! appear at most once across all nights. Duplicate insertions are rejected by +//! [`AlertStore::insert_alert`]. +//! - **Immutability by convention:** alerts are treated as immutable once inserted, which +//! simplifies sharing across threads and avoids hard-to-debug aliasing issues. +//! - **Night completeness is contextual:** a night may contain a subset of all alerts +//! (e.g., after filtering) depending on pipeline stage. +//! +//! Performance notes +//! ----------------- +//! - `AHashMap` provides fast hashing for integer-like keys (such as `NightId`). +//! - Within a night, `Vec` offers cache-friendly iteration. +//! - Prefer [`AlertStore::merge_in_place`] to combine stores: +//! it **moves** alerts without cloning and uses `Vec::append`. +//! - If you need deterministic iteration order across nights, sort nights at call site; +//! hash map iteration order is not stable. +//! +//! Persistence integration +//! ----------------------- +//! The store includes a convenience helper [`AlertStore::save_alert_night`] to persist +//! the alerts for a given night using the project persistence layer: +//! - `AlertSlice::save_alerts_night(layout, manifest, night_id)` is used to write data +//! and update the [`Manifest`]. +//! +//! This helper is intentionally conservative: +//! - it only writes the requested night, +//! - it updates/creates the manifest entry for that night, +//! - it does **not** remove stale manifest entries for nights not present in the store. +//! +//! Limitations +//! ----------- +//! - This module does not expose deletion APIs or compaction logic. +//! - Thread-safety is handled at higher layers (e.g., by partitioning nights or using +//! immutable sharing patterns). +//! - The store does not validate consistency between `AlertKey.dia_source_id` and the +//! reverse index on bulk operations. Use [`AlertStore::sort_each_night_and_rekey`] +//! or [`AlertStore::merge_in_place`] (which rebuild the index internally) after any +//! external mutation of the per-night vectors. + +use std::collections::hash_map::Entry; + +use ahash::AHashMap; +use camino::Utf8PathBuf; + +use crate::{ + MJDTT, + alerts::{ + Alert, AlertKey, AlertSlice, DiaSourceId, + error::{AlertStoreError, InsertError}, + }, + night_id::{NightId, PairingMode}, + persistence::{ + compression::Compression, error::PersistenceIoError, layout::PersistenceLayout, + manifest::Manifest, + }, +}; + +/// In-memory store of alerts grouped by night. +/// +/// This is a thin wrapper around an `AHashMap>` +/// providing common operations used by the ingestion, seeding, and persistence +/// layers of the pipeline. +/// +/// Key properties +/// -------------- +/// - **Grouping by night:** each key is a [`NightId`]. +/// - **Contiguous storage:** alerts for a night are in a `Vec` for +/// cache-friendly iteration. +/// - **Key-based addressing:** [`AlertKey`] pairs a +/// `NightId` with a [`DiaSourceId`]; the internal +/// reverse index resolves the `dia_source_id` to the vector position in O(1). +/// +/// See the module-level documentation for invariants and performance notes. +#[derive(Debug, Clone)] +pub struct AlertStore { + /// Alerts grouped by night (cache-friendly iteration) + alerts_by_night: AHashMap>, + + /// Reverse index: dia_source_id → (night, index in vec) + /// + /// Built during insertion/load to enable O(1) stable lookups. + /// This index is **not persisted** (rebuilt on load). + pub(self) id_to_location: AHashMap, +} + +impl Default for AlertStore { + fn default() -> Self { + Self::new() + } +} + +impl AlertStore { + /// Create a new empty `AlertStore`. + /// + /// Behavior + /// -------- + /// - Allocates an empty internal [`AHashMap`]. + /// - No nights are present initially. + /// + /// Complexity + /// ---------- + /// - Time: `O(1)` + /// - Space: `O(1)` (empty map) + pub fn new() -> Self { + Self { + alerts_by_night: AHashMap::new(), + id_to_location: AHashMap::new(), + } + } + + /// Create an `AlertStore` from a pre-existing map of night IDs to alert vectors. + /// + /// Arguments + /// --------- + /// * `map` – Map from [`NightId`] to `Vec`. + /// + /// Return + /// ------ + /// A new `AlertStore` with its reverse index (`id_to_location`) built from the + /// provided map. + /// + /// Notes + /// ----- + /// - No validation is performed on alert ordering or `dia_source_id` uniqueness. + /// - The reverse index is built during construction, mapping each + /// `dia_source_id` to its `(night_id, vec_index)` pair. + pub fn from_map(map: AHashMap>) -> Self { + // Build reverse index for stable lookups + let mut id_to_location = AHashMap::new(); + for (night_id, alerts) in &map { + for (idx, alert) in alerts.iter().enumerate() { + id_to_location.insert(alert.key.dia_source_id, (*night_id, idx)); + } + } + + Self { + alerts_by_night: map, + id_to_location, + } + } + + /// Insert a single alert into the store, placing it in the appropriate night's vector. + /// + /// Arguments + /// --------- + /// * `alert` – The [`Alert`] to insert. Its `key.night_id` + /// determines the target night. + /// + /// Return + /// ------ + /// * `Ok(())` – The alert was successfully inserted and the reverse index updated. + /// * `Err(InsertError::DuplicateId)` – An alert with the same `dia_source_id` + /// already exists in the store. + pub fn insert_alert(&mut self, alert: Alert) -> Result<(), InsertError> { + // Check for duplicate dia_source_id + let night_id = alert.key.night_id; + let dia_source_id = alert.key.dia_source_id; + if self.id_to_location.contains_key(&dia_source_id) { + return Err(InsertError::DuplicateId(dia_source_id)); + } + + // Insert into the appropriate night's vector + let alerts_for_night = self.alerts_by_night.entry(night_id).or_default(); + let idx_in_night = alerts_for_night.len(); + alerts_for_night.push(alert); + + // Update reverse index + self.id_to_location + .insert(dia_source_id, (night_id, idx_in_night)); + + Ok(()) + } + + /// Test if the store is empty (contains no nights). + /// + /// Return + /// ------ + /// `true` if there are no nights in the store, `false` otherwise. + pub fn is_empty(&self) -> bool { + self.alerts_by_night.is_empty() + } + + /// Get a list of night IDs currently present in the store. + /// + /// Return + /// ------ + /// An iterator yielding `NightId` values corresponding to the keys in the internal map. + /// + /// Notes + /// ----- + /// - The order is not guaranteed to be stable or sorted, as it depends on the + /// internal state of the hash map. + /// - If the store is empty, returns an empty vector. + #[inline] + pub fn nights(&self) -> impl Iterator { + self.alerts_by_night.keys() + } + + /// Get a sorted list of night IDs currently present in the store. + /// + /// Return + /// ------ + /// A `Vec` containing all night IDs in the store, sorted in ascending order. + /// + /// Notes + /// ----- + /// - If the store is empty, returns an empty vector. + /// - The sorting is done at call time; the internal map does not maintain order. + #[inline] + pub fn nights_sorted(&self) -> Vec { + let mut v: Vec = self.nights().copied().collect(); + v.sort(); + v + } + + /// Get the latest night ID present in the store, if any. + /// + /// Return + /// ------ + /// - `Some(NightId)` corresponding to the maximum night ID in the store. + /// - `None` if the store is empty. + /// + /// Notes + /// ----- + /// - This is a convenience method that relies on `nights_sorted` to find the maximum night ID. + #[inline] + pub fn last_night(&self) -> Option { + self.nights_sorted().last().copied() + } + + /// Get the MJD TT of the first alert in a given night, if it exists. + /// + /// Arguments + /// --------- + /// * `night_id` – Night identifier to query. + /// + /// Return + /// ------ + /// - `Some(MJDTT)` if the night exists and contains at least one alert, + /// where the returned value is the `mjd_tt` of the first alert in the vector. + /// - `None` if the night does not exist or contains no alerts. + /// + /// Notes + /// ----- + /// - Return the first alert's `mjd_tt` of the night only if the alerts are sorted by time + /// (e.g., after calling `sort_each_night_and_rekey`). + /// - Should be the case if the alerts have been + /// ingested using the [`crate::pipeline::stages::PipelineStage::IngestNights`] stage, + /// which calls `sort_each_night_and_rekey` after loading. + pub fn night_t0(&self, night_id: &NightId) -> Option { + self.alerts_by_night + .get(night_id) + .and_then(|alerts| alerts.first().map(|a| a.mjd_tt)) + } + + /// Insert a vector of alerts for a given night. + /// + /// Behavior + /// -------- + /// - Replaces any existing vector stored under `night_id`. + /// - Ownership of `alerts` is moved into the store. + /// + /// Arguments + /// --------- + /// * `night_id` – Night identifier for the inserted alerts. + /// * `alerts` – Contiguous alerts for that night. + /// + /// Notes + /// ----- + /// - This is an overwrite operation. If you want to extend an existing night, + /// use [`get_or_init`](Self::get_or_init) / [`get_or_init_with_capacity`](Self::get_or_init_with_capacity) + /// or [`merge_in_place`](Self::merge_in_place). + pub fn insert(&mut self, night_id: NightId, alerts: Vec) { + // Update reverse index + for (idx, alert) in alerts.iter().enumerate() { + self.id_to_location + .insert(alert.key.dia_source_id, (night_id, idx)); + } + self.alerts_by_night.insert(night_id, alerts); + } + + /// Get the vector of alerts for a given night, if it exists. + /// + /// Arguments + /// --------- + /// * `night_id` – Night identifier to query. + /// + /// Return + /// ------ + /// - `Some(&Vec)` if the night exists. + /// - `None` otherwise. + /// + /// Notes + /// ----- + /// - The returned vector should be treated as immutable by convention to keep + /// the reverse index (`id_to_location`) consistent with vector positions. + pub fn get(&self, night_id: &NightId) -> Option<&Vec> { + self.alerts_by_night.get(night_id) + } + + /// Get a mutable reference to the vector of alerts for a given night, + /// creating an empty vector if it does not exist. + /// + /// Behavior + /// -------- + /// - If `night_id` is present, returns a mutable reference to its vector. + /// - Otherwise, inserts `Vec::new()` and returns a mutable reference to it. + /// + /// Arguments + /// --------- + /// * `night_id` – Night identifier. + /// + /// Return + /// ------ + /// Mutable reference to the `Vec` for the given night. + /// + /// Notes + /// ----- + /// - Mutating the vector (especially reordering/removals) may invalidate the + /// internal `id_to_location` reverse index. Call + /// [`sort_each_night_and_rekey`](Self::sort_each_night_and_rekey) or + /// [`merge_in_place`](Self::merge_in_place) after bulk mutations to + /// rebuild the index. + pub fn get_or_init(&mut self, night_id: NightId) -> &mut Vec { + self.alerts_by_night.entry(night_id).or_default() + } + + /// Get a mutable reference to the vector of alerts for a given night, + /// creating a vector with the specified capacity if it does not exist. + /// + /// This is a capacity-optimized variant of [`get_or_init`](Self::get_or_init), + /// useful when the expected number of alerts for a night is known in advance. + /// + /// Arguments + /// --------- + /// * `night_id` – Night identifier. + /// * `capacity` – Initial capacity used when creating the vector. + /// + /// Return + /// ------ + /// Mutable reference to the `Vec` for the given night. + /// + /// Notes + /// ----- + /// - The capacity hint can reduce reallocations when pushing alerts. + /// - Same mutation caveats as [`get_or_init`](Self::get_or_init): + /// modifications to the returned vector may invalidate the reverse index. + pub fn get_or_init_with_capacity( + &mut self, + night_id: NightId, + capacity: usize, + ) -> &mut Vec { + self.alerts_by_night + .entry(night_id) + .or_insert_with(|| Vec::with_capacity(capacity)) + } + + /// Get an alert by its composite key (night ID + `dia_source_id`). + /// + /// This is the canonical constant-time lookup for pipeline components that + /// carry compact references via [`AlertKey`]. + /// + /// Arguments + /// --------- + /// * `key` – Composite alert identifier: `(night_id, dia_source_id)`. + /// + /// Return + /// ------ + /// - `Some(&Alert)` if the night exists and the `dia_source_id` is found + /// in the reverse index with a valid vector position. + /// - `None` otherwise. + /// + /// Notes + /// ----- + /// - Internally resolves `dia_source_id` to a vector index via `id_to_location`. + /// - If alerts have been reordered or removed without rebuilding the index, + /// the returned reference may be incorrect or `None`. + pub fn get_by_key(&self, key: AlertKey) -> Option<&Alert> { + let vec = self.alerts_by_night.get(&key.night_id)?; + vec.get(self.id_to_location.get(&key.dia_source_id)?.1) + } + + /// Get an alert by its `dia_source_id` using the reverse index. + /// + /// Arguments + /// --------- + /// * `dia_source_id` – Unique identifier for the alert source. + /// + /// Return + /// ------ + /// - `Some(&Alert)` if an alert with the given `dia_source_id` exists in the store. + /// - `None` if the `dia_source_id` is not present in the reverse index or the + /// referenced night/position is invalid. + /// + /// Notes + /// ----- + /// - This method provides O(1) lookup by `dia_source_id` using the internal + /// `id_to_location` index. + pub fn get_by_id(&self, dia_source_id: DiaSourceId) -> Option<&Alert> { + let (night_id, idx_in_night) = self.id_to_location.get(&dia_source_id)?; + let vec = self.alerts_by_night.get(night_id)?; + vec.get(*idx_in_night) + } + + /// Get an iterator over all alerts in the store, across all nights. + /// + /// Return + /// ------ + /// An iterator yielding `&Alert` in the hash map’s internal iteration order. + /// + /// Notes + /// ----- + /// - The iteration order over nights is **not deterministic** because it depends + /// on hash map internal state. + /// - Within each night, order matches the `Vec` order. + pub fn iter(&self) -> impl Iterator { + self.alerts_by_night.values().flatten() + } + + /// Get an iterator over all alerts for a specific night, if it exists. + /// + /// Arguments + /// --------- + /// * `night_id` – Night identifier to iterate over. + /// + /// Return + /// ------ + /// - `Some(iterator)` if the night exists. + /// - `None` otherwise. + /// + /// Notes + /// ----- + /// - The iterator yields alerts in the stored vector order. + pub fn iter_night(&self, night_id: &NightId) -> Option> { + self.alerts_by_night + .get(night_id) + .map(|alerts| alerts.iter()) + } + + /// Iterate over `(night_id, alerts)` pairs. + /// + /// Return + /// ------ + /// An iterator over references to the internal map entries. + /// + /// Notes + /// ----- + /// - The order is hash-dependent and not deterministic. + /// - This is useful for bulk operations such as persistence or aggregation. + pub fn as_map_iter(&self) -> impl Iterator)> { + self.alerts_by_night.iter() + } + + /// Return alert slices for the given list of night IDs. + /// + /// Arguments + /// --------- + /// * `night_ids` – Ordered list of night IDs to retrieve. The iterator + /// preserves this order. + /// + /// Return + /// ------ + /// * `Ok(iterator)` – An iterator yielding `&[Alert]` for each requested night, + /// in the same order as `night_ids`. + /// * `Err(AlertStoreError::GetError(night_id))` – The first night ID in `night_ids` + /// that is not present in the store. + /// + /// Notes + /// ----- + /// - All night IDs are validated before the iterator is returned. + /// If any night is missing the function fails immediately without + /// consuming any item. + pub fn night_iter( + &self, + night_ids: &Vec, + ) -> Result + use<'_>, AlertStoreError> { + let mut slices = Vec::with_capacity(night_ids.len()); + for &night in night_ids { + match self.alerts_by_night.get(&night) { + Some(alerts) => slices.push(alerts.as_slice()), + None => return Err(AlertStoreError::GetError(night)), + } + } + Ok(slices.into_iter()) + } + + /// Return the sorted list of night IDs covered by `night_window`. + /// + /// Returns only the identifiers, without borrowing the alert slices. + /// Useful for progress reporting and counter initialisation before the + /// actual iteration. + /// + /// Behavior + /// -------- + /// - **`SingleNight` mode**: returns `[anchor]` if the anchor is present, + /// otherwise `[]`. + /// - **`BatchRange` mode**: returns all nights within `[start, end]` that + /// exist in the store, in sorted ascending order. + pub fn night_window_nights(&self, night_window: PairingMode) -> Vec { + match night_window { + PairingMode::SingleNight { anchor, .. } => { + if self.alerts_by_night.contains_key(&anchor) { + vec![anchor] + } else { + vec![] + } + } + _ => { + let available = self.nights_sorted(); + night_window.filter_nights(&available).unwrap_or_default() + } + } + } + + /// Get the internal map size (number of nights present). + /// + /// Return + /// ------ + /// Number of distinct `NightId` keys currently stored. + pub fn n_nights(&self) -> usize { + self.alerts_by_night.len() + } + + /// Get the total number of alerts across all nights. + /// + /// Return + /// ------ + /// Total count of `Alert` values stored across all nights. + pub fn n_alerts(&self) -> usize { + self.alerts_by_night.values().map(|v| v.len()).sum() + } + + /// Merges another `AlertStore` into this one in-place. + /// + /// All alerts from `other` are moved into `self`, grouped by night. + /// After merging, the internal `id_to_location` index is fully rebuilt to reflect + /// the new alert positions. + /// + /// Complexity + /// ---------- + /// - Time: O(N_self + N_other) where N = total number of alerts + /// - Space: O(1) additional (reuses existing allocations where possible) + /// + /// Arguments + /// --------- + /// * `other` – The `AlertStore` to merge into this one (consumed). + pub fn merge_in_place(&mut self, mut other: AlertStore) { + for (night_id, mut alerts) in other.alerts_by_night.drain() { + match self.alerts_by_night.entry(night_id) { + Entry::Occupied(mut e) => { + e.get_mut().append(&mut alerts); + } + Entry::Vacant(e) => { + e.insert(alerts); + } + } + } + + // Rebuild the id_to_location index to maintain O(1) lookup invariant + self.rebuild_index(); + } + + /// Rebuilds the `id_to_location` reverse index from scratch. + /// + /// This method is called internally after operations that modify the alert + /// storage structure (e.g., merging, bulk insertions). + /// + /// # Complexity + /// Time: O(N) where N = total number of alerts across all nights. + fn rebuild_index(&mut self) { + self.id_to_location.clear(); + + for (night_id, alerts) in &self.alerts_by_night { + for (idx, alert) in alerts.iter().enumerate() { + self.id_to_location + .insert(alert.key.dia_source_id, (*night_id, idx)); + } + } + } + + /// Sort alerts within each night by their `mjd_tt` and rebuild the reverse index. + /// + /// This is a utility function that should be called after inserting or merging + /// alerts to ensure that alerts within each night are ordered by observation + /// time and that the internal `id_to_location` index reflects the new + /// vector positions. + /// + /// Behavior + /// -------- + /// For each night in the store: + /// 1. The vector of alerts is sorted in-place using + /// [`sort_unstable`](slice::sort_unstable) (primary key: `mjd_tt`, + /// tie-breakers follow the [`Ord`] implementation on [`Alert`]). + /// 2. After all nights are sorted, the reverse index is fully rebuilt + /// to re-map every `dia_source_id` to its new vector position. + /// + /// This operation modifies the internal state of the store. Any vector + /// index previously obtained from `id_to_location` is invalid until the + /// rebuild completes. It should typically be called once after all + /// insertions/merges are complete. + /// + /// Complexity + /// ---------- + /// Let `N` be the number of nights and `K` the total number of alerts across all nights. + /// - Time: `O(K log K)` in the worst case (if all alerts are in one night), + /// but typically `O(N * M log M)` where `M` is the average number of alerts per night, + /// plus `O(K)` for the index rebuild. + /// - Space: `O(1)` extra (sort is in-place, index rebuild reuses the existing map). + /// + /// Notes + /// ----- + /// - Sorting is done by `mjd_tt` to ensure temporal ordering within each night, + /// which is a common convention for alert processing. + /// - After this operation, the `id_to_location` reverse index is consistent + /// with the sorted vectors, which is important for downstream components + /// that rely on [`get_by_key`](Self::get_by_key) or + /// [`get_by_id`](Self::get_by_id). + pub fn sort_each_night_and_rekey(&mut self) { + for (_, v) in self.alerts_by_night.iter_mut() { + v.sort_unstable(); + } + self.rebuild_index(); + } + + /// Persist the alerts of a single night currently present in the store. + /// + /// This is a convenience function that: + /// - retrieves the alerts for `night_id`, + /// - writes the night payload using [`AlertSlice::save_alerts_night`], + /// - updates the manifest entry via the persistence layer. + /// + /// Arguments + /// --------- + /// * `night_id` – Night to persist. + /// * `layout` – Persistence layout describing the root paths and naming scheme. + /// * `manifest` – Manifest to update with the written night payload metadata. + /// + /// Return + /// ------ + /// * `Ok(Utf8PathBuf)` – Path of the written night file (as returned by the persistence layer). + /// * `Err(PersistenceIoError)` – If: + /// - `night_id` is not present in the store, + /// - or the underlying I/O / serialization fails. + /// + /// Notes + /// ----- + /// - This does **not** remove old manifest entries. If a "sync" semantics is required + /// (drop nights not present in the store), implement it at the call site. + /// - This persists **only one night**. Bulk persistence can be implemented by iterating + /// over [`as_map_iter`](Self::as_map_iter) and calling this function per night. + pub fn save_alert_night( + &self, + night_id: NightId, + layout: &PersistenceLayout, + manifest: &mut Manifest, + compression: Compression, + ) -> Result { + let alerts = self.alerts_by_night.get(&night_id).ok_or_else(|| { + PersistenceIoError::Other(format!("No alerts for night_id {}", night_id)) + })?; + + let path = alerts + .as_slice() + .save_alerts_night(layout, manifest, night_id, compression)?; + + Ok(path) + } +} + +#[cfg(test)] +mod alert_store_tests { + use super::*; + use crate::alerts::error::InsertError; + + // ------------------------------------------------------------------------- + // Helper functions + // ------------------------------------------------------------------------- + + fn nid(v: u32) -> NightId { + NightId(v) + } + + /// Create a mock alert with specified fields for testing. + fn mock_alert(dia_source_id: DiaSourceId, night_id: NightId, mjd: f64) -> Alert { + Alert { + key: AlertKey { + night_id, + dia_source_id, + }, + ra: 0.0, + dec: 0.0, + ra_err: 0.001, + dec_err: 0.001, + mjd_tt: mjd, + flux: 100.0, + flux_err: 10.0, + band: 0, + ..Default::default() + } + } + + /// Create a store with predefined alerts for testing. + fn make_test_store() -> AlertStore { + let mut store = AlertStore::new(); + + // Night 100: 3 alerts + let _ = store.insert_alert(mock_alert(1001, nid(100), 59000.0)); + let _ = store.insert_alert(mock_alert(1002, nid(100), 59000.5)); + let _ = store.insert_alert(mock_alert(1003, nid(100), 59001.0)); + + // Night 101: 2 alerts + let _ = store.insert_alert(mock_alert(2001, nid(101), 59001.5)); + let _ = store.insert_alert(mock_alert(2002, nid(101), 59002.0)); + + store + } + + // ------------------------------------------------------------------------- + // Basic construction and query tests + // ------------------------------------------------------------------------- + + #[test] + fn new_store_is_empty() { + let store = AlertStore::new(); + assert_eq!(store.n_alerts(), 0); + assert!(store.is_empty()); + assert_eq!(store.nights().count(), 0); + } + + #[test] + fn from_map_builds_index() { + let mut map: AHashMap> = AHashMap::new(); + map.insert( + nid(100), + vec![ + mock_alert(1001, nid(100), 59000.0), + mock_alert(1002, nid(100), 59000.5), + ], + ); + + let store = AlertStore::from_map(map); + + assert_eq!(store.n_alerts(), 2); + assert!(store.get_by_id(1001).is_some()); + assert!(store.get_by_id(1002).is_some()); + assert!(store.get_by_id(9999).is_none()); + } + + #[test] + fn nights_sorted_returns_ordered_list() { + let store = make_test_store(); + let nights = store.nights_sorted(); + + assert_eq!(nights, vec![nid(100), nid(101)]); + } + + #[test] + fn last_night_returns_maximum() { + let store = make_test_store(); + assert_eq!(store.last_night(), Some(nid(101))); + } + + #[test] + fn last_night_empty_store() { + let store = AlertStore::new(); + assert_eq!(store.last_night(), None); + } + + // ------------------------------------------------------------------------- + // Index lookup tests (get_by_id) + // ------------------------------------------------------------------------- + + #[test] + fn get_by_id_finds_existing_alert() { + let store = make_test_store(); + + let alert = store.get_by_id(1001).unwrap(); + assert_eq!(alert.key.dia_source_id, 1001); + assert_eq!(alert.key.night_id, nid(100)); + assert_eq!(alert.mjd_tt, 59000.0); + } + + #[test] + fn get_by_id_returns_none_for_missing_id() { + let store = make_test_store(); + assert!(store.get_by_id(9999).is_none()); + } + + #[test] + fn get_by_id_finds_alerts_across_nights() { + let store = make_test_store(); + + // Alert from night 100 + assert!(store.get_by_id(1001).is_some()); + + // Alert from night 101 + assert!(store.get_by_id(2001).is_some()); + } + + // ------------------------------------------------------------------------- + // Insert and index consistency tests + // ------------------------------------------------------------------------- + + #[test] + fn insert_alert_updates_index() { + let mut store = AlertStore::new(); + + let _ = store.insert_alert(mock_alert(1001, nid(100), 59000.0)); + + // Check the alert is findable by ID + let alert = store.get_by_id(1001).unwrap(); + assert_eq!(alert.key.dia_source_id, 1001); + + // Check the alert is in the correct night vector + let night_alerts = store.get(&nid(100)).unwrap(); + assert_eq!(night_alerts.len(), 1); + assert_eq!(night_alerts[0].key.dia_source_id, 1001); + } + + #[test] + fn insert_alert_duplicate_key_fails() { + let mut store = AlertStore::new(); + + let _ = store.insert_alert(mock_alert(1001, nid(100), 59000.0)); + + // Try to insert alert with same dia_source_id + let result = store.insert_alert(mock_alert(1001, nid(100), 59000.5)); + + assert!(result.is_err()); + match result { + Err(InsertError::DuplicateId(id)) => { + assert_eq!(id, 1001); + } + _ => panic!("Expected DuplicateId error"), + } + } + + #[test] + fn insert_multiple_alerts_same_night() { + let mut store = AlertStore::new(); + + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59000.5)) + .unwrap(); + store + .insert_alert(mock_alert(1003, nid(100), 59001.0)) + .unwrap(); + + assert_eq!(store.n_alerts(), 3); + + // All alerts should be findable by ID + assert!(store.get_by_id(1001).is_some()); + assert!(store.get_by_id(1002).is_some()); + assert!(store.get_by_id(1003).is_some()); + + // All should be in the same night vector + let night_alerts = store.get(&nid(100)).unwrap(); + assert_eq!(night_alerts.len(), 3); + } + + #[test] + fn insert_multiple_alerts_different_nights() { + let mut store = AlertStore::new(); + + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store + .insert_alert(mock_alert(2001, nid(101), 59001.0)) + .unwrap(); + store + .insert_alert(mock_alert(3001, nid(102), 59002.0)) + .unwrap(); + + assert_eq!(store.n_alerts(), 3); + assert_eq!(store.nights().count(), 3); + + // Each alert should be in its correct night + assert_eq!(store.get(&nid(100)).unwrap().len(), 1); + assert_eq!(store.get(&nid(101)).unwrap().len(), 1); + assert_eq!(store.get(&nid(102)).unwrap().len(), 1); + } + + // ------------------------------------------------------------------------- + // Batch insert tests + // ------------------------------------------------------------------------- + + #[test] + fn insert_night_vec_success() { + let mut store = AlertStore::new(); + + let alerts = vec![ + mock_alert(1001, nid(100), 59000.0), + mock_alert(1002, nid(100), 59000.5), + mock_alert(1003, nid(100), 59001.0), + ]; + + store.insert(nid(100), alerts); + + assert_eq!(store.n_alerts(), 3); + assert!(store.get_by_id(1001).is_some()); + assert!(store.get_by_id(1002).is_some()); + assert!(store.get_by_id(1003).is_some()); + } + + // ------------------------------------------------------------------------- + // Merge tests + // ------------------------------------------------------------------------- + + #[test] + fn merge_in_place_combines_stores() { + let mut store1 = AlertStore::new(); + store1 + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store1 + .insert_alert(mock_alert(1002, nid(100), 59000.5)) + .unwrap(); + + let mut store2 = AlertStore::new(); + store2 + .insert_alert(mock_alert(2001, nid(101), 59001.0)) + .unwrap(); + store2 + .insert_alert(mock_alert(2002, nid(101), 59001.5)) + .unwrap(); + + store1.merge_in_place(store2); + + assert_eq!(store1.n_alerts(), 4); + assert_eq!(store1.n_nights(), 2); + + // All alerts should be findable + assert!(store1.get_by_id(1001).is_some()); + assert!(store1.get_by_id(1002).is_some()); + assert!(store1.get_by_id(2001).is_some()); + assert!(store1.get_by_id(2002).is_some()); + } + + #[test] + fn merge_in_place_same_night() { + let mut store1 = AlertStore::new(); + store1 + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + let mut store2 = AlertStore::new(); + store2 + .insert_alert(mock_alert(1002, nid(100), 59000.5)) + .unwrap(); + + store1.merge_in_place(store2); + + assert_eq!(store1.n_alerts(), 2); + assert_eq!(store1.n_nights(), 1); + + let night_alerts = store1.get(&nid(100)).unwrap(); + assert_eq!(night_alerts.len(), 2); + } + + #[test] + fn merge_in_place_rebuilds_index() { + let mut store1 = AlertStore::new(); + store1 + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + let mut store2 = AlertStore::new(); + store2 + .insert_alert(mock_alert(2001, nid(101), 59001.0)) + .unwrap(); + + store1.merge_in_place(store2); + + // Index should be updated for both stores' alerts + let alert1 = store1.get_by_id(1001).unwrap(); + let alert2 = store1.get_by_id(2001).unwrap(); + + assert_eq!(alert1.key.night_id, nid(100)); + assert_eq!(alert2.key.night_id, nid(101)); + } + + #[test] + fn merge_empty_stores() { + let mut store1 = AlertStore::new(); + let store2 = AlertStore::new(); + + store1.merge_in_place(store2); + + assert!(store1.is_empty()); + } + + #[test] + fn merge_into_empty_store() { + let mut store1 = AlertStore::new(); + + let mut store2 = AlertStore::new(); + store2 + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + store1.merge_in_place(store2); + + assert_eq!(store1.n_alerts(), 1); + assert!(store1.get_by_id(1001).is_some()); + } + + // ------------------------------------------------------------------------- + // Sort and rekey tests + // ------------------------------------------------------------------------- + + #[test] + fn sort_and_rekey_orders_by_mjd() { + let mut store = AlertStore::new(); + + // Insert alerts in non-chronological order + store + .insert_alert(mock_alert(1003, nid(100), 59002.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59001.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + let night_alerts = store.get(&nid(100)).unwrap(); + + // Check chronological order + assert_eq!(night_alerts[0].mjd_tt, 59000.0); + assert_eq!(night_alerts[1].mjd_tt, 59001.0); + assert_eq!(night_alerts[2].mjd_tt, 59002.0); + } + + #[test] + fn sort_and_rekey_preserves_index_lookup() { + let mut store = AlertStore::new(); + + // Insert alerts in non-chronological order + store + .insert_alert(mock_alert(1003, nid(100), 59002.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59001.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + // All alerts should still be findable by ID + let alert1 = store.get_by_id(1001).unwrap(); + let alert2 = store.get_by_id(1002).unwrap(); + let alert3 = store.get_by_id(1003).unwrap(); + + assert_eq!(alert1.mjd_tt, 59000.0); + assert_eq!(alert2.mjd_tt, 59001.0); + assert_eq!(alert3.mjd_tt, 59002.0); + } + + #[test] + fn sort_and_rekey_multiple_nights() { + let mut store = AlertStore::new(); + + // Night 100 + store + .insert_alert(mock_alert(1003, nid(100), 59002.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + // Night 101 + store + .insert_alert(mock_alert(2002, nid(101), 59003.5)) + .unwrap(); + store + .insert_alert(mock_alert(2001, nid(101), 59003.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + // Check night 100 + let alerts100 = store.get(&nid(100)).unwrap(); + assert_eq!(alerts100[0].mjd_tt, 59000.0); + assert_eq!(alerts100[1].mjd_tt, 59002.0); + + // Check night 101 + let alerts101 = store.get(&nid(101)).unwrap(); + assert_eq!(alerts101[0].mjd_tt, 59003.0); + assert_eq!(alerts101[1].mjd_tt, 59003.5); + } + + // ------------------------------------------------------------------------- + // Index consistency after modifications + // ------------------------------------------------------------------------- + + #[test] + fn index_consistent_after_multiple_operations() { + let mut store = AlertStore::new(); + + // Insert some alerts + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59000.5)) + .unwrap(); + + // Merge with another store + let mut store2 = AlertStore::new(); + store2 + .insert_alert(mock_alert(2001, nid(101), 59001.0)) + .unwrap(); + store.merge_in_place(store2); + + // Sort and rekey + store.sort_each_night_and_rekey(); + + // All alerts should still be findable + assert!(store.get_by_id(1001).is_some()); + assert!(store.get_by_id(1002).is_some()); + assert!(store.get_by_id(2001).is_some()); + + // Verify correct night assignment + assert_eq!(store.get_by_id(1001).unwrap().key.night_id, nid(100)); + assert_eq!(store.get_by_id(2001).unwrap().key.night_id, nid(101)); + } + + // ------------------------------------------------------------------------- + // Property-based tests for index consistency + // ------------------------------------------------------------------------- + + #[cfg(test)] + mod proptest_index { + use super::*; + use proptest::prelude::*; + + /// Generate a valid alert with random but bounded fields + fn arb_alert() -> impl Strategy { + ( + any::().prop_filter("non-zero dia_source_id", |&id| id > 0), + 0u32..1000, + 59000.0..60000.0f64, + ) + .prop_map(|(id, night, mjd)| mock_alert(id, nid(night), mjd)) + } + + proptest! { + /// Inserting alerts maintains index consistency + #[test] + fn prop_insert_maintains_index( + alerts in prop::collection::vec(arb_alert(), 1..100) + ) { + let mut store = AlertStore::new(); + let mut inserted_ids = Vec::new(); + + for alert in alerts { + let id = alert.key.dia_source_id; + + // Try to insert (may fail on duplicate) + if store.insert_alert(alert).is_ok() { + inserted_ids.push(id); + } + } + + // All successfully inserted alerts should be findable + for id in inserted_ids { + prop_assert!(store.get_by_id(id).is_some()); + } + } + + /// Merging stores maintains index consistency + #[test] + fn prop_merge_maintains_index( + alerts1 in prop::collection::vec(arb_alert(), 0..50), + alerts2 in prop::collection::vec(arb_alert(), 0..50) + ) { + let mut store1 = AlertStore::new(); + let mut store2 = AlertStore::new(); + + let mut all_ids = Vec::new(); + + // Insert into store1 + for alert in alerts1 { + let id = alert.key.dia_source_id; + if store1.insert_alert(alert).is_ok() { + all_ids.push(id); + } + } + + // Insert into store2 + for alert in alerts2 { + let id = alert.key.dia_source_id; + if store2.insert_alert(alert).is_ok() && !all_ids.contains(&id) { + all_ids.push(id); + } + } + + // Merge + store1.merge_in_place(store2); + + // All unique IDs should be findable + for id in all_ids { + prop_assert!(store1.get_by_id(id).is_some()); + } + } + + /// Sort and rekey maintains index consistency + #[test] + fn prop_sort_maintains_index( + alerts in prop::collection::vec(arb_alert(), 1..100) + ) { + let mut store = AlertStore::new(); + let mut inserted_ids = Vec::new(); + + for alert in alerts { + let id = alert.key.dia_source_id; + if store.insert_alert(alert).is_ok() { + inserted_ids.push(id); + } + } + + store.sort_each_night_and_rekey(); + + // All alerts should still be findable after sort + for id in inserted_ids { + prop_assert!(store.get_by_id(id).is_some()); + } + } + + /// Store length matches index size + #[test] + fn prop_length_matches_index( + alerts in prop::collection::vec(arb_alert(), 0..100) + ) { + let mut store = AlertStore::new(); + let mut unique_ids = std::collections::HashSet::new(); + + for alert in alerts { + let dia_source_id = alert.key.dia_source_id; + if store.insert_alert(alert).is_ok() { + unique_ids.insert(dia_source_id); + } + } + + // Store length should match number of unique inserted IDs + prop_assert_eq!(store.n_alerts(), unique_ids.len()); + } + } + } + + // ------------------------------------------------------------------------- + // Edge case tests + // ------------------------------------------------------------------------- + + #[test] + fn get_returns_none_for_missing_night() { + let store = make_test_store(); + assert!(store.get(&nid(999)).is_none()); + } + + #[test] + fn sort_empty_store() { + let mut store = AlertStore::new(); + store.sort_each_night_and_rekey(); + assert!(store.is_empty()); + } + + #[test] + fn merge_with_self_pattern() { + // This tests the pattern where we might accidentally merge a store with itself + // (though the API takes ownership, so this is more about testing similar data) + let mut store1 = AlertStore::new(); + store1 + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + let mut store2 = AlertStore::new(); + store2 + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + // This should work since stores are separate, but IDs match + store1.merge_in_place(store2); + + // Should still have just one alert (same night and vector position) + assert_eq!(store1.n_alerts(), 2); // Actually 2 because same ID in different stores + } + + #[test] + fn very_large_dia_source_id() { + let mut store = AlertStore::new(); + let large_id = u64::MAX - 1; + + store + .insert_alert(mock_alert(large_id, nid(100), 59000.0)) + .unwrap(); + + assert!(store.get_by_id(large_id).is_some()); + } + + // ------------------------------------------------------------------------- + // Tests for sort_each_night_and_rekey() invariants + // ------------------------------------------------------------------------- + + #[cfg(test)] + mod sort_and_rekey_invariants { + use super::*; + + #[test] + fn sort_rekey_maintains_temporal_order() { + let mut store = AlertStore::new(); + + // Insert alerts in reverse temporal order + store + .insert_alert(mock_alert(1003, nid(100), 59003.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59001.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59002.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + let alerts = store.get(&nid(100)).unwrap(); + + // Verify temporal ordering + assert!(alerts[0].mjd_tt < alerts[1].mjd_tt); + assert!(alerts[1].mjd_tt < alerts[2].mjd_tt); + + // Verify specific order + assert_eq!(alerts[0].key.dia_source_id, 1001); + assert_eq!(alerts[1].key.dia_source_id, 1002); + assert_eq!(alerts[2].key.dia_source_id, 1003); + } + + #[test] + fn sort_rekey_idx_matches_position() { + let mut store = AlertStore::new(); + + // Insert in random order + store + .insert_alert(mock_alert(1005, nid(100), 59005.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59002.0)) + .unwrap(); + store + .insert_alert(mock_alert(1008, nid(100), 59008.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59001.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + let alerts = store.get(&nid(100)).unwrap(); + + // After rekey, the index position in id_to_location should match actual position + for (expected_idx, alert) in alerts.iter().enumerate() { + let (indexed_night, indexed_idx) = store + .id_to_location + .get(&alert.key.dia_source_id) + .expect("Alert should be in id_to_location index"); + + assert_eq!( + *indexed_night, + nid(100), + "Alert {} should be indexed in night 100", + alert.key.dia_source_id + ); + assert_eq!( + *indexed_idx, expected_idx, + "Alert {} index position should be {} but got {}", + alert.key.dia_source_id, expected_idx, indexed_idx + ); + } + } + + #[test] + fn sort_rekey_id_to_location_consistency() { + let mut store = AlertStore::new(); + + // Insert alerts across multiple nights + store + .insert_alert(mock_alert(1003, nid(100), 59003.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59001.0)) + .unwrap(); + store + .insert_alert(mock_alert(2002, nid(101), 59102.0)) + .unwrap(); + store + .insert_alert(mock_alert(2001, nid(101), 59101.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + // Verify id_to_location points to correct positions + for (night_id, alerts) in store.as_map_iter() { + for (idx, alert) in alerts.iter().enumerate() { + let (indexed_night, indexed_idx) = store + .id_to_location + .get(&alert.key.dia_source_id) + .expect("Alert should be in index"); + + assert_eq!( + *indexed_night, *night_id, + "Index night mismatch for alert {}", + alert.key.dia_source_id + ); + assert_eq!( + *indexed_idx, idx, + "Index position mismatch for alert {}", + alert.key.dia_source_id + ); + } + } + } + + #[test] + fn sort_rekey_all_alerts_still_findable() { + let mut store = AlertStore::new(); + + let ids = vec![1005, 1002, 1008, 1001, 1003]; + for (i, &id) in ids.iter().enumerate() { + store + .insert_alert(mock_alert(id, nid(100), 59000.0 + i as f64)) + .unwrap(); + } + + store.sort_each_night_and_rekey(); + + // All alerts should still be findable by ID + for &id in &ids { + assert!( + store.get_by_id(id).is_some(), + "Alert {} should be findable after sort/rekey", + id + ); + } + } + + #[test] + fn sort_rekey_multiple_nights_independent() { + let mut store = AlertStore::new(); + + // Night 100: reverse order + store + .insert_alert(mock_alert(1003, nid(100), 59003.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59001.0)) + .unwrap(); + + // Night 101: reverse order + store + .insert_alert(mock_alert(2003, nid(101), 59103.0)) + .unwrap(); + store + .insert_alert(mock_alert(2001, nid(101), 59101.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + // Each night should be sorted independently + let night100 = store.get(&nid(100)).unwrap(); + assert_eq!(night100[0].key.dia_source_id, 1001); + assert_eq!(night100[1].key.dia_source_id, 1003); + + let night101 = store.get(&nid(101)).unwrap(); + assert_eq!(night101[0].key.dia_source_id, 2001); + assert_eq!(night101[1].key.dia_source_id, 2003); + } + + #[test] + fn sort_rekey_preserves_alert_count() { + let mut store = AlertStore::new(); + + // Insert 10 alerts across 3 nights + for i in 0..10 { + let night = nid(100 + (i % 3)); + let mjd = 59000.0 + (10 - i) as f64; // Reverse time order + store + .insert_alert(mock_alert(1000 + i as u64, night, mjd)) + .unwrap(); + } + + let count_before = store.n_alerts(); + store.sort_each_night_and_rekey(); + let count_after = store.n_alerts(); + + assert_eq!( + count_before, count_after, + "Sort/rekey should not change alert count" + ); + } + + #[test] + fn sort_rekey_stable_for_equal_mjd() { + let mut store = AlertStore::new(); + + // Insert alerts with identical MJD + let same_mjd = 59000.0; + store + .insert_alert(mock_alert(1001, nid(100), same_mjd)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), same_mjd)) + .unwrap(); + store + .insert_alert(mock_alert(1003, nid(100), same_mjd)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + let alerts = store.get(&nid(100)).unwrap(); + + // All should have the same MJD + for alert in alerts { + assert_eq!(alert.mjd_tt, same_mjd); + } + + // Position in id_to_location should be 0, 1, 2 + for (expected_idx, alert) in alerts.iter().enumerate() { + let (_night, idx) = store + .id_to_location + .get(&alert.key.dia_source_id) + .expect("Alert should be in index"); + assert_eq!( + *idx, expected_idx, + "Alert {} should be at position {}", + alert.key.dia_source_id, expected_idx + ); + } + } + + #[test] + fn sort_rekey_empty_store_no_panic() { + let mut store = AlertStore::new(); + + // Should not panic on empty store + store.sort_each_night_and_rekey(); + + assert!(store.is_empty()); + } + + #[test] + fn sort_rekey_single_alert_no_change() { + let mut store = AlertStore::new(); + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + let alert = store.get_by_id(1001).unwrap(); + + // Verify via id_to_location that position is 0 + let (_night, idx) = store + .id_to_location + .get(&1001) + .expect("Alert should be in index"); + assert_eq!(*idx, 0, "Single alert should be at position 0"); + assert_eq!(alert.mjd_tt, 59000.0); + } + + #[test] + fn sort_rekey_night_t0_returns_earliest() { + let mut store = AlertStore::new(); + + // Insert in reverse chronological order + store + .insert_alert(mock_alert(1003, nid(100), 59003.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59002.0)) + .unwrap(); + store + .insert_alert(mock_alert(1001, nid(100), 59001.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + // night_t0 should return the first (earliest) alert's MJD + let t0 = store.night_t0(&nid(100)).expect("night_t0 should exist"); + assert_eq!( + t0, 59001.0, + "night_t0 should return earliest MJD after sort" + ); + } + + #[test] + fn sort_rekey_preserves_night_count() { + let mut store = AlertStore::new(); + + // Insert alerts across 3 nights + store + .insert_alert(mock_alert(1001, nid(100), 59000.0)) + .unwrap(); + store + .insert_alert(mock_alert(2001, nid(101), 59001.0)) + .unwrap(); + store + .insert_alert(mock_alert(3001, nid(102), 59002.0)) + .unwrap(); + + let nights_before = store.n_nights(); + store.sort_each_night_and_rekey(); + let nights_after = store.n_nights(); + + assert_eq!( + nights_before, nights_after, + "Sort/rekey should not change night count" + ); + } + + #[test] + fn sort_rekey_get_by_id_returns_correct_alert() { + let mut store = AlertStore::new(); + + // Insert alerts + store + .insert_alert(mock_alert(1005, nid(100), 59005.0)) + .unwrap(); + store + .insert_alert(mock_alert(1002, nid(100), 59002.0)) + .unwrap(); + store + .insert_alert(mock_alert(1008, nid(100), 59008.0)) + .unwrap(); + + store.sort_each_night_and_rekey(); + + // get_by_id should return the correct alert + let alert = store.get_by_id(1002).unwrap(); + assert_eq!(alert.key.dia_source_id, 1002); + assert_eq!(alert.mjd_tt, 59002.0); + + // Should be first after sort (lowest MJD) + let (_night, idx) = store + .id_to_location + .get(&1002) + .expect("Alert should be in index"); + assert_eq!(*idx, 0, "Alert 1002 should be at position 0 after sort"); + } + } + + // ------------------------------------------------------------------------- + // Property-based tests for sort/rekey invariants + // ------------------------------------------------------------------------- + + #[cfg(test)] + mod proptest_sort_rekey { + use super::*; + use proptest::prelude::*; + + proptest! { + /// After sort_each_night_and_rekey, id_to_location index must match actual position + #[test] + fn prop_sort_rekey_idx_matches_position( + alerts in prop::collection::vec( + (any::().prop_filter("non-zero", |&id| id > 0), 0u32..10, 59000.0..60000.0f64), + 1..50 + ) + ) { + let mut store = AlertStore::new(); + + for (id, night, mjd) in alerts { + let _ = store.insert_alert(mock_alert(id, nid(night), mjd)); + } + + store.sort_each_night_and_rekey(); + + // For every night, verify id_to_location index matches actual position + for (night_id, alerts) in store.as_map_iter() { + for (expected_pos, alert) in alerts.iter().enumerate() { + let dia_source_id = alert.key.dia_source_id; + + // Check that alert is in id_to_location + if let Some((indexed_night, indexed_pos)) = + store.id_to_location.get(&dia_source_id) + { + prop_assert_eq!( + *indexed_night, *night_id, + "Alert {} indexed in wrong night: expected {}, got {}", + dia_source_id, night_id, indexed_night + ); + prop_assert_eq!( + *indexed_pos, expected_pos, + "Alert {} position mismatch: expected {}, got {}", + dia_source_id, expected_pos, indexed_pos + ); + } else { + return Err(proptest::test_runner::TestCaseError::fail( + format!("Alert {} not found in id_to_location index", dia_source_id) + )); + } + } + } + } + + /// After sort_each_night_and_rekey, alerts within each night are temporally ordered + #[test] + fn prop_sort_rekey_temporal_order( + alerts in prop::collection::vec( + (any::().prop_filter("non-zero", |&id| id > 0), 0u32..10, 59000.0..60000.0f64), + 2..50 + ) + ) { + let mut store = AlertStore::new(); + + for (id, night, mjd) in alerts { + let _ = store.insert_alert(mock_alert(id, nid(night), mjd)); + } + + store.sort_each_night_and_rekey(); + + // For every night with 2+ alerts, verify temporal ordering + for (_night_id, alerts) in store.as_map_iter() { + if alerts.len() >= 2 { + for window in alerts.windows(2) { + prop_assert!( + window[0].mjd_tt <= window[1].mjd_tt, + "Temporal order violated: {} > {}", + window[0].mjd_tt, window[1].mjd_tt + ); + } + } + } + } + + /// After sort_each_night_and_rekey, all alerts remain findable + #[test] + fn prop_sort_rekey_preserves_findability( + alerts in prop::collection::vec( + (any::().prop_filter("non-zero", |&id| id > 0), 0u32..10, 59000.0..60000.0f64), + 1..50 + ) + ) { + let mut store = AlertStore::new(); + let mut inserted_ids = Vec::new(); + + for (id, night, mjd) in alerts { + if store.insert_alert(mock_alert(id, nid(night), mjd)).is_ok() { + inserted_ids.push(id); + } + } + + store.sort_each_night_and_rekey(); + + // All successfully inserted alerts should still be findable + for id in inserted_ids { + prop_assert!( + store.get_by_id(id).is_some(), + "Alert {} not findable after sort/rekey", + id + ); + } + } + + /// After sort_each_night_and_rekey, id_to_location index is consistent + #[test] + fn prop_sort_rekey_index_consistency( + alerts in prop::collection::vec( + (any::().prop_filter("non-zero", |&id| id > 0), 0u32..10, 59000.0..60000.0f64), + 1..50 + ) + ) { + let mut store = AlertStore::new(); + + for (id, night, mjd) in alerts { + let _ = store.insert_alert(mock_alert(id, nid(night), mjd)); + } + + store.sort_each_night_and_rekey(); + + // Verify index points to correct positions + for (night_id, alerts) in store.as_map_iter() { + for (idx, alert) in alerts.iter().enumerate() { + if let Some((indexed_night, indexed_idx)) = + store.id_to_location.get(&alert.key.dia_source_id) + { + prop_assert_eq!(*indexed_night, *night_id); + prop_assert_eq!(*indexed_idx, idx); + } else { + return Err(proptest::test_runner::TestCaseError::fail( + format!("Alert {} missing from index", alert.key.dia_source_id) + )); + } + } + } + } + } + } +} diff --git a/crates/fink-fat-engine/src/astro_math.rs b/crates/fink-fat-engine/src/astro_math.rs new file mode 100644 index 00000000..b7bcc8e6 --- /dev/null +++ b/crates/fink-fat-engine/src/astro_math.rs @@ -0,0 +1,2115 @@ +//! Lightweight geometric and numerical utilities for sky-coordinate arithmetic. +//! +//! This module provides the low-level building blocks for seed construction, +//! kinematic propagation, and feature computation in the fink-fat pipeline. +//! All functions operate on plain Rust primitives (`f64`, fixed-size arrays) +//! to keep hot paths allocation-free. +//! +//! ## Function groups +//! +//! ### Spherical geometry +//! - [`unit_vec`] — equatorial `(ra, dec)` to 3D unit vector. +//! - [`ang_sep`] — great-circle distance via the spherical law of cosines. +//! - [`angular_separation_vincenty`] — numerically stable Vincenty formula. +//! - [`spherical_midpoint`] — robust angular mean of two sky directions. +//! +//! ### Tangent-plane projection +//! - [`planar_offset_fast`] — small-angle Cartesian offsets around a center. +//! - [`radec_to_tangent`] — full gnomonic projection onto a local tangent plane. +//! - [`tangent_to_radec`] — inverse gnomonic: tangent-plane back to sky coordinates. +//! +//! ### 2D linear algebra +//! - [`dot2`], [`dot3`] — fixed-size dot products. +//! - [`mat_vec2`] — 2×2 matrix–vector product. +//! - [`mat_mul2`] — 2×2 matrix–matrix product. +//! - [`trace_2x2`] — matrix trace. +//! - [`det_sym_2x2`] — determinant of a symmetric 2×2 matrix. +//! - [`invert_sym_2x2`] — robust inversion of a symmetric 2×2 matrix. +//! - [`cholesky_lower_sym_2x2`] — Cholesky factorization of a symmetric 2×2 matrix. +//! - [`lambda_max_2x2`] — largest eigenvalue of a symmetric 2×2 matrix. +//! - [`l2_norm`] — Euclidean norm of a 2D vector. +//! +//! ### Kinematics +//! - [`fit_quad_1d`] — analytic quadratic-motion fit through three time samples. +//! +//! ### Numerical guards and unit conversion +//! - [`clamp_unit`] — clamp to $[-1, 1]$ with NaN protection. +//! - [`safe_ln`] — natural logarithm with zero fallback for non-positive inputs. +//! - [`wrap_pm_pi`] — fold an angle into $(-\pi, \pi]$. +//! - [`arcsec_to_rad`] — arcseconds to radians. + +use std::f64::consts::{PI, TAU}; + +use crate::{Arcsec, Radian}; + +/// Convert equatorial coordinates `(ra, dec)` to a 3D unit vector on the +/// celestial sphere. +/// +/// The mapping assumes: +/// - `ra` is a right ascension in **radians**, in `[0, 2π)` (not enforced), +/// - `dec` is a declination in **radians**, in `[-π/2, π/2]`. +/// +/// The returned unit vector is: +/// - `x = cos(dec) * cos(ra)` +/// - `y = cos(dec) * sin(ra)` +/// - `z = sin(dec)` +/// +/// Arguments +/// --------- +/// * `ra` – Right ascension in radians. +/// * `dec` – Declination in radians. +/// +/// Return +/// ------ +/// 3D unit vector `[x, y, z]` on the unit sphere. +#[inline] +pub fn unit_vec(ra: Radian, dec: Radian) -> [f64; 3] { + let cos_dec = dec.cos(); + [cos_dec * ra.cos(), cos_dec * ra.sin(), dec.sin()] +} + +/// Compute the dot product of two 3D vectors. +/// +/// This is a minimal helper to keep hot paths explicit and branch-free. +/// +/// Arguments +/// --------- +/// * `a` – First vector `[x, y, z]`. +/// * `b` – Second vector `[x, y, z]`. +/// +/// Return +/// ------ +/// Scalar dot product `a · b`. +#[inline] +pub fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 { + a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +} + +/// Compute the dot product of two 2D vectors. +/// +/// The dot product is $a \cdot b = a\_0 b\_0 + a\_1 b\_1$. +/// +/// Arguments +/// --------- +/// * `a` – First 2D vector `[x, y]`. +/// * `b` – Second 2D vector `[x, y]`. +/// +/// Return +/// ------ +/// Scalar dot product $a \cdot b$. +#[inline] +pub fn dot2(a: [f64; 2], b: [f64; 2]) -> f64 { + a[0] * b[0] + a[1] * b[1] +} + +/// Multiply a 2×2 matrix by a 2D vector. +/// +/// This helper evaluates the linear transformation: +/// ```text +/// [ m00 m01 ] [ v0 ] = [ m00·v0 + m01·v1 ] +/// [ m10 m11 ] [ v1 ] [ m10·v0 + m11·v1 ] +/// ``` +/// +/// Arguments +/// --------- +/// * `m` – 2×2 matrix stored in row-major order. +/// * `v` – 2D vector `[v0, v1]`. +/// +/// Return +/// ------ +/// Resulting 2D vector `m · v`. +/// +/// Notes +/// ----- +/// - The matrix is assumed to be small and dense. +/// - No symmetry or conditioning assumptions are required. +/// - This function is used extensively in innovation and covariance +/// computations where allocating a generic matrix type would be +/// unnecessary overhead. +#[inline] +pub fn mat_vec2(m: [[f64; 2]; 2], v: [f64; 2]) -> [f64; 2] { + [ + m[0][0] * v[0] + m[0][1] * v[1], + m[1][0] * v[0] + m[1][1] * v[1], + ] +} + +/// Clamp a scalar to the interval [−1, 1], with NaN/Inf protection. +/// +/// This helper is primarily intended for quantities that should lie +/// within trigonometric bounds, such as: +/// - cosine of an angle, +/// - normalized dot products. +/// +/// Arguments +/// --------- +/// * `x` – Input scalar value. +/// +/// Return +/// ------ +/// Value clamped to the interval `[−1, 1]`. +/// Returns `0.0` if `x` is not finite. +/// +/// Notes +/// ----- +/// - Returning `0.0` for non-finite inputs avoids propagating NaNs +/// into downstream computations (e.g. `acos`, ML features). +/// - This behavior is intentional and favors robustness over strict +/// error signaling. +#[inline] +pub fn clamp_unit(x: f64) -> f64 { + if x.is_finite() { + x.clamp(-1.0, 1.0) + } else { + 0.0 + } +} + +/// Compute a numerically safe natural logarithm. +/// +/// This helper returns `ln(x)` only when the input is strictly positive +/// and finite. Otherwise, it returns `0.0`. +/// +/// Arguments +/// --------- +/// * `x` – Input scalar value. +/// +/// Return +/// ------ +/// `ln(x)` if `x > 0` and finite, otherwise `0.0`. +/// +/// Notes +/// ----- +/// - This function is typically used for log-transformed features +/// (e.g. `log(chi² + ε)`) where negative or non-finite values are +/// not meaningful. +/// - Returning `0.0` instead of `-∞` or `NaN` avoids destabilizing +/// downstream ML pipelines. +#[inline] +pub fn safe_ln(x: f64) -> f64 { + if x.is_finite() && x > 0.0 { + x.ln() + } else { + 0.0 + } +} + +/// Invert a symmetric 2×2 matrix with numerical safeguards. +/// +/// This routine computes the inverse of a **symmetric** matrix: +/// ```text +/// [ a b ] +/// [ b d ] +/// ``` +/// +/// with additional protections against: +/// - non-finite entries, +/// - near-singular determinants, +/// - poorly conditioned covariance matrices. +/// +/// A diagonal floor is applied before inversion to ensure numerical +/// stability. +/// +/// Arguments +/// --------- +/// * `m` – Symmetric 2×2 matrix (only the symmetric part is used). +/// * `floor` – Minimum allowed value for diagonal terms and determinant +/// regularization. +/// +/// Return +/// ------ +/// Inverse 2×2 matrix. +/// +/// Notes +/// ----- +/// - If the determinant is too small or non-finite, the function falls +/// back to a **diagonal inverse**: +/// ```text +/// inv ≈ diag(1/a, 1/d) +/// ``` +/// effectively discarding off-diagonal correlations. +/// - This behavior is intentional and favors robustness over exactness +/// in degenerate cases. +/// - Designed primarily for inverting innovation or covariance matrices +/// in short-arc astrometric linking. +#[inline] +pub fn invert_sym_2x2(m: [[f64; 2]; 2], floor: f64) -> [[f64; 2]; 2] { + let mut a = m[0][0]; + let mut b = 0.5 * (m[0][1] + m[1][0]); + let mut d = m[1][1]; + + if !a.is_finite() || a < floor { + a = floor; + } + if !d.is_finite() || d < floor { + d = floor; + } + if !b.is_finite() { + b = 0.0 + } + + let det = a * d - b * b; + if !det.is_finite() || det <= floor { + return [[1.0 / a.max(floor), 0.0], [0.0, 1.0 / d.max(floor)]]; + } + + let inv_det = 1.0 / det; + [[d * inv_det, -b * inv_det], [-b * inv_det, a * inv_det]] +} + +/// Compute the trace of a 2×2 matrix. +/// +/// The trace is the sum of diagonal elements: $\mathrm{tr}(M) = M\_{00} + M\_{11}$. +/// +/// Arguments +/// --------- +/// * `m` – 2×2 matrix. +/// +/// Return +/// ------ +/// Trace of the matrix. +/// +/// Notes +/// ----- +/// For covariance matrices, the trace equals the total variance and is commonly +/// used as a scalar uncertainty proxy. +#[inline] +pub fn trace_2x2(m: [[f64; 2]; 2]) -> f64 { + m[0][0] + m[1][1] +} + +/// Multiply two 2×2 matrices. +/// +/// Evaluates $C = A \cdot B$ where $C\_{ij} = \sum\_k A\_{ik} B\_{kj}$. +/// Matrices are stored in row-major order. +/// +/// Arguments +/// --------- +/// * `a` – Left-hand 2×2 matrix. +/// * `b` – Right-hand 2×2 matrix. +/// +/// Return +/// ------ +/// Product matrix $A \cdot B$ as a row-major 2×2 array. +/// +/// Notes +/// ----- +/// Allocation-free; suitable for propagating 2D covariance matrices and +/// verifying numerical inverses. +/// +/// See also +/// -------- +/// * [`mat_vec2`] – Matrix–vector multiplication for 2D vectors. +/// * [`invert_sym_2x2`] – Robust inversion of symmetric 2×2 matrices. +#[inline] +pub fn mat_mul2(a: [[f64; 2]; 2], b: [[f64; 2]; 2]) -> [[f64; 2]; 2] { + [ + [ + a[0][0] * b[0][0] + a[0][1] * b[1][0], + a[0][0] * b[0][1] + a[0][1] * b[1][1], + ], + [ + a[1][0] * b[0][0] + a[1][1] * b[1][0], + a[1][0] * b[0][1] + a[1][1] * b[1][1], + ], + ] +} + +/// Compute the determinant of a symmetric 2×2 matrix. +/// +/// For a symmetric matrix with diagonal entries $a$, $d$ and symmetrized +/// off-diagonal $b = \tfrac{1}{2}(m\_{01} + m\_{10})$, the determinant is: +/// $$\det(M) = ad - b^2$$ +/// +/// Arguments +/// --------- +/// * `m` – Symmetric 2×2 matrix (only the symmetric part is used). +/// +/// Return +/// ------ +/// Determinant of the matrix. +/// +/// Notes +/// ----- +/// - A positive determinant is a necessary condition for positive definiteness. +/// - No finiteness checks are performed; callers are responsible for +/// handling pathological inputs. +/// +/// See also +/// -------- +/// * [`invert_sym_2x2`] – Robust inversion of symmetric 2×2 matrices. +/// * [`lambda_max_2x2`] – Largest eigenvalue of a symmetric 2×2 matrix. +#[inline] +pub fn det_sym_2x2(m: [[f64; 2]; 2]) -> f64 { + // Determinant of symmetric 2x2: a*d - b^2 (symmetrize b) + let a = m[0][0]; + let b = 0.5 * (m[0][1] + m[1][0]); + let d = m[1][1]; + a * d - b * b +} + +/// Compute the lower-triangular Cholesky factor `L` of a symmetric 2×2 matrix. +/// +/// This routine factorizes a **symmetric** matrix: +/// ```text +/// M = [ a b ] +/// [ b d ] +/// ``` +/// into: +/// ```text +/// M = L · Lᵀ, with L = [ l00 0 ] +/// [ l10 l11] +/// ``` +/// +/// The implementation is specialized for 2×2 and designed for **hot paths** +/// (innovation whitening, covariance normalization, gating). +/// +/// Numerical robustness +/// -------------------- +/// This function applies safety guards before factorization: +/// - symmetrizes the off-diagonal term `b`, +/// - floors diagonal terms to at least `floor`, +/// - treats non-finite entries as invalid (returns `None`), +/// - checks positive definiteness via the implied Schur complement: +/// `t = d - (b² / a)` and requires `t > floor`. +/// +/// If the matrix is not numerically positive definite, returns `None`. +/// +/// Arguments +/// --------- +/// * `m` – Symmetric 2×2 matrix (only the symmetric part is used). +/// * `floor` – Minimum allowed value for diagonal terms and for the Schur complement. +/// Typical values: `1e-20` for radians² covariances, or `1e-12` in more +/// conservative settings. +/// +/// Return +/// ------ +/// * `Some(L)` where `L` is a 2×2 lower-triangular matrix stored in row-major order: +/// ```text +/// [ l00 0.0 ] +/// [ l10 l11 ] +/// ``` +/// * `None` if the input is not finite or not positive definite after flooring. +/// +/// Notes +/// ----- +/// - For a 2×2 symmetric matrix, positive definiteness is equivalent to: +/// `a > 0` and `det(M) > 0`. +/// This routine uses an equivalent check via the Schur complement. +/// - If you want a *fallback* behavior (diagonal-only whitening), implement that +/// at the call site when `None` is returned. +/// +/// See also +/// -------- +/// * [`invert_sym_2x2`] – Robust inversion with diagonal fallback. +/// * [`det_sym_2x2`] – Determinant of a symmetric 2×2 matrix. +#[inline] +pub fn cholesky_lower_sym_2x2(m: [[f64; 2]; 2], floor: f64) -> Option<[[f64; 2]; 2]> { + // Symmetrize and extract. + let mut a = m[0][0]; + let mut d = m[1][1]; + let b = 0.5 * (m[0][1] + m[1][0]); + + // Validate and floor diagonal terms. + if !a.is_finite() || !d.is_finite() || !b.is_finite() || !floor.is_finite() || floor <= 0.0 { + return None; + } + if a < floor { + a = floor; + } + if d < floor { + d = floor; + } + + // Cholesky for 2×2 SPD: + // l00 = sqrt(a) + // l10 = b / l00 + // l11 = sqrt(d - l10^2) + let l00 = a.sqrt(); + if !l00.is_finite() || l00 <= 0.0 { + return None; + } + + let l10 = b / l00; + let t = d - l10 * l10; + + // Require strictly positive Schur complement (with floor). + if !t.is_finite() || t < floor { + return None; + } + + let l11 = t.sqrt(); + if !l11.is_finite() || l11 <= 0.0 { + return None; + } + + Some([[l00, 0.0], [l10, l11]]) +} + +/// Wrap an angle into the interval $(-\pi, \pi]$. +/// +/// Useful when computing RA differences that should be taken modulo $2\pi$. +/// +/// Arguments +/// --------- +/// * `x` – Input angle in radians (unbounded). +/// +/// Return +/// ------ +/// Angle in radians, $y \in (-\pi, \pi]$. +#[inline] +pub fn wrap_pm_pi(x: Radian) -> Radian { + let two_pi = 2.0 * PI; + let mut y = (x + PI) % two_pi; + if y < 0.0 { + y += two_pi; + } + y - PI +} + +/// Compute tangent-plane offsets around `(ra0, dec0)` using a precomputed `cos(dec0)`. +/// +/// This routine projects a target position `(ra, dec)` onto the local tangent +/// plane centered at `(ra0, dec0)`. It is intended for **small angular +/// separations**, where a Cartesian approximation is sufficient. +/// +/// The offsets are: +/// - `dx = wrap_pm_pi(ra − ra0) * cos(dec0)` +/// - `dy = dec − dec0` +/// +/// Arguments +/// --------- +/// * `ra0` – Center right ascension (radians). +/// * `dec0` – Center declination (radians). +/// * `cos_dec0` – Precomputed cosine of `dec0`, i.e. `dec0.cos()`. +/// * `ra` – Target right ascension (radians). +/// * `dec` – Target declination (radians). +/// +/// Return +/// ------ +/// `(dx, dy)` tangent-plane offsets in **radians**. +#[inline] +pub fn planar_offset_fast( + ra0: Radian, + dec0: Radian, + cos_dec0: f64, + ra: Radian, + dec: Radian, +) -> (Radian, Radian) { + let dx = wrap_pm_pi(ra - ra0) * cos_dec0; + let dy = dec - dec0; + (dx, dy) +} + +/// Convert an angle from arcseconds to radians. +/// +/// Applies $1\text{ arcsec} = \pi / 648000$ radians. +/// +/// Arguments +/// --------- +/// * `x` – Angle in arcseconds. +/// +/// Return +/// ------ +/// Angle in radians. +#[inline] +pub fn arcsec_to_rad(x: Arcsec) -> Radian { + x * PI / (180.0 * 3600.0) +} + +/// Compute the great-circle angular separation between two sky positions. +/// +/// Evaluates the spherical law of cosines: +/// $$\cos d = \sin\delta\_1\sin\delta\_2 + \cos\delta\_1\cos\delta\_2\cos(\Delta\alpha)$$ +/// where $\Delta\alpha = \alpha\_2 - \alpha\_1$ (wrapped to $(-\pi, \pi]$). +/// The result $d \in [0, \pi]$ is symmetric in its arguments. +/// +/// Arguments +/// --------- +/// * `ra1` – First point right ascension (radians). +/// * `dec1` – First point declination (radians). +/// * `ra2` – Second point right ascension (radians). +/// * `dec2` – Second point declination (radians). +/// +/// Return +/// ------ +/// Great-circle angular distance $d$ in radians. +#[inline] +pub fn ang_sep(ra1: f64, dec1: f64, ra2: f64, dec2: f64) -> f64 { + let s1 = dec1.sin(); + let c1 = dec1.cos(); + let s2 = dec2.sin(); + let c2 = dec2.cos(); + let dlon = wrap_pm_pi(ra2 - ra1); + + let cos_d = s1 * s2 + c1 * c2 * dlon.cos(); + cos_d.clamp(-1.0, 1.0).acos() +} + +/// Compute the great-circle angular separation between two sky positions +/// using the **Vincenty formula**. +/// +/// This formulation is numerically stable for all separations, including +/// near the poles and antipodal points. It corresponds to the implementation +/// used in Astropy's ``angular_separation``. +/// +/// Arguments +/// --------- +/// * `lon1` – Longitude of the first point (radians). +/// * `lat1` – Latitude of the first point (radians). +/// * `lon2` – Longitude of the second point (radians). +/// * `lat2` – Latitude of the second point (radians). +/// +/// Return +/// ------ +/// Angular separation in **radians**, guaranteed to lie in `[0, π]`. +/// +/// Notes +/// ----- +/// Uses the Vincenty formula: +/// $$d = \mathrm{atan2}\left(\sqrt{n\_1^2 + n\_2^2}, D\right)$$ +/// where $\Delta\lambda = \mathrm{lon}\_2 - \mathrm{lon}\_1$, +/// $n\_1 = \cos(\mathrm{lat}\_2)\sin(\Delta\lambda)$, +/// $n\_2 = \cos(\mathrm{lat}\_1)\sin(\mathrm{lat}\_2) - \sin(\mathrm{lat}\_1)\cos(\mathrm{lat}\_2)\cos(\Delta\lambda)$, and +/// $D = \sin(\mathrm{lat}\_1)\sin(\mathrm{lat}\_2) + \cos(\mathrm{lat}\_1)\cos(\mathrm{lat}\_2)\cos(\Delta\lambda)$. +/// +/// See also +/// -------- +/// * +#[inline] +pub fn angular_separation_vincenty( + lon1: Radian, + lat1: Radian, + lon2: Radian, + lat2: Radian, +) -> Radian { + let dlon = lon2 - lon1; + + let (slon, clon) = dlon.sin_cos(); + let (slat1, clat1) = lat1.sin_cos(); + let (slat2, clat2) = lat2.sin_cos(); + + let num1 = clat2 * slon; + let num2 = clat1 * slat2 - slat1 * clat2 * clon; + let denom = slat1 * slat2 + clat1 * clat2 * clon; + + num1.hypot(num2).atan2(denom) +} + +// ----------------------------------------------------------------------------- +// Numerical guard constants +// ----------------------------------------------------------------------------- + +/// Smallest allowed value for the denominator `cosc` in gnomonic projection. +/// +/// Context +/// ------- +/// In the gnomonic projection: +/// ```text +/// cosc = sin(dec0) sin(dec) + cos(dec0) cos(dec) cos(ra - ra0) +/// x = cos(dec) sin(ra - ra0) / cosc +/// y = [cos(dec0) sin(dec) - sin(dec0) cos(dec) cos(ra - ra0)] / cosc +/// ``` +/// When the target direction lies close to **90° from the tangent point**, +/// `cosc → 0` and the exact projection diverges to infinity. +/// +/// Why this constant? +/// ------------------ +/// Mathematically, divergence is expected and correct. But numerically, when +/// `cosc` reaches values smaller than ~1e-15, floating-point rounding can produce: +/// - divisions by zero, +/// - NaNs, +/// - overflow into `Inf` even for non-pathological inputs. +/// +/// We therefore enforce: +/// ```text +/// inv = 1.0 / max(cosc, INV_COSC_MIN) +/// ``` +/// +/// Choice of value +/// ---------------- +/// - `1e-12` keeps enough dynamic range for small-angle work (few degrees), +/// - avoids Inf/NaN for borderline cases, +/// - does *not* distort the projection in the regime where we actually use it: +/// all fink-fat seeds remain well within the validity domain. +/// +/// This value is not physically meaningful; it's a **numerical safety floor**. +const INV_COSC_MIN: f64 = 1e-12; + +/// Lower bound used to avoid division by a nearly-zero vector norm when +/// averaging or normalizing spherical vectors. +/// +/// Context +/// ------- +/// - Used in `spherical_midpoint()`: we add two unit vectors and normalize them. +/// - When the directions are **nearly opposite**, the sum vector can approach +/// the zero vector, making its length extremely small. +/// +/// Consequences without guard +/// -------------------------- +/// A norm `r ≈ 0` produces catastrophic amplification of noise when +/// normalizing `(x/r, y/r, z/r)` → `NaN`, `Inf`, or huge garbage values. +/// +/// Why this constant? +/// ------------------ +/// We clamp: +/// ```text +/// r = max(r, NORM_MIN) +/// ``` +/// before normalizing. +/// +/// Choice of value +/// --------------- +/// - `1e-16` is slightly above the smallest meaningful double-precision values +/// for normalized vectors (~1e-308 is too small, causes underflow well before). +/// - It preserves stability without biasing typical use. +/// - It only activates in extreme geometries (nearly antipodal sources) that we +/// *never* use for seed construction anyway. +/// +/// This constant is purely a **numerical robustness guard**. +const NORM_MIN: f64 = 1e-16; + +/// Compute a robust spherical midpoint between two sky directions. +/// +/// Returns the angular mean via vector averaging: the two unit vectors are +/// summed and the result is renormalized to the unit sphere. This is not +/// the exact geodesic midpoint, but is stable and accurate enough for +/// defining a tangent-plane center. +/// +/// Arguments +/// --------- +/// * `ra1` – Right ascension of the first point (radians). +/// * `dec1` – Declination of the first point (radians). +/// * `ra2` – Right ascension of the second point (radians). +/// * `dec2` – Declination of the second point (radians). +/// +/// Return +/// ------ +/// `(ra, dec)` of the midpoint in radians, with $\mathrm{ra} \in [0, 2\pi)$. +/// +/// Notes +/// ----- +/// When the two directions are nearly antipodal the sum vector approaches +/// zero; a numerical floor is applied before normalization to avoid NaN. +#[inline] +pub fn spherical_midpoint( + ra1: Radian, + dec1: Radian, + ra2: Radian, + dec2: Radian, +) -> (Radian, Radian) { + let (x1, y1, z1) = sph_to_cart(ra1, dec1); + let (x2, y2, z2) = sph_to_cart(ra2, dec2); + let (x, y, z) = (x1 + x2, y1 + y2, z1 + z2); + let r = (x * x + y * y + z * z).sqrt().max(NORM_MIN); + cart_to_sph(x / r, y / r, z / r) +} + +/// Gnomonic (zenithal perspective) projection of a sky position onto the +/// tangent plane centred at `(ra0, dec0)`. +/// +/// The projection formulas are: +/// $$\begin{align} x &= \frac{\cos\delta\sin(\alpha - \alpha\_0)}{c} \\ y &= \frac{\cos\delta\_0\sin\delta - \sin\delta\_0\cos\delta\cos(\alpha - \alpha\_0)}{c} \end{align}$$ +/// where $c = \sin\delta\_0\sin\delta + \cos\delta\_0\cos\delta\cos(\alpha - \alpha\_0)$ +/// is the cosine of the angular distance to the tangent point. +/// +/// Arguments +/// --------- +/// * `ra` – Target right ascension (radians). +/// * `dec` – Target declination (radians). +/// * `ra0` – Tangent-point right ascension (radians). +/// * `dec0` – Tangent-point declination (radians). +/// +/// Return +/// ------ +/// Tangent-plane coordinates `[x, y]` in radians. +/// +/// Notes +/// ----- +/// The denominator $c$ is floored before inversion to protect against +/// division by zero when the target is near 90° from the tangent point. +/// All fink-fat seeds remain well within the valid operating range of a +/// few degrees from the tangent point. +#[inline] +pub fn radec_to_tangent(ra: Radian, dec: Radian, ra0: Radian, dec0: Radian) -> [f64; 2] { + let (sdec, cdec) = dec.sin_cos(); + let (sdec0, cdec0) = dec0.sin_cos(); + let dra = ra - ra0; + let (sdra, cdra) = dra.sin_cos(); + + // cosc = sin(dec0) sin(dec) + cos(dec0) cos(dec) cos(dra) + let cosc = cdec0 * cdec * cdra + sdec0 * sdec; + let inv = 1.0 / cosc.max(INV_COSC_MIN); + + let x = cdec * sdra * inv; + let y = (cdec0 * sdec - sdec0 * cdec * cdra) * inv; + [x, y] +} + +/// Inverse gnomonic projection: map tangent-plane coordinates `(x, y)` back +/// to equatorial `(ra, dec)`. +/// +/// Arguments +/// --------- +/// * `x` – Tangent-plane east offset in radians. +/// * `y` – Tangent-plane north offset in radians. +/// * `ra0` – Tangent-point right ascension (radians). +/// * `dec0` – Tangent-point declination (radians). +/// +/// Return +/// ------ +/// `(ra, dec)` in radians with $\mathrm{ra} \in [0, 2\pi)$. +/// +/// Notes +/// ----- +/// When `(x, y)` is within numerical zero of the tangent point the function +/// returns `(ra0, dec0)` directly to avoid a degenerate `atan2(0, 0)` call. +#[inline] +pub fn tangent_to_radec(x: f64, y: f64, ra0: Radian, dec0: Radian) -> (Radian, Radian) { + let rho2 = x * x + y * y; + if rho2 < 1e-24 { + return (ra0.rem_euclid(TAU), dec0); + } + let rho = rho2.sqrt(); + let c = rho.atan(); + let (sc, cc) = c.sin_cos(); + let (s0, c0) = dec0.sin_cos(); + + let dec = (cc * s0 + (y * sc * c0) / rho).asin(); + let denom = rho * c0 * cc - y * s0 * sc; + let ra = ra0 + (x * sc).atan2(denom); + (ra.rem_euclid(TAU), dec) +} + +/// Fit a 1D quadratic motion model through three time samples. +/// +/// Fits the kinematic model $x(t) = p\_0 + v\, t + \tfrac{1}{2} a\, t^2$ +/// through **exactly three samples** $(t\_i, x\_i)$ using an analytic solution. +/// Returns the position $p\_0$, velocity $v$, and acceleration $a$ evaluated +/// at $t = 0$. +/// +/// The three time samples must be **distinct** and expressed relative to the +/// same origin. The motion is assumed to be well approximated by constant +/// acceleration over each interval. +/// +/// Why this formulation? +/// --------------------- +/// Instead of solving a full linear system, this implementation: +/// - first estimates local velocities using finite differences, +/// - then derives the acceleration from the *change in velocity*, +/// - and finally recovers $(p\_0, v)$ consistently. +/// +/// This is numerically stable for small time spans, fast (no matrix +/// inversion), and well suited for short-arc astrometric fitting. +/// +/// Arguments +/// --------- +/// * `dt` – Array of three time offsets `[t0, t1, t2]` (in days), +/// expressed **relative to the same origin**. +/// * `x` – Array of three scalar positions `[x0, x1, x2]` corresponding +/// to the times in `dt`. +/// +/// Return +/// ------ +/// `(p0, v, a)` where: +/// * `p0` – position at $t = 0$, +/// * `v` – velocity at $t = 0$, +/// * `a` – constant acceleration. +/// +/// Notes +/// ----- +/// - The reference time $t = 0$ does **not** need to coincide with any of +/// the sample times. +/// - Choosing $t = 0$ near the middle sample reduces numerical correlations +/// between $p\_0$, $v$, and $a$. +/// +/// Panics +/// ------ +/// Panics if any two time samples are equal (division by zero). +#[inline] +pub fn fit_quad_1d(dt: [f64; 3], x: [f64; 3]) -> (f64, f64, f64) { + // Unpack time samples + let (t0, t1, t2) = (dt[0], dt[1], dt[2]); + + // Inverse time intervals between consecutive samples + // (used for finite-difference velocity estimates) + let inv_01 = 1.0 / (t1 - t0); + let inv_12 = 1.0 / (t2 - t1); + + // First-order finite-difference velocities on [t0, t1] and [t1, t2] + let d01 = (x[1] - x[0]) * inv_01; + let d12 = (x[2] - x[1]) * inv_12; + + // Inverse total time span (t2 - t0) + let inv_20 = 1.0 / (t2 - t0); + + // Acceleration is twice the slope of the velocity change: + // + // a = 2 · (d12 − d01) / (t2 − t0) + // + // This follows from the quadratic model where velocity varies linearly. + let a = 2.0 * (d12 - d01) * inv_20; + + // Velocity at t = 0. + // + // We start from the velocity on [t0, t1] and remove the contribution + // of acceleration evaluated at the midpoint of the interval. + let v = d01 - 0.5 * a * (t0 + t1); + + // Position at t = 0, obtained by rearranging: + // + // x(t1) = p0 + v·t1 + 0.5·a·t1² + let p0 = x[1] - v * t1 - 0.5 * a * t1 * t1; + + (p0, v, a) +} + +/// Compute the largest eigenvalue $\lambda\_{\max}$ of a symmetric 2×2 matrix. +/// +/// $$\lambda\_{\max} = \frac{1}{2}\Bigl(\mathrm{tr}(A) + \sqrt{(a\_{11} - a\_{22})^2 + 4a\_{12}^2}\Bigr)$$ +/// where $a\_{12} = \tfrac{1}{2}(a\_{01} + a\_{10})$ (symmetrized off-diagonal). +/// +/// Arguments +/// --------- +/// * `a` – Symmetric 2×2 matrix. +/// +/// Return +/// ------ +/// Largest eigenvalue $\lambda\_{\max}$. +/// +/// Notes +/// ----- +/// Primarily used to convert a 2D positional covariance into a scalar search +/// radius for spatial cone queries. +#[inline] +pub fn lambda_max_2x2(a: [[f64; 2]; 2]) -> f64 { + let a11 = a[0][0]; + let a22 = a[1][1]; + let a12 = 0.5 * (a[0][1] + a[1][0]); // symmetrize + + let tr = a11 + a22; + let rad = (a11 - a22).hypot(2.0 * a12); + 0.5 * (tr + rad) +} + +/// Compute the Euclidean L2 norm of a 2D vector. +/// +/// Returns $\sqrt{x^2 + y^2}$ using `f64::hypot` for numerical stability. +/// +/// Arguments +/// --------- +/// * `x` – First component. +/// * `y` – Second component. +/// +/// Return +/// ------ +/// Euclidean norm $\|(x, y)\|\_2$. +#[inline] +pub fn l2_norm(x: f64, y: f64) -> f64 { + x.hypot(y) +} + +/* --------------------------- Private helpers --------------------------- */ + +/// Spherical → cartesian unit vector. +#[inline] +fn sph_to_cart(ra: Radian, dec: Radian) -> (f64, f64, f64) { + let (sdec, cdec) = dec.sin_cos(); + let (sra, cra) = ra.sin_cos(); + (cdec * cra, cdec * sra, sdec) +} + +/// Cartesian → spherical (ra in [0, 2π)). +#[inline] +fn cart_to_sph(x: f64, y: f64, z: f64) -> (Radian, Radian) { + // hypot(x,y) is stable for tiny x,y (near poles) + let rho = x.hypot(y); + + // declination robust near poles + let dec = z.atan2(rho); + + // RA: still fine, but ill-defined when rho ~ 0 (exact pole) + let ra = y.atan2(x).rem_euclid(TAU); + + (ra, dec) +} + +#[cfg(test)] +mod astro_math_tests { + use super::*; + use approx::abs_diff_eq; + use proptest::prelude::*; + use std::f64::consts::PI; + + const EPS: f64 = 1e-12; + const SMALL_EPS: f64 = 1e-9; + + /* ------------------------------ unit tests ------------------------------ */ + + #[test] + fn unit_vec_has_unit_norm() { + // Test a small grid in (ra, dec) and ensure |v| ≈ 1. + for &ra in &[0.0, PI / 6.0, PI / 2.0, PI, 3.0 * PI / 2.0] { + for &dec in &[-PI / 2.0 + 1e-3, -PI / 3.0, 0.0, PI / 4.0, PI / 2.0 - 1e-3] { + let v = unit_vec(ra, dec); + let norm2 = dot3(v, v); + assert!( + abs_diff_eq!(norm2, 1.0, epsilon = 1e-12), + "norm² must be ~1, got {norm2} for ra={ra}, dec={dec}" + ); + } + } + } + + #[test] + fn unit_vec_special_positions() { + // North pole: (ra arbitrary, dec = +π/2) → (0, 0, 1) + let v_n = unit_vec(1.234, PI / 2.0); + assert!(abs_diff_eq!(v_n[0], 0.0, epsilon = 1e-12)); + assert!(abs_diff_eq!(v_n[1], 0.0, epsilon = 1e-12)); + assert!(abs_diff_eq!(v_n[2], 1.0, epsilon = 1e-12)); + + // Equator: (ra = 0, dec = 0) → (1, 0, 0) + let v_e = unit_vec(0.0, 0.0); + assert!(abs_diff_eq!(v_e[0], 1.0, epsilon = 1e-12)); + assert!(abs_diff_eq!(v_e[1], 0.0, epsilon = 1e-12)); + assert!(abs_diff_eq!(v_e[2], 0.0, epsilon = 1e-12)); + } + + #[test] + fn dot3_behaves_as_dot_product() { + let a = [1.0, 2.0, 3.0]; + let b = [-2.0, 0.5, 4.0]; + let d = dot3(a, b); + let expected = 1.0 * -2.0 + 2.0 * 0.5 + 3.0 * 4.0; + assert!(abs_diff_eq!(d, expected, epsilon = EPS)); + } + + #[test] + fn wrap_pm_pi_output_range() { + let test_values = [ + 0.0, + PI, + -PI, + 3.0 * PI, + -3.0 * PI, + 10.0 * PI, + -10.0 * PI, + 1e6, + -1e6, + ]; + + for x in test_values { + let y = wrap_pm_pi(x); + assert!( + y >= -PI - 1e-12 && y < PI + 1e-12, + "wrap_pm_pi({x}) = {y} is not in [-π, π)" + ); + } + } + + #[test] + fn wrap_pm_pi_simple_cases() { + assert!(abs_diff_eq!(wrap_pm_pi(0.0), 0.0, epsilon = EPS)); + assert!(abs_diff_eq!(wrap_pm_pi(PI), -PI, epsilon = EPS)); + assert!(abs_diff_eq!(wrap_pm_pi(-PI), -PI, epsilon = EPS)); + assert!(abs_diff_eq!(wrap_pm_pi(3.0 * PI), -PI, epsilon = EPS)); + assert!(abs_diff_eq!(wrap_pm_pi(-3.0 * PI), -PI, epsilon = EPS)); + } + + #[test] + fn planar_offset_fast_zero_at_center() { + let ra0 = 1.0; + let dec0: f64 = 0.4; + let cos_dec0 = dec0.cos(); + + let (dx, dy) = planar_offset_fast(ra0, dec0, cos_dec0, ra0, dec0); + assert!(abs_diff_eq!(dx, 0.0, epsilon = EPS)); + assert!(abs_diff_eq!(dy, 0.0, epsilon = EPS)); + } + + #[test] + fn planar_offset_fast_small_offset_matches_delta() { + let ra0 = 1.0; + let dec0: f64 = 0.3; + let cos_dec0 = dec0.cos(); + + let d_ra = 1e-6; + let d_dec = -2e-6; + + // For small offsets, dx ≈ d_ra * cos(dec0), dy ≈ d_dec. + let (dx, dy) = planar_offset_fast(ra0, dec0, cos_dec0, ra0 + d_ra, dec0 + d_dec); + + assert!(abs_diff_eq!(dx, d_ra * cos_dec0, epsilon = 1e-12)); + assert!(abs_diff_eq!(dy, d_dec, epsilon = 1e-12)); + } + + #[test] + fn arcsec_to_rad_consistency() { + // 0 arcsec → 0 rad + assert!(abs_diff_eq!(arcsec_to_rad(0.0), 0.0, epsilon = EPS)); + + // 3600 arcsec = 1 degree = π/180 rad + let one_degree_rad = PI / 180.0; + let val = arcsec_to_rad(3600.0); + assert!(abs_diff_eq!(val, one_degree_rad, epsilon = 1e-12)); + } + + #[test] + fn ang_sep_basic_properties() { + // Same point → 0 + let d0 = ang_sep(1.0, 0.2, 1.0, 0.2); + assert!(abs_diff_eq!(d0, 0.0, epsilon = EPS)); + + // Antipodal points (0,0) and (π,0) → π + let d_pi = ang_sep(0.0, 0.0, PI, 0.0); + assert!(abs_diff_eq!(d_pi, PI, epsilon = 1e-12)); + + // Symmetry: sep(p1, p2) == sep(p2, p1) + let d1 = ang_sep(0.3, -0.1, 1.1, 0.5); + let d2 = ang_sep(1.1, 0.5, 0.3, -0.1); + assert!(abs_diff_eq!(d1, d2, epsilon = EPS)); + } + + #[test] + fn ang_sep_matches_unit_vec_dot_product() { + // Check consistency between spherical formula and dot3(unit_vec, unit_vec). + let points = &[ + (0.1, 0.0, 1.0, 0.2), + (2.0, 0.4, 2.2, 0.45), + (5.0, -0.3, 0.1, 0.1), + ]; + + for &(ra1, dec1, ra2, dec2) in points { + let d = ang_sep(ra1, dec1, ra2, dec2); + let v1 = unit_vec(ra1, dec1); + let v2 = unit_vec(ra2, dec2); + let cos_d = d.cos(); + let dot = dot3(v1, v2); + assert!( + abs_diff_eq!(cos_d, dot, epsilon = 1e-12), + "cos(d) and dot product mismatch: cos(d)={cos_d}, dot={dot}" + ); + } + } + + /* --------------------------- property-based tests --------------------------- */ + + fn ra_strategy() -> impl Strategy { + 0.0f64..(2.0 * PI) + } + + fn dec_strategy() -> impl Strategy { + // Avoid exactly ±π/2 to reduce numeric pathologies at poles. + let eps = 1e-6; + (-(PI / 2.0 - eps))..(PI / 2.0 - eps) + } + + proptest! { + #![proptest_config(ProptestConfig { + cases: 64, + .. ProptestConfig::default() + })] + + /// `unit_vec` must always output a vector of unit norm within numerical tolerance. + #[test] + fn prop_unit_vec_is_unit_length(ra in ra_strategy(), dec in dec_strategy()) { + let v = unit_vec(ra, dec); + let norm2 = dot3(v, v); + prop_assert!(abs_diff_eq!(norm2, 1.0, epsilon = 1e-12)); + } + + /// `wrap_pm_pi` always returns an angle in (−π, π]. + #[test] + fn prop_wrap_pm_pi_range(x in -1e6f64..1e6f64) { + let y = wrap_pm_pi(x); + prop_assert!(y > -PI - SMALL_EPS && y <= PI + SMALL_EPS); + } + + /// For small offsets, the tangent-plane distance approximates the great-circle distance. + #[test] + fn prop_planar_offset_approximates_ang_sep_small_offsets( + ra0 in ra_strategy(), + dec0 in dec_strategy(), + d_ra in -1e-4f64..1e-4f64, + d_dec in -1e-4f64..1e-4f64, + ) { + let cos_dec0 = dec0.cos(); + let ra = ra0 + d_ra; + let dec = dec0 + d_dec; + + let (dx, dy) = planar_offset_fast(ra0, dec0, cos_dec0, ra, dec); + let r_tan = (dx*dx + dy*dy).sqrt(); + + let d_sph = ang_sep(ra0, dec0, ra, dec); + + // Small-angle approximation: r_tan ≈ d_sph + let diff = (r_tan - d_sph).abs(); + prop_assert!(diff < 1e-6); + } + + /// `ang_sep` must be symmetric, non-negative, and ≤ π. + #[test] + fn prop_ang_sep_basic_properties( + ra1 in ra_strategy(), + dec1 in dec_strategy(), + ra2 in ra_strategy(), + dec2 in dec_strategy(), + ) { + let d12 = ang_sep(ra1, dec1, ra2, dec2); + let d21 = ang_sep(ra2, dec2, ra1, dec1); + + prop_assert!(d12 >= 0.0); + prop_assert!(d12 <= PI + SMALL_EPS); + prop_assert!(abs_diff_eq!(d12, d21, epsilon = 1e-12)); + } + + /// `ang_sep` must be consistent with the dot product of the corresponding unit vectors: + /// cos(d) ≈ unit_vec(p1) · unit_vec(p2). + #[test] + fn prop_ang_sep_matches_unit_vec_dot( + ra1 in ra_strategy(), + dec1 in dec_strategy(), + ra2 in ra_strategy(), + dec2 in dec_strategy(), + ) { + let d = ang_sep(ra1, dec1, ra2, dec2); + let v1 = unit_vec(ra1, dec1); + let v2 = unit_vec(ra2, dec2); + let cos_d = d.cos(); + let dot = dot3(v1, v2); + + prop_assert!(abs_diff_eq!(cos_d, dot, epsilon = 1e-12)); + } + } + + #[cfg(test)] + mod vincenty_tests { + use super::*; + use approx::abs_diff_eq; + use std::f64::consts::PI; + + const EPS: f64 = 1e-12; + const SMALL_EPS: f64 = 1e-9; + + /* ------------------------------ unit tests ------------------------------ */ + + #[test] + fn vincenty_zero_separation_same_point() { + let d = angular_separation_vincenty(1.234, 0.5, 1.234, 0.5); + assert!( + abs_diff_eq!(d, 0.0, epsilon = EPS), + "separation of identical points must be ~0, got {d}" + ); + } + + #[test] + fn vincenty_equator_quarter_circle() { + // (0, 0) to (π/2, 0) → quarter great circle → π/2 + let d = angular_separation_vincenty(0.0, 0.0, PI / 2.0, 0.0); + assert!( + abs_diff_eq!(d, PI / 2.0, epsilon = 1e-12), + "expected π/2, got {d}" + ); + } + + #[test] + fn vincenty_pole_quarter_circle() { + // (0, 0) to (0, π/2) → quarter great circle → π/2 + let d = angular_separation_vincenty(0.0, 0.0, 0.0, PI / 2.0); + assert!( + abs_diff_eq!(d, PI / 2.0, epsilon = 1e-12), + "expected π/2, got {d}" + ); + } + + #[test] + fn vincenty_antipodal_points() { + // (0, 0) and (π, 0) are antipodal → separation = π + let d = angular_separation_vincenty(0.0, 0.0, PI, 0.0); + assert!( + abs_diff_eq!(d, PI, epsilon = 1e-12), + "antipodal points must be ~π, got {d}" + ); + } + + #[test] + fn vincenty_symmetry() { + let lon1 = 0.7; + let lat1 = -0.3; + let lon2 = 2.1; + let lat2 = 0.4; + + let d12 = angular_separation_vincenty(lon1, lat1, lon2, lat2); + let d21 = angular_separation_vincenty(lon2, lat2, lon1, lat1); + + assert!( + abs_diff_eq!(d12, d21, epsilon = EPS), + "separation must be symmetric, d12={d12}, d21={d21}" + ); + } + + #[test] + fn vincenty_in_range_0_to_pi() { + let cases = &[ + (0.1, -0.2, 1.5, 0.7), + (2.3, 0.4, 0.5, -0.1), + (5.8, -1.0, 1.1, 1.0), + (0.0, 0.0, PI, 0.0), // antipodal + (0.0, 0.0, 0.0, PI / 2.0), // pole + ]; + + for &(lon1, lat1, lon2, lat2) in cases { + let d = angular_separation_vincenty(lon1, lat1, lon2, lat2); + assert!( + d >= 0.0 - 1e-15 && d <= PI + 1e-15, + "separation must be in [0, π], got {d}" + ); + } + } + + /* --------------------------- property-based tests --------------------------- */ + + fn lon_strategy() -> impl Strategy { + 0.0f64..(2.0 * PI) + } + + fn lat_strategy() -> impl Strategy { + // Avoid exactement ±π/2 pour réduire les pathologies numériques aux pôles. + let eps = 1e-9; + (-(PI / 2.0 - eps))..(PI / 2.0 - eps) + } + + proptest! { + #![proptest_config(ProptestConfig { + cases: 64, + .. ProptestConfig::default() + })] + + /// Separation is always non-negative, ≤ π, and symmetric. + #[test] + fn prop_vincenty_basic_properties( + lon1 in lon_strategy(), + lat1 in lat_strategy(), + lon2 in lon_strategy(), + lat2 in lat_strategy(), + ) { + let d12 = angular_separation_vincenty(lon1, lat1, lon2, lat2); + let d21 = angular_separation_vincenty(lon2, lat2, lon1, lat1); + + prop_assert!(d12 >= 0.0); + prop_assert!(d12 <= PI + SMALL_EPS); + prop_assert!(abs_diff_eq!(d12, d21, epsilon = 1e-12)); + } + + /// Vincenty separation of identical points is ~0. + #[test] + fn prop_vincenty_zero_for_identical_points( + lon in lon_strategy(), + lat in lat_strategy(), + ) { + let d = angular_separation_vincenty(lon, lat, lon, lat); + prop_assert!(abs_diff_eq!(d, 0.0, epsilon = 1e-12)); + } + + /// For generic points, Vincenty and cosine-law separation should agree + /// within a small tolerance (except for very small or very large angles). + #[test] + fn prop_vincenty_matches_cosine_law_most_of_the_time( + lon1 in lon_strategy(), + lat1 in lat_strategy(), + lon2 in lon_strategy(), + lat2 in lat_strategy(), + ) { + let d_vinc = angular_separation_vincenty(lon1, lat1, lon2, lat2); + let d_cos = ang_sep(lon1, lat1, lon2, lat2); // loi des cosinus + + let diff = (d_vinc - d_cos).abs(); + prop_assert!(diff < 1e-9, "Vincenty and cosine-law disagree: diff={diff}, d_vinc={d_vinc}, d_cos={d_cos}"); + } + } + } + + // ========================================================================= + // Unit tests — sph_to_cart / cart_to_sph + // ========================================================================= + + #[test] + fn sph_to_cart_produces_unit_vectors() { + let samples = &[ + (0.0, 0.0), + (PI / 3.0, 0.1), + (PI, 0.5), + (1.7 * PI, -0.4), + (0.0, PI / 2.0 - 1e-6), + (0.0, -PI / 2.0 + 1e-6), + ]; + + for &(ra, dec) in samples { + let (x, y, z) = sph_to_cart(ra, dec); + let r2 = x * x + y * y + z * z; + assert!( + abs_diff_eq!(r2, 1.0, epsilon = 1e-12), + "r2 = {r2} not close to 1.0" + ); + } + } + + #[test] + fn sph_cart_roundtrip_for_fixed_points() { + let samples = &[(0.0, 0.0), (PI / 3.0, 0.2), (2.3, -0.7), (5.0, 0.8)]; + + for &(ra, dec) in samples { + let (x, y, z) = sph_to_cart(ra, dec); + let (ra2, dec2) = cart_to_sph(x, y, z); + assert!(abs_diff_eq!(ra2, ra.rem_euclid(TAU), epsilon = 1e-12)); + assert!(abs_diff_eq!(dec2, dec, epsilon = 1e-12)); + } + } + + // ========================================================================= + // Property tests — sph_to_cart / cart_to_sph + // ========================================================================= + + fn any_ra() -> impl Strategy { + 0.0f64..TAU + } + + fn any_dec() -> impl Strategy { + (-PI / 2.0 + 1e-6)..(PI / 2.0 - 1e-6) + } + + proptest! { + #[test] + fn prop_sph_cart_is_roundtrip(ra in any_ra(), dec in any_dec()) { + let (x, y, z) = sph_to_cart(ra, dec); + let norm = (x*x + y*y + z*z).sqrt(); + prop_assert!(abs_diff_eq!(norm, 1.0, epsilon = 1e-12)); + + let (ra2, dec2) = cart_to_sph(x, y, z); + + // Check vector roundtrip (always well-defined) + let (x2, y2, z2) = sph_to_cart(ra2, dec2); + prop_assert!(abs_diff_eq!(x2, x, epsilon = 1e-12)); + prop_assert!(abs_diff_eq!(y2, y, epsilon = 1e-12)); + prop_assert!(abs_diff_eq!(z2, z, epsilon = 1e-12)); + + // Optional: check angles when RA is well-conditioned + let rho = x.hypot(y); + if rho > 1e-10 { + prop_assert!(abs_diff_eq!(ra2, ra.rem_euclid(TAU), epsilon = 1e-12)); + } + prop_assert!(abs_diff_eq!(dec2, dec, epsilon = 1e-12)); + } + } + + // ========================================================================= + // Unit tests — spherical_midpoint + // ========================================================================= + + #[test] + fn spherical_midpoint_of_identical_directions_is_itself() { + let samples = &[(0.5, 0.2), (2.1, -0.3), (5.9, 0.7)]; + + for &(ra, dec) in samples { + let (ram, decm) = spherical_midpoint(ra, dec, ra, dec); + assert!(abs_diff_eq!(ram, ra.rem_euclid(TAU), epsilon = 1e-12)); + assert!(abs_diff_eq!(decm, dec, epsilon = 1e-12)); + } + } + + #[test] + fn spherical_midpoint_is_symmetric_in_arguments() { + let a = (0.3, 0.1); + let b = (1.7, -0.2); + + let (ra_ab, dec_ab) = spherical_midpoint(a.0, a.1, b.0, b.1); + let (ra_ba, dec_ba) = spherical_midpoint(b.0, b.1, a.0, a.1); + + assert!(abs_diff_eq!(ra_ab, ra_ba, epsilon = 1e-12)); + assert!(abs_diff_eq!(dec_ab, dec_ba, epsilon = 1e-12)); + } + + #[test] + fn spherical_midpoint_handles_nearly_antipodal_without_nan() { + let ra1 = 0.0; + let dec1 = 0.0; + let ra2 = PI; + let dec2 = 0.0; + + let (ram, decm) = spherical_midpoint(ra1, dec1, ra2, dec2); + assert!(ram.is_finite()); + assert!(decm.is_finite()); + + let (x, y, z) = sph_to_cart(ram, decm); + let r2 = x * x + y * y + z * z; + assert!(abs_diff_eq!(r2, 1.0, epsilon = 1e-12)); + } + + proptest! { + #[test] + fn prop_spherical_midpoint_on_unit_sphere( + ra1 in any_ra(), + dec1 in any_dec(), + ra2 in any_ra(), + dec2 in any_dec(), + ) { + let (ram, decm) = spherical_midpoint(ra1, dec1, ra2, dec2); + + prop_assert!(ram.is_finite()); + prop_assert!(decm.is_finite()); + + let (x, y, z) = sph_to_cart(ram, decm); + let r2 = x*x + y*y + z*z; + prop_assert!(abs_diff_eq!(r2, 1.0, epsilon = 1e-12)); + } + } + + // ========================================================================= + // Unit tests — gnomonic projection / inverse + // ========================================================================= + + #[test] + fn gnomonic_projection_of_center_is_zero() { + let ra0 = 1.2; + let dec0 = 0.3; + + let [x, y] = radec_to_tangent(ra0, dec0, ra0, dec0); + assert!(abs_diff_eq!(x, 0.0, epsilon = 1e-15)); + assert!(abs_diff_eq!(y, 0.0, epsilon = 1e-15)); + + let (ra2, dec2) = tangent_to_radec(0.0, 0.0, ra0, dec0); + assert!(abs_diff_eq!(ra2, ra0.rem_euclid(TAU), epsilon = 1e-15)); + assert!(abs_diff_eq!(dec2, dec0, epsilon = 1e-15)); + } + + #[test] + fn radec_tangent_handles_cosc_zero_without_nan() { + let ra0 = 0.0; + let dec0 = 0.0; + + let ra = PI / 2.0; + let dec = 0.0; + + let [x, y] = radec_to_tangent(ra, dec, ra0, dec0); + assert!(x.is_finite()); + assert!(y.is_finite()); + } + + #[test] + fn tangent_to_radec_handles_rho_zero_gracefully() { + let ra0 = 2.0; + let dec0 = -0.4; + + let (ra2, dec2) = tangent_to_radec(0.0, 0.0, ra0, dec0); + assert!(abs_diff_eq!(ra2, ra0.rem_euclid(TAU), epsilon = 1e-15)); + assert!(abs_diff_eq!(dec2, dec0, epsilon = 1e-15)); + } + + fn small_plane() -> impl Strategy { + -5e-3f64..5e-3 + } + + proptest! { + #[test] + fn prop_gnomonic_roundtrip_small_offsets( + ra0 in any_ra(), + dec0 in -0.8f64..0.8, + dx in small_plane(), + dy in small_plane(), + ) { + let (ra, dec) = tangent_to_radec(dx, dy, ra0, dec0); + let [x2, y2] = radec_to_tangent(ra, dec, ra0, dec0); + + prop_assert!(x2.is_finite() && y2.is_finite()); + prop_assert!(abs_diff_eq!(x2, dx, epsilon = 5e-12)); + prop_assert!(abs_diff_eq!(y2, dy, epsilon = 5e-12)); + } + + #[test] + fn prop_gnomonic_inverse_is_locally_stable( + ra in any_ra(), + dec in -0.8f64..0.8, + d_ra in -5e-3f64..5e-3, + d_dec in -5e-3f64..5e-3, + ) { + let ra0 = (ra + d_ra).rem_euclid(TAU); + let dec0 = (dec + d_dec).clamp(-0.8, 0.8); + + let [x, y] = radec_to_tangent(ra, dec, ra0, dec0); + let (ra2, dec2) = tangent_to_radec(x, y, ra0, dec0); + + prop_assert!(ra2.is_finite() && dec2.is_finite()); + prop_assert!(abs_diff_eq!(ra2, ra.rem_euclid(TAU), epsilon = 5e-12)); + prop_assert!(abs_diff_eq!(dec2, dec, epsilon = 5e-12)); + } + } + + // ========================================================================= + // Unit tests — fit_quad_1d + // ========================================================================= + + #[test] + fn fit_quad_1d_exact_recovery_for_known_quadratic() { + // Modèle : x(t) = p0 + v t + 0.5 a t^2 + let p0_true = 0.3; + let v_true = -0.2; + let a_true = 0.05; + + let dt = [-1.0, 0.0, 2.0]; + let x = dt.map(|t| p0_true + v_true * t + 0.5 * a_true * t * t); + + let (p0, v, a) = fit_quad_1d(dt, x); + + assert!(abs_diff_eq!(p0, p0_true, epsilon = 1e-12)); + assert!(abs_diff_eq!(v, v_true, epsilon = 1e-12)); + assert!(abs_diff_eq!(a, a_true, epsilon = 1e-12)); + } + + proptest! { + #[test] + fn prop_fit_quad_1d_recovers_coeffs( + t0 in -1.0f64..0.0, + t1 in 0.0f64..1.0, + t2 in 1.1f64..2.0, + p0_true in -1.0f64..1.0, + v_true in -1.0f64..1.0, + a_true in -1.0f64..1.0, + ) { + prop_assume!(t0 < t1 && t1 < t2); + + let dt = [t0, t1, t2]; + let x = dt.map(|t| p0_true + v_true * t + 0.5 * a_true * t * t); + + let (p0, v, a) = fit_quad_1d(dt, x); + + prop_assert!(abs_diff_eq!(p0, p0_true, epsilon = 1e-10)); + prop_assert!(abs_diff_eq!(v, v_true, epsilon = 1e-10)); + prop_assert!(abs_diff_eq!(a, a_true, epsilon = 1e-10)); + } + } + + // ========================================================================= + // Unit tests — lambda_max_2x2 + // ========================================================================= + + #[test] + fn lambda_max_2x2_of_diagonal_is_max_diagonal() { + let a = [[2.0, 0.0], [0.0, 5.0]]; + let lmax = lambda_max_2x2(a); + assert!(abs_diff_eq!(lmax, 5.0, epsilon = 1e-15)); + } + + #[test] + fn lambda_max_2x2_respects_shift_by_identity() { + let a = [[2.0, 0.3], [0.3, 1.0]]; + let c = 4.0; + + let a_shifted = [[a[0][0] + c, a[0][1]], [a[1][0], a[1][1] + c]]; + + let lmax = lambda_max_2x2(a); + let lmax_shifted = lambda_max_2x2(a_shifted); + + assert!(abs_diff_eq!(lmax_shifted, lmax + c, epsilon = 1e-12)); + } + + #[test] + fn lambda_max_2x2_non_negative_for_simple_covariances() { + let a = [[1e-4, 2e-5], [2e-5, 3e-4]]; + let lmax = lambda_max_2x2(a); + assert!(lmax >= 0.0); + } + + proptest! { + #[test] + fn prop_lambda_max_2x2_scales_with_positive_factor( + a11 in 0.0f64..1e3, + a22 in 0.0f64..1e3, + a12 in -1e2f64..1e2, + k in 0.0f64..1e3 + ) { + let a = [[a11, a12], [a12, a22]]; + let l = lambda_max_2x2(a); + + let a_scaled = [[k * a11, k * a12], [k * a12, k * a22]]; + let l_scaled = lambda_max_2x2(a_scaled); + + if k == 0.0 { + prop_assert!(abs_diff_eq!(l_scaled, 0.0, epsilon = 1e-12)); + } else { + prop_assert!(abs_diff_eq!( + l_scaled, + k * l, + epsilon = 1e-9 * (1.0 + k.abs().max(l.abs())) + )); + } + } + } + + #[cfg(test)] + mod algebra2_tests { + use super::*; + use approx::assert_abs_diff_eq; + + // ----------------------------------------------------------------------------- + // Helpers for tests + // ----------------------------------------------------------------------------- + + #[inline] + fn is_finite_mat2(m: [[f64; 2]; 2]) -> bool { + m[0][0].is_finite() && m[0][1].is_finite() && m[1][0].is_finite() && m[1][1].is_finite() + } + + #[inline] + fn is_finite_vec2(v: [f64; 2]) -> bool { + v[0].is_finite() && v[1].is_finite() + } + + // A reasonable range for random floats to avoid overflow in products. + fn finite_f64() -> impl Strategy { + -1.0e150f64..=1.0e150f64 + } + + // Generate a symmetric positive-definite (SPD) 2x2 matrix: + // [[a, b], + // [b, d]] + // with det > 0 and a,d > 0. + fn spd_sym_2x2() -> BoxedStrategy<[[f64; 2]; 2]> { + // Keep values moderate to avoid pathological conditioning. + ( + 1.0e-6f64..=1.0e3f64, + 1.0e-6f64..=1.0e3f64, + -0.999f64..=0.999f64, + ) + .prop_map(|(a, d, rho)| { + // b = rho * sqrt(a*d) ensures |b| < sqrt(a*d) => det > 0 + let b = rho * (a * d).sqrt(); + [[a, b], [b, d]] + }) + .boxed() + } + + // ----------------------------------------------------------------------------- + // Unit tests + // ----------------------------------------------------------------------------- + + #[test] + fn dot2_matches_manual() { + let a = [1.0, 2.0]; + let b = [3.0, 4.0]; + assert_abs_diff_eq!(dot2(a, b), 11.0, epsilon = 0.0); + } + + #[test] + fn mat_vec2_matches_manual() { + let m = [[1.0, 2.0], [3.0, 4.0]]; + let v = [5.0, 6.0]; + let out = mat_vec2(m, v); + assert_abs_diff_eq!(out[0], 1.0 * 5.0 + 2.0 * 6.0, epsilon = 0.0); + assert_abs_diff_eq!(out[1], 3.0 * 5.0 + 4.0 * 6.0, epsilon = 0.0); + } + + #[test] + fn clamp_unit_basic_cases() { + assert_abs_diff_eq!(clamp_unit(0.5), 0.5, epsilon = 0.0); + assert_abs_diff_eq!(clamp_unit(2.0), 1.0, epsilon = 0.0); + assert_abs_diff_eq!(clamp_unit(-2.0), -1.0, epsilon = 0.0); + } + + #[test] + fn clamp_unit_non_finite_returns_zero() { + assert_abs_diff_eq!(clamp_unit(f64::NAN), 0.0, epsilon = 0.0); + assert_abs_diff_eq!(clamp_unit(f64::INFINITY), 0.0, epsilon = 0.0); + assert_abs_diff_eq!(clamp_unit(f64::NEG_INFINITY), 0.0, epsilon = 0.0); + } + + #[test] + fn safe_ln_basic_cases() { + assert_abs_diff_eq!(safe_ln(1.0), 0.0, epsilon = 0.0); + assert_abs_diff_eq!(safe_ln(std::f64::consts::E), 1.0, epsilon = 1e-15); + assert_abs_diff_eq!(safe_ln(0.0), 0.0, epsilon = 0.0); + assert_abs_diff_eq!(safe_ln(-1.0), 0.0, epsilon = 0.0); + } + + #[test] + fn safe_ln_non_finite_returns_zero() { + assert_abs_diff_eq!(safe_ln(f64::NAN), 0.0, epsilon = 0.0); + assert_abs_diff_eq!(safe_ln(f64::INFINITY), 0.0, epsilon = 0.0); + assert_abs_diff_eq!(safe_ln(f64::NEG_INFINITY), 0.0, epsilon = 0.0); + } + + #[test] + fn trace_2x2_basic() { + let m = [[1.0, 2.0], [3.0, 4.0]]; + assert_abs_diff_eq!(trace_2x2(m), 5.0, epsilon = 0.0); + } + + #[test] + fn invert_sym_2x2_identity() { + let floor = 1e-20; + let m = [[1.0, 0.0], [0.0, 1.0]]; + let inv = invert_sym_2x2(m, floor); + + assert_abs_diff_eq!(inv[0][0], 1.0, epsilon = 0.0); + assert_abs_diff_eq!(inv[0][1], 0.0, epsilon = 0.0); + assert_abs_diff_eq!(inv[1][0], 0.0, epsilon = 0.0); + assert_abs_diff_eq!(inv[1][1], 1.0, epsilon = 0.0); + } + + #[test] + fn invert_sym_2x2_near_singular_falls_back_to_diagonal() { + let floor = 1e-12; + + // a*d - b^2 == 0 -> singular (exact). Should trigger fallback. + let a: f64 = 2.0; + let d = 8.0; + let b = (a * d).sqrt(); // det = 0 + let m = [[a, b], [b, d]]; + + let inv = invert_sym_2x2(m, floor); + + // Fallback should return diagonal inverse (off-diagonals ~0). + assert_abs_diff_eq!(inv[0][1], 0.0, epsilon = 0.0); + assert_abs_diff_eq!(inv[1][0], 0.0, epsilon = 0.0); + assert_abs_diff_eq!(inv[0][0], 1.0 / a, epsilon = 1e-15); + assert_abs_diff_eq!(inv[1][1], 1.0 / d, epsilon = 1e-15); + } + + // ----------------------------------------------------------------------------- + // Property-based tests + // ----------------------------------------------------------------------------- + + proptest! { + #[test] + fn prop_dot2_is_commutative( + a in finite_f64(), b in finite_f64(), c in finite_f64(), d in finite_f64() + ) { + let u = [a, b]; + let v = [c, d]; + let uv = dot2(u, v); + let vu = dot2(v, u); + + prop_assert!(uv.is_finite() && vu.is_finite()); + let tol = 1e-12 * (uv.abs().max(vu.abs()).max(1.0)); + prop_assert!((uv - vu).abs() <= tol); + } + + #[test] + fn prop_dot2_linearity_in_first_argument( + a in finite_f64(), b in finite_f64(), + c in finite_f64(), d in finite_f64(), + e in finite_f64(), f in finite_f64(), + ) { + let u = [a, b]; + let v = [c, d]; + let w = [e, f]; + + let uv = [u[0] + v[0], u[1] + v[1]]; + let lhs = dot2(uv, w); + let rhs = dot2(u, w) + dot2(v, w); + + prop_assert!(lhs.is_finite() && rhs.is_finite()); + let tol = 1e-9 * (lhs.abs().max(rhs.abs()).max(1.0)); + prop_assert!((lhs - rhs).abs() <= tol); + } + + #[test] + fn prop_mat_vec2_matches_expanded( + m00 in finite_f64(), m01 in finite_f64(), m10 in finite_f64(), m11 in finite_f64(), + v0 in finite_f64(), v1 in finite_f64() + ) { + let m = [[m00, m01], [m10, m11]]; + let v = [v0, v1]; + let out = mat_vec2(m, v); + + let exp0 = m00 * v0 + m01 * v1; + let exp1 = m10 * v0 + m11 * v1; + + prop_assert!(out[0].is_finite() && out[1].is_finite()); + prop_assert!((out[0] - exp0).abs() <= 0.0); + prop_assert!((out[1] - exp1).abs() <= 0.0); + } + + #[test] + fn prop_mat_vec2_is_linear_in_vector( + m00 in finite_f64(), m01 in finite_f64(), m10 in finite_f64(), m11 in finite_f64(), + a in finite_f64(), b in finite_f64(), + c in finite_f64(), d in finite_f64(), + ) { + let m = [[m00, m01], [m10, m11]]; + let u = [a, b]; + let v = [c, d]; + let uv = [u[0] + v[0], u[1] + v[1]]; + + let lhs = mat_vec2(m, uv); + let rhs_u = mat_vec2(m, u); + let rhs_v = mat_vec2(m, v); + let rhs = [rhs_u[0] + rhs_v[0], rhs_u[1] + rhs_v[1]]; + + prop_assert!(is_finite_vec2(lhs) && is_finite_vec2(rhs)); + let tol0 = 1e-9 * (lhs[0].abs().max(rhs[0].abs()).max(1.0)); + let tol1 = 1e-9 * (lhs[1].abs().max(rhs[1].abs()).max(1.0)); + prop_assert!((lhs[0] - rhs[0]).abs() <= tol0); + prop_assert!((lhs[1] - rhs[1]).abs() <= tol1); + } + + #[test] + fn prop_clamp_unit_bounds(x in any::()) { + let y = clamp_unit(x); + prop_assert!(y.is_finite()); + prop_assert!(y >= -1.0 && y <= 1.0); + } + + #[test] + fn prop_safe_ln_behavior(x in any::()) { + let y = safe_ln(x); + prop_assert!(y.is_finite()); + + if x.is_finite() && x > 0.0 { + prop_assert!((y - x.ln()).abs() <= 0.0); + } else { + prop_assert!(y == 0.0); + } + } + + #[test] + fn prop_trace_2x2_matches_sum( + m00 in finite_f64(), m01 in finite_f64(), m10 in finite_f64(), m11 in finite_f64() + ) { + let m = [[m00, m01], [m10, m11]]; + let tr = trace_2x2(m); + prop_assert!(tr.is_finite()); + prop_assert!((tr - (m00 + m11)).abs() <= 0.0); + } + + #[test] + fn prop_invert_sym_2x2_spd_is_inverse_both_sides_and_symmetric_when_not_fallback( + m in spd_sym_2x2(), + floor in 1e-20f64..=1e-12f64 + ) { + // SPD => determinant should be > 0. + let det = det_sym_2x2(m); + prop_assert!(det.is_finite() && det > 0.0); + + let inv = invert_sym_2x2(m, floor); + prop_assert!(is_finite_mat2(inv)); + + // Check inv * m ≈ I + let left = mat_mul2(inv, m); + prop_assert!(is_finite_mat2(left)); + + // Check m * inv ≈ I + let right = mat_mul2(m, inv); + prop_assert!(is_finite_mat2(right)); + + // Tolerance: moderate since m range is controlled. + let eps = 1e-9; + + // inv*m + prop_assert!((left[0][0] - 1.0).abs() <= eps); + prop_assert!(left[0][1].abs() <= eps); + prop_assert!(left[1][0].abs() <= eps); + prop_assert!((left[1][1] - 1.0).abs() <= eps); + + // m*inv + prop_assert!((right[0][0] - 1.0).abs() <= eps); + prop_assert!(right[0][1].abs() <= eps); + prop_assert!(right[1][0].abs() <= eps); + prop_assert!((right[1][1] - 1.0).abs() <= eps); + + // Symmetry check for the non-fallback path: + // For SPD and "reasonable" floor, we expect det > floor => we should not fallback. + // In that case, the inverse of a symmetric matrix should also be symmetric. + if det > floor { + let tol = 1e-12 * inv[0][1].abs().max(inv[1][0].abs()).max(1.0); + prop_assert!((inv[0][1] - inv[1][0]).abs() <= tol); + } + } + + #[test] + fn prop_invert_sym_2x2_output_is_finite_for_reasonable_inputs( + a in finite_f64(), b in finite_f64(), d in finite_f64(), + floor in 1e-20f64..=1e-12f64 + ) { + // General symmetric matrix (may be indefinite). + let m = [[a, b], [b, d]]; + let inv = invert_sym_2x2(m, floor); + + // Even with odd inputs, we expect finite output due to flooring and fallback. + prop_assert!(is_finite_mat2(inv)); + } + } + } + + #[cfg(test)] + mod cholesky_tests { + use super::*; + use approx::assert_relative_eq; + + /// Compute L * L^T for a lower-triangular 2×2 matrix: + /// L = [[l00, 0], [l10, l11]] + #[inline] + fn ll_t(l: [[f64; 2]; 2]) -> [[f64; 2]; 2] { + let l00 = l[0][0]; + let l10 = l[1][0]; + let l11 = l[1][1]; + + [[l00 * l00, l00 * l10], [l00 * l10, l10 * l10 + l11 * l11]] + } + + /// Symmetrize a 2×2 matrix (used for comparison since the API expects symmetric input). + #[inline] + fn sym(m: [[f64; 2]; 2]) -> [[f64; 2]; 2] { + let b = 0.5 * (m[0][1] + m[1][0]); + [[m[0][0], b], [b, m[1][1]]] + } + + /// Add `eps * I` to a 2×2 matrix. + #[inline] + fn add_eps_i(m: [[f64; 2]; 2], eps: f64) -> [[f64; 2]; 2] { + [[m[0][0] + eps, m[0][1]], [m[1][0], m[1][1] + eps]] + } + + /// Construct SPD matrix from any 2×2 A via M = A A^T + eps I. + #[inline] + fn make_spd_from_a(a: [[f64; 2]; 2], eps: f64) -> [[f64; 2]; 2] { + // A A^T + let m00 = a[0][0] * a[0][0] + a[0][1] * a[0][1]; + let m01 = a[0][0] * a[1][0] + a[0][1] * a[1][1]; + let m11 = a[1][0] * a[1][0] + a[1][1] * a[1][1]; + add_eps_i([[m00, m01], [m01, m11]], eps) + } + + #[test] + fn cholesky_identity() { + let m = [[1.0, 0.0], [0.0, 1.0]]; + let l = cholesky_lower_sym_2x2(m, 1e-20).expect("I should be SPD"); + assert_relative_eq!(l[0][0], 1.0, max_relative = 1e-12); + assert_relative_eq!(l[0][1], 0.0, max_relative = 1e-12); + assert_relative_eq!(l[1][1], 1.0, max_relative = 1e-12); + + let recon = ll_t(l); + assert_relative_eq!(recon[0][0], 1.0, max_relative = 1e-12); + assert_relative_eq!(recon[0][1], 0.0, max_relative = 1e-12); + assert_relative_eq!(recon[1][0], 0.0, max_relative = 1e-12); + assert_relative_eq!(recon[1][1], 1.0, max_relative = 1e-12); + } + + #[test] + fn cholesky_simple_spd() { + // SPD: [[4, 2], [2, 3]] + let m = [[4.0, 2.0], [2.0, 3.0]]; + let l = cholesky_lower_sym_2x2(m, 1e-20).expect("matrix should be SPD"); + + // L should be lower triangular with positive diagonal. + assert!(l[0][0] > 0.0); + assert_relative_eq!(l[0][1], 0.0, max_relative = 0.0); + assert!(l[1][1] > 0.0); + + // Reconstruction must match. + let recon = ll_t(l); + let ms = sym(m); + assert_relative_eq!(recon[0][0], ms[0][0], max_relative = 1e-12); + assert_relative_eq!(recon[0][1], ms[0][1], max_relative = 1e-12); + assert_relative_eq!(recon[1][0], ms[1][0], max_relative = 1e-12); + assert_relative_eq!(recon[1][1], ms[1][1], max_relative = 1e-12); + } + + #[test] + fn cholesky_rejects_non_spd() { + // Not SPD: determinant <= 0 (even with positive diagonal) + let m = [[1.0, 2.0], [2.0, 1.0]]; // det = -3 + assert!(cholesky_lower_sym_2x2(m, 1e-20).is_none()); + + // Non-finite input should be rejected. + let m_nan = [[f64::NAN, 0.0], [0.0, 1.0]]; + assert!(cholesky_lower_sym_2x2(m_nan, 1e-20).is_none()); + + // Negative diagonal is *floored* and can become factorisable. + let m_neg = [[-1.0, 0.0], [0.0, 1.0]]; + assert!(cholesky_lower_sym_2x2(m_neg, 1e-20).is_some()); + } + + #[test] + fn cholesky_floors_small_diagonal() { + let m = [[0.0, 0.0], [0.0, 0.0]]; + let floor = 1e-6; + + let l = cholesky_lower_sym_2x2(m, floor).expect("floored zero matrix should factorize"); + + // Triangular + positive diagonal + assert!(l[0][0] > 0.0); + assert_eq!(l[0][1], 0.0); + assert!(l[1][1] > 0.0); + + let recon = ll_t(l); + assert_relative_eq!(recon[0][0], floor, max_relative = 1e-12); + assert_relative_eq!(recon[1][1], floor, max_relative = 1e-12); + assert_relative_eq!(recon[0][1], 0.0, max_relative = 0.0); + assert_relative_eq!(recon[1][0], 0.0, max_relative = 0.0); + } + + // ------------------------------------------------------------------------- + // Property-based tests + // ------------------------------------------------------------------------- + + proptest! { + #[test] + fn prop_cholesky_reconstructs_spd( + a00 in -10.0f64..10.0, + a01 in -10.0f64..10.0, + a10 in -10.0f64..10.0, + a11 in -10.0f64..10.0, + ) { + // Build SPD matrix from arbitrary A: M = A A^T + eps I. + let a = [[a00, a01], [a10, a11]]; + let eps = 1e-3; + let m = make_spd_from_a(a, eps); + + let floor = 1e-20; + let l = cholesky_lower_sym_2x2(m, floor).expect("constructed SPD must factorize"); + + // Invariants: lower-triangular and positive diagonal. + prop_assert!(l[0][0].is_finite() && l[0][0] > 0.0); + prop_assert!(l[1][1].is_finite() && l[1][1] > 0.0); + prop_assert_eq!(l[0][1], 0.0); + + // Reconstruction accuracy: L L^T ≈ M (relative tolerance). + let recon = ll_t(l); + + // Use a tolerance that scales with the matrix magnitude. + // This avoids flaky failures when M is very large. + let scale = m[0][0].abs().max(m[1][1].abs()).max(1.0); + let tol = 1e-10 * scale; + + prop_assert!((recon[0][0] - m[0][0]).abs() <= tol); + prop_assert!((recon[0][1] - m[0][1]).abs() <= tol); + prop_assert!((recon[1][0] - m[1][0]).abs() <= tol); + prop_assert!((recon[1][1] - m[1][1]).abs() <= tol); + } + + #[test] + fn prop_cholesky_matches_spd_conditions( + a in 1e-6f64..100.0, + d in 1e-6f64..100.0, + b in -10.0f64..10.0, + ) { + // For symmetric 2×2: SPD iff a>0 and det>0. + // We'll craft det using a, d, b and check behavior. + let m = [[a, b], [b, d]]; + let det = det_sym_2x2(m); + + let floor = 1e-20; + let chol = cholesky_lower_sym_2x2(m, floor); + + if det > 0.0 { + // It *should* factorize (almost always) for positive det and positive diag. + // There are rare numeric edge cases, but with these ranges it should pass. + prop_assert!(chol.is_some()); + } else { + prop_assert!(chol.is_none()); + } + } + } + } +} diff --git a/crates/fink-fat-engine/src/display_format.rs b/crates/fink-fat-engine/src/display_format.rs new file mode 100644 index 00000000..8b119fe0 --- /dev/null +++ b/crates/fink-fat-engine/src/display_format.rs @@ -0,0 +1,56 @@ +/// Indent a multi-line string by a fixed number of spaces. +/// +/// This helper is primarily intended for **pretty-printing** complex objects +/// (e.g. nested `Display` implementations) in a human-readable, indented form. +/// +/// Each line of the input string `s` is prefixed with `spaces` ASCII spaces. +/// Line breaks are preserved exactly as in the original string. +/// +/// This function performs **no trimming or normalization**: +/// - empty lines are kept, +/// - leading/trailing whitespace in `s` is preserved, +/// - indentation is applied uniformly to all lines. +/// +/// Parameters +/// ---------- +/// s : &str +/// Input string, potentially spanning multiple lines. +/// spaces : usize +/// Number of spaces to prepend to each line. +/// +/// Returns +/// ------- +/// String +/// A new string where each line of `s` is indented by `spaces` spaces. +/// +/// Notes +/// ----- +/// This utility is intentionally simple and allocation-friendly: +/// - it allocates a single padding string, +/// - it allocates a new `String` for the final result. +/// +/// It is well-suited for formatting diagnostic output, CLI reports, +/// and structured `Display` implementations, but should not be used +/// in performance-critical inner loops. +pub fn indent_block(s: &str, spaces: usize) -> String { + let pad = " ".repeat(spaces); + s.lines() + .map(|line| format!("{pad}{line}")) + .collect::>() + .join("\n") +} + +/// Format a 2D vector as "(x, y)" with a compact scientific style. +pub fn fmt_vec2(v: [f64; 2]) -> String { + // Keep it short but stable for QA logs. + // Use scientific notation: good for small rad values. + format!("({:.6e}, {:.6e})", v[0], v[1]) +} + +/// Format a 2x2 matrix as "[[a, b], [c, d]]" with compact scientific notation. +pub fn fmt_mat2(m: [[f64; 2]; 2]) -> String { + format!( + "[[{:.6e}, {:.6e}], [{:.6e}, {:.6e}]]", + m[0][0], m[0][1], m[1][0], m[1][1] + ) +} diff --git a/crates/fink-fat-engine/src/engine_config/edge_config.rs b/crates/fink-fat-engine/src/engine_config/edge_config.rs new file mode 100644 index 00000000..38a1cb68 --- /dev/null +++ b/crates/fink-fat-engine/src/engine_config/edge_config.rs @@ -0,0 +1,602 @@ +//! # Inter-night edge construction configuration (`EdgeConfig`) +//! +//! This module defines the runtime configuration used to build **directed inter-night +//! edges** between seeds, i.e. potential links `from -> to` where `night(to) > night(from)`. +//! +//! In the engine pipeline, edge construction typically sits after seeding (pairs/triplets) +//! and before connected-components / solving. Its core responsibilities are: +//! +//! 1) **Candidate retrieval**: for each left seed, retrieve a set of plausible right seeds +//! from a spatial/time index (cone query around the propagated prediction). +//! 2) **Feature computation**: compute `EdgeFeatures` for each candidate edge. +//! 3) **Cost assignment**: derive a solver-facing `cost` from features. +//! 4) **Optional Top-K pruning**: keep only the `top_k_per_left` lowest-cost candidates +//! per left seed (physics-based ranking, no ONNX required). +//! 5) **Optional ML post-filter**: score the retained edges with an ONNX classifier and +//! discard those below a probability threshold (`ml_post_filter`). +//! +//! ----------------------------------------------------------------------------- +//! Operational modes +//! ----------------------------------------------------------------------------- +//! +//! The two orthogonal configuration axes are: +//! +//! - **`top_k_per_left`** — controls *whether* and *how many* candidates survive +//! the physics-based cost filter per left seed. +//! - **`ml_post_filter`** — controls whether the surviving edges are further filtered +//! by an ONNX classifier applied once on the entire retained set. +//! +//! ## 1) No filtering (`top_k_per_left = None`, `ml_post_filter = false`) +//! +//! Intended use-cases: +//! - debugging candidate retrieval, +//! - generating exhaustive datasets for offline training, +//! - validating physics-only gating and feature distributions. +//! +//! Behavior: +//! - for each left seed, iterate all candidates returned by the candidate generator, +//! - compute `EdgeFeatures` and edge cost for each candidate, +//! - emit all edges without any pruning. +//! +//! Consequences: +//! - the edge set can become very large (fan-out grows quickly with cone size), +//! - connected components become denser and solvers cost more, +//! - deterministic and simple — no ranking required. +//! +//! ## 2) Cost-based Top-K (`top_k_per_left = Some(k)`, `ml_post_filter = false`) +//! +//! Intended use-cases: +//! - production-scale runs without an ONNX model, +//! - controlling fan-out per left seed with physics-derived ranking. +//! +//! Behavior (per-left seed): +//! - retrieve candidates, +//! - compute `EdgeFeatures` and edge cost, +//! - retain only the `k` candidates with the **lowest cost**, +//! - emit edges for those winners only. +//! +//! Notes: +//! - No ONNX model is required. +//! - Cost ranking uses the function configured in `cost` (`CostConfig`). +//! - Top-K selection is implemented with a fixed-capacity min-heap so memory stays +//! bounded by $O(K)$ per left seed (see `ranking_topk`). +//! +//! ## 3) Cost-based Top-K + ML post-filter +//! (`top_k_per_left = Some(k)`, `ml_post_filter = true`) +//! +//! Intended use-cases: +//! - production-scale runs with a trained ONNX edge classifier, +//! - highest-purity edge selection before connected components / solvers. +//! +//! Behavior: +//! 1. For each left seed, apply cost-based Top-K as in mode 2 above. +//! 2. After all left seeds are processed, score the **entire retained edge set** +//! with the ONNX classifier in batches of `onnx_batch_size`. +//! 3. Discard any edge whose `p(class=1)` is below `ml_post_filter_threshold`. +//! +//! Notes: +//! - ONNX inference runs **once**, on the already-pruned set — much cheaper than +//! scoring every raw candidate. +//! - ML inference is performed by `EdgeRankingModel` / `EdgeRankingModelPool` +//! (see `edge_prediction`). +//! - Requires `edge_ranking_model_path` and a live `EdgeRankingModelPool`. +//! +//! ----------------------------------------------------------------------------- +//! Parallelism and chunking +//! ----------------------------------------------------------------------------- +//! +//! Edge construction is naturally parallelizable over the **left** seeds. +//! +//! If `parallel_left_batches = true`, the engine may process left seeds in +//! Rayon using chunked parallel iteration (conceptually `left.par_chunks(...)`). +//! The chunk size is controlled by `parallel_left_batch_size`. +//! +//! Rationale for chunking: +//! - amortize per-chunk setup costs (e.g., index references), +//! - limit per-thread working set, +//! - reduce overhead from scheduling extremely small tasks. +//! +//! ----------------------------------------------------------------------------- +//! Predictor configuration (`predictor_config`) +//! ----------------------------------------------------------------------------- +//! +//! Candidate retrieval typically uses a propagated **sky cone** prediction: +//! - propagate the left seed to the target epoch (or target time bins), +//! - derive an uncertainty radius from a plane covariance, +//! - retrieve right-side seeds whose sky position falls in that cone. +//! +//! This behavior is configured by [`PredictorParams`] (`predictor_config`), which +//! controls: +//! - kσ inflation (`k_sigma`), +//! - additive model noise schedule (`noise`), +//! - optional cell-radius padding (`pad_cell_radius`), +//! - time bin handling (`time_bin_dt`), +//! - optional velocity slack (`v_slack`). +//! +//! The predictor is a major driver of candidate fan-out: increasing its radius +//! increases recall but also runtime. When `top_k_per_left` is `Some(k)`, fan-out +//! is capped per left seed, but feature computation cost still scales with the +//! number of raw candidates. +//! +//! ----------------------------------------------------------------------------- +//! Configuration examples (YAML) +//! ----------------------------------------------------------------------------- +//! +//! No filtering — emit all candidates (debug / dataset generation): +//! +//! ```yaml +//! edges: +//! top_k_per_left: ~ # null = no filtering +//! parallel_left_batches: true +//! parallel_left_batch_size: 512 +//! predictor_config: +//! k_sigma: 3.0 +//! noise: { variance_floor: 0.0, drift_per_day: 0.0, curvature_per_day2: 0.0 } +//! pad_cell_radius: true +//! time_bin_dt: 0.021 +//! v_slack: 0.0 +//! cost: +//! variant: gaussian_chi2 +//! ``` +//! +//! Cost-based Top-K (recommended default for production without ONNX): +//! +//! ```yaml +//! edges: +//! top_k_per_left: 32 +//! parallel_left_batches: true +//! parallel_left_batch_size: 512 +//! predictor_config: +//! k_sigma: 3.5 +//! noise: { variance_floor: 1.0e-12, drift_per_day: 0.0, curvature_per_day2: 5.0e-14 } +//! pad_cell_radius: true +//! time_bin_dt: 0.021 +//! v_slack: 0.0 +//! cost: +//! variant: singer_cwna +//! sigma_q: 1.0e-3 +//! ``` +//! +//! Cost-based Top-K + ML post-filter (production with ONNX model): +//! +//! ```yaml +//! edges: +//! ml_post_filter: true +//! ml_post_filter_threshold: 0.5 +//! edge_ranking_model_path: "edge_ranker.onnx" +//! top_k_per_left: 32 +//! onnx_batch_size: 128 +//! parallel_left_batches: true +//! parallel_left_batch_size: 512 +//! predictor_config: +//! k_sigma: 3.5 +//! noise: { variance_floor: 1.0e-12, drift_per_day: 0.0, curvature_per_day2: 5.0e-14 } +//! pad_cell_radius: true +//! time_bin_dt: 0.021 +//! v_slack: 0.0 +//! cost: +//! variant: singer_cwna +//! sigma_q: 1.0e-3 +//! ``` +//! +//! Unknown keys are rejected (`deny_unknown_fields`) to catch typos early. +//! +//! Add `max_cost_cut` to any mode to apply a hard cost upper bound: +//! +//! ```yaml +//! edges: +//! max_cost_cut: 5.0 # discard candidates whose cost exceeds 5.0 +//! top_k_per_left: 32 +//! cost: +//! variant: singer_cwna +//! sigma_q: 1.0e-3 +//! ``` +//! +//! ----------------------------------------------------------------------------- +//! Cost cut (`max_cost_cut`) +//! ----------------------------------------------------------------------------- +//! +//! An optional hard upper bound on the edge cost returned by +//! [`crate::graph::edge::edge_features::EdgeFeatures::compute_cost`]. +//! +//! When set, any candidate whose computed cost exceeds this threshold is +//! discarded immediately — before Top-K scoring and before being added to the +//! heap. The cut is applied in **all** edge-building modes: +//! +//! - **Emit-all**: candidates above the cut are skipped before materialising the +//! edge, even though no Top-K filtering is otherwise active. +//! - **Cost-based Top-K**: the cut is applied after computing cost and before the +//! score mapping; it reduces the number of candidates competing for the K slots. +//! +//! Notes: +//! - `None` disables the cut; all candidates are processed regardless of cost. +//! - `Some(v)` with `v <= 0.0` is rejected by [`EdgeConfig::validate`] because +//! costs are strictly positive and such a value would silently discard every +//! candidate. +//! - `max_cost_cut` does not interact with the ML post-filter: it is applied +//! during Top-K candidate selection, before ONNX inference. +//! +//! ----------------------------------------------------------------------------- +//! Validation +//! ----------------------------------------------------------------------------- +//! +//! [`EdgeConfig::validate`] currently enforces the following invariants: +//! - `top_k_per_left != Some(0)` — a zero limit would silently discard all edges. +//! - `max_cost_cut` must satisfy `v > 0.0` when `Some(v)` — a non-positive cut +//! would silently discard every candidate. +//! - `ml_post_filter_threshold` must be in `(0.0, 1.0]` when `ml_post_filter = true`. +//! +//! Additional checks (often useful in production) can be added if desired: +//! - `onnx_batch_size > 0`, +//! - `parallel_left_batch_size > 0` (or clamp in a single place), +//! - `predictor_config.validate()` (if not already validated upstream). +//! +//! ----------------------------------------------------------------------------- +//! See also +//! ----------------------------------------------------------------------------- +//! +//! - `crate::graph::edge` (edge construction entrypoint and the operational modes). +//! - `crate::graph::edge::edge_prediction` (ONNX model loading + inference). +//! - `crate::graph::edge::ranking_topk` (cost-based per-left Top-K ranking). +//! - [`PredictorParams`] (cone prediction controlling candidate retrieval). +use serde::{Deserialize, Serialize}; + +use crate::engine_config::{error::EdgeConfigError, propagator_config::PredictorParams}; + +fn default_false() -> bool { + false +} + +fn default_ml_post_filter_threshold() -> f32 { + 0.5 +} + +// ============================================================================= +// Cost-function configuration +// ============================================================================= + +/// Selector for the kinematic cost function used when building graph edges. +/// +/// All variants apply the same photometry terms unchanged. They differ in +/// how the positional/velocity chi² is computed (covariance model) and in +/// which loss function maps chi² to a scalar cost. +/// +/// YAML spelling is `snake_case` (e.g., `robust_cauchy`). +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum CostVariant { + /// Backward-compatibility alias for `GaussianChi2` with `sigma_q = 0`. + /// + /// Produces identical numerical results to `GaussianChi2` (same ½χ² formula, + /// same baseline covariances). Use this to guarantee the same edge costs as + /// runs predating the configurable cost system. + KinematicLogLikelihood, + + /// Gaussian ½χ²: standard negative-log-likelihood for a constant-velocity + /// motion model with no process noise. + /// + /// `c = ½(χ²_pos + χ²_vel)` where chi² uses the fixed measurement-noise + /// covariance. Cost grows as Δt² for large inter-night gaps. + #[default] + GaussianChi2, + + /// Singer CWNA: Gaussian ½χ² with continuous white-noise acceleration + /// (CWNA) covariance inflation. + /// + /// Adds `σ_q² · Δt³/3 · I` to the positional innovation covariance and + /// `σ_q² · Δt · I` to the velocity covariance. χ² stays ~O(1) for all + /// night-gaps when `sigma_q` is well-calibrated (~1e-3 rad/day^(3/2)). + /// Requires `sigma_q > 0`; falls back to `GaussianChi2` when `sigma_q = 0`. + SingerCwna, // Continuous White Noise Acceleration (Singer process noise model). + + /// Robust Cauchy loss: `c = ln(1 + χ²_pos/scale) + ln(1 + χ²_vel/scale)`. + /// + /// The logarithmic saturation bounds the cost for large residuals, limiting + /// the influence of high-curvature trajectories or large night-gaps. + /// Can optionally be combined with CWNA process noise (`sigma_q > 0`). + RobustCauchy, + + /// Robust Student-t loss: `c = (ν+1)/2 · [ln(1+χ²_pos/ν) + ln(1+χ²_vel/ν)]`. + /// + /// Generalises Cauchy (ν=1) toward Gaussian (ν→∞). Provides a smoother + /// transition between the two regimes. Can optionally be combined with + /// CWNA process noise (`sigma_q > 0`). + RobustStudentT, // Degrees of freedom ν is configured separately in `student_nu`. +} + +/// Parameters for the edge kinematic cost function. +/// +/// These settings are exposed in the YAML config under `edges.cost`: +/// +/// ```yaml +/// edges: +/// cost: +/// variant: singer_cwna # gaussian_chi2 | kinematic_log_likelihood | singer_cwna | robust_cauchy | robust_student_t +/// sigma_q: 1.0e-3 # CWNA accel. spectral density [rad·day^(-3/2)] +/// cauchy_scale: 2.0 # Cauchy transition scale +/// student_nu: 3.0 # Student-t degrees of freedom +/// ``` +/// +/// Notes +/// ----- +/// - `sigma_q` is used by `SingerCwna`, `RobustCauchy`, and `RobustStudentT`. +/// Set to `0.0` to disable process noise (pure measurement-noise covariance). +/// - `cauchy_scale` is only used by `RobustCauchy`. +/// - `student_nu` is only used by `RobustStudentT`. +/// - `KinematicLogLikelihood` is the original cost function, included for +/// backward compatibility; it is numerically equivalent to `GaussianChi2` +/// with `sigma_q = 0.0`. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct CostConfig { + /// Which kinematic loss function to apply. + pub variant: CostVariant, + + /// CWNA acceleration spectral density σ_q (rad · day^(−3/2)). + /// + /// When non-zero, the positional innovation covariance is inflated by + /// `σ_q² · Δt³/3 · I` and the velocity covariance by `σ_q² · Δt · I`. + /// Recommended calibrated value: `1e-3`. + pub sigma_q: f64, + + /// Transition scale for the Cauchy loss: `ρ(χ²) = ln(1 + χ²/scale)`. + /// + /// Defaults to `2.0` (Gaussian and Cauchy regimes cross at χ² = 2·scale). + pub cauchy_scale: f64, + + /// Degrees of freedom ν for the Student-t loss. + /// + /// `ν=1` reproduces Cauchy; `ν→∞` converges to Gaussian. Default: `3.0`. + pub student_nu: f64, +} + +impl Default for CostConfig { + fn default() -> Self { + Self { + variant: CostVariant::GaussianChi2, + sigma_q: 0.0, + cauchy_scale: 2.0, + student_nu: 3.0, + } + } +} + +/// Runtime configuration used by the engine to build inter-night edges. +/// +/// This structure is intended to be deserialized from configuration files +/// (YAML/TOML/JSON) and then validated before execution. +/// +/// Notes +/// ----- +/// - `deny_unknown_fields` rejects unknown YAML keys to catch typos early. +/// - `serde(default)` fills missing fields from [`Default`]. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct EdgeConfig { + /// Path to the ONNX model used for ML post-filtering. + /// + /// Used when `ml_post_filter = true`: + /// - passed to `EdgeRankingModel::load_edge_ranking_model(...)` (or a model pool), + /// - the model is expected to output a `probabilities` tensor of shape `[N, 2]` + /// where column 1 is `p(class=1)` (true edge probability). + /// + /// Notes + /// ----- + /// - When `ml_post_filter = false`, this path is unused. + /// - The file is not validated here; failures usually surface during model loading. + pub edge_ranking_model_path: Option, + + /// Maximum number of edges retained **per left seed** after ranking. + /// + /// Behavior + /// -------- + /// - When `Some(k)`: apply Top-K filtering after ranking (either ML or cost-based), + /// retaining only the `k` best candidates per left seed. + /// - When `None`: no Top-K filtering is applied; all candidate edges are emitted. + /// + /// Trade-off + /// --------- + /// - Smaller values reduce runtime and graph density but can reduce recall. + /// - Larger values increase recall but can create larger connected components + /// and increase solver cost. + /// - `None` can produce very large edge sets; use with care on dense nights. + /// + /// Validation + /// ---------- + /// `Some(0)` is rejected by [`EdgeConfig::validate`] as it would silently + /// discard all edges. + pub top_k_per_left: Option, + + /// Batch size used for ONNX inference in ML post-filtering. + /// + /// After cost-based Top-K selection, the retained edges are scored in batches + /// of this size. Larger batches improve throughput (amortise per-call overhead) + /// but increase temporary memory usage. + /// + /// Notes + /// ----- + /// - This value is only relevant when `ml_post_filter = true`. + /// - If set too small, throughput can degrade due to per-call overhead. + pub onnx_batch_size: usize, + + /// Optional number of intra-op threads for each ONNX Runtime session. + /// + /// *Intra-op* threads control parallelism **within** a single kernel + /// operation (e.g., a matrix multiply inside the ONNX graph). This is + /// orthogonal to the Rayon-level parallelism controlled by + /// `parallel_left_batches`: Rayon divides work across seeds (inter-op), + /// while intra-op threads subdivide individual tensor operations. + /// + /// Behavior + /// -------- + /// - `None` (default): ORT selects the thread count automatically, + /// typically equal to the number of logical CPUs. Best throughput on a + /// dedicated machine but can saturate all cores. + /// - `Some(n)`: ORT uses exactly `n` intra-op threads per session. + /// Set to `1` for fully single-threaded ONNX execution. Useful on + /// shared machines to limit CPU contention, or when Rayon parallelism + /// already saturates available cores. + /// + /// Notes + /// ----- + /// - Only relevant when `ml_post_filter = true`; ignored otherwise. + /// - In the [`crate::graph::edge::edge_prediction::EdgeRankingModelPool`] + /// design, each Rayon thread holds its own lazily-initialized session. + /// The thread count is applied **once** at session creation (the first + /// `with_mut` call on each thread); subsequent calls reuse the live session. + pub onnx_intra_threads: Option, + + /// Enable ML post-filtering of cost-selected edges. + /// + /// When `true`, all edges retained by the cost-based Top-K step are scored + /// by the ONNX classifier. Any edge whose `p(class=1)` is below + /// `ml_post_filter_threshold` is discarded. + /// + /// Requires `edge_ranking_model_path` to point to a valid ONNX model and + /// a live `EdgeRankingModelPool` to be provided at the call site. + #[serde(default = "default_false")] + pub ml_post_filter: bool, + + /// Minimum `p(class=1)` probability to retain an edge in ML post-filtering. + /// + /// Ignored when `ml_post_filter = false`. + /// Must be in `(0.0, 1.0]`. + #[serde(default = "default_ml_post_filter_threshold")] + pub ml_post_filter_threshold: f32, + /// Enable parallel processing of left seeds with Rayon. + /// + /// Behavior + /// -------- + /// When enabled, the engine processes chunks of left seeds in parallel. + /// This can substantially improve throughput for large datasets. + /// + /// Notes + /// ----- + /// - Parallel inference typically requires one ONNX session per thread + /// (see `EdgeRankingModelPool`). + #[serde(default = "default_false")] + pub parallel_left_batches: bool, + + /// Chunk size for left-side batch processing. + /// + /// Context + /// ------- + /// When `parallel_left_batches = true`, the engine may conceptually run: + /// + /// ```text + /// left.par_chunks(parallel_left_batch_size).for_each(...) + /// ``` + /// + /// Notes + /// ----- + /// - Values of `0` are not meaningful for chunking. Some call sites clamp + /// `<= 0` to 1; this struct stores the raw value. + /// - Tune this to balance Rayon overhead vs per-chunk working set. + pub parallel_left_batch_size: usize, + + /// Parameters controlling propagation-based cone prediction for candidate retrieval. + /// + /// This configuration is used to: + /// - propagate a left seed to a target epoch, + /// - compute an uncertainty radius (kσ + model noise), + /// - optionally pad by spatial cell radius, + /// - retrieve right-side candidates using that cone. + pub predictor_config: PredictorParams, + + /// Cost function used to assign a scalar weight to each edge. + /// + /// This controls both the covariance model (optional CWNA process noise) + /// and the loss function (Gaussian, Cauchy, Student-t). + /// The photometry penalty terms are unaffected by this choice. + /// + /// In cost-based Top-K mode (`top_k_per_left = Some(k)`), this is also the + /// ranking criterion: the K candidates with the lowest cost are retained per + /// left seed. + #[serde(rename = "cost")] + pub cost_config: CostConfig, + + /// Hard upper bound on the edge cost used as a pre-filter. + /// + /// Behavior + /// -------- + /// - `None` (default): no cut is applied; all candidates are evaluated + /// regardless of their cost. + /// - `Some(max)`: any candidate whose + /// [`crate::graph::edge::edge_features::EdgeFeatures::compute_cost`] + /// result exceeds `max` is discarded immediately — before Top-K scoring + /// and before entering the emitted edge list. + /// + /// This cut takes effect in all edge-building modes: + /// + /// - **Emit-all** (`top_k_per_left = None`): applied after cost computation, + /// before materialising the edge. + /// - **Cost-based Top-K**: applied after cost computation, before the score + /// mapping and heap insertion. + /// + /// Notes + /// ----- + /// - `Some(v)` with `v <= 0.0` is rejected by [`EdgeConfig::validate`]. + pub max_cost_cut: Option, +} + +impl Default for EdgeConfig { + fn default() -> Self { + Self { + edge_ranking_model_path: None, + top_k_per_left: Some(32), + onnx_batch_size: 128, + onnx_intra_threads: None, + ml_post_filter: false, + ml_post_filter_threshold: 0.5, + parallel_left_batches: false, + parallel_left_batch_size: 512, + predictor_config: PredictorParams::default(), + cost_config: CostConfig::default(), + max_cost_cut: None, + } + } +} + +impl EdgeConfig { + /// Validate the configuration for basic invariants. + /// + /// Checks performed + /// ---------------- + /// - `top_k_per_left != Some(0)`: a zero limit would silently discard all edges. + /// - `max_cost_cut > 0.0` when `Some(v)`: a non-positive cut discards every candidate. + /// - `ml_post_filter_threshold` must be in `(0.0, 1.0]` when `ml_post_filter = true`. + /// + /// Return + /// ------ + /// - `Ok(())` if valid. + /// - `Err(EdgeConfigError)` otherwise. + /// + /// Notes + /// ----- + /// This function is intentionally minimal. Depending on where configuration + /// is loaded, additional validation can be useful (batch sizes, predictor + /// parameters, model path existence, etc.). + pub fn validate(&self) -> Result<(), EdgeConfigError> { + self.predictor_config.validate()?; + + if self.top_k_per_left == Some(0) { + return Err(EdgeConfigError::TopKPerLeftZero); + } + + if let Some(max) = self.max_cost_cut + && max <= 0.0 + { + return Err(EdgeConfigError::MaxCostCutNotPositive(max)); + } + + if self.ml_post_filter + && (self.ml_post_filter_threshold <= 0.0 || self.ml_post_filter_threshold > 1.0) + { + return Err(EdgeConfigError::MlPostFilterThresholdInvalid( + self.ml_post_filter_threshold, + )); + } + + Ok(()) + } +} diff --git a/crates/fink-fat-engine/src/engine_config/error.rs b/crates/fink-fat-engine/src/engine_config/error.rs new file mode 100644 index 00000000..6ccabf03 --- /dev/null +++ b/crates/fink-fat-engine/src/engine_config/error.rs @@ -0,0 +1,217 @@ +//! # Engine configuration error types (`ConfigError`, `EdgeConfigError`) +//! +//! This module defines the error types used by the **engine configuration layer**. +//! +//! The configuration system typically has three stages: +//! +//! 1) **Loading / merging** configuration sources (files, environment overrides, defaults) +//! using the `config` crate. +//! 2) **Deserialization** into strongly-typed Rust structs (e.g. `PairConfig`, +//! `TripletConfig`, `PredictorParams`, `EdgeConfig`). +//! 3) **Validation** of numeric ranges and cross-field invariants. +//! +//! The errors in this module are designed to preserve enough context so the +//! caller can decide whether the failure is: +//! - an I/O / parsing problem (cannot load the config), +//! - a schema mismatch (unsupported version), +//! - a user mistake (invalid value, wrong unit string, unknown field), +//! - or a higher-level invariant violation detected by explicit validation. +//! +//! ----------------------------------------------------------------------------- +//! Error taxonomy +//! ----------------------------------------------------------------------------- +//! +//! ## [`ConfigError`] +//! +//! High-level error enum returned by the configuration loader / validator. +//! It groups failures by their origin: +//! +//! - Loader failures from the `config` crate (`ConfigRs`): missing file, invalid YAML, +//! environment override parsing errors, etc. +//! - Versioning errors (`UnsupportedVersion`): the config file declares a schema +//! version the engine does not understand. +//! - Static “invalid config” markers (`Invalid`): used when a particular invariant +//! is violated but does not fit a more specialized error type. +//! - Parameter-level validation failures for: +//! - seeding (`Seed`): pairs/triplets parameter validation, +//! - propagation predictor (`Predictor`): predictor parameters / noise schedule, +//! - edge construction (`Edges`): edge configuration constraints. +//! +//! This structure enables the top-level CLI / application to provide clear +//! user-facing messages such as: +//! - “YAML parsing error” +//! - “unsupported config version” +//! - “pairs.max_dt must be non-negative” +//! - “edges.top_k_per_left must be > 0” +//! +//! ## [`EdgeConfigError`] +//! +//! Specialized error enum for edge configuration validation. +//! This is separated so the edge subsystem can evolve its own invariants without +//! making `ConfigError` too large or too coupled to edge internals. +//! +//! ----------------------------------------------------------------------------- +//! Propagation and `#[from]` conversions +//! ----------------------------------------------------------------------------- +//! +//! Both error enums use `thiserror` and rely heavily on `#[from]` to support +//! ergonomic propagation with `?`. +//! +//! Typical usage: +//! +//! ```rust, ignore +//! fn load_and_validate() -> Result { +//! let cfg: EngineConfig = loader.load()?; // may produce ConfigRsError +//! cfg.pairs.validate()?; // may produce SeedError +//! cfg.triplets.validate()?; // may produce SeedError +//! cfg.edges.validate()?; // may produce EdgeConfigError +//! cfg.edges.predictor_config.validate()?; // may produce PredictorParamError +//! Ok(cfg) +//! } +//! ``` +//! +//! ----------------------------------------------------------------------------- +//! Notes for user-facing diagnostics +//! ----------------------------------------------------------------------------- +//! +//! - Deserialization failures caused by `deny_unknown_fields` on config structs +//! are usually surfaced as `ConfigRsError` during the load/deserialize step. +//! - Unit parsing failures (e.g. `"35 foounit/day"`) from `engine_config::units` +//! also surface as deserialization errors and are therefore typically wrapped +//! by `ConfigRsError`. +//! - Post-deserialization semantic checks (finite / non-negative / cross-field +//! constraints) should use the dedicated `validate()` routines and produce +//! `SeedError`, `PredictorParamError`, or `EdgeConfigError` for precise messages. + +use thiserror::Error; + +use config::ConfigError as ConfigRsError; + +use crate::{ + error::{PredictorParamError, SeedError}, + graph::edge::error::EdgeModelError, +}; + +/// Top-level configuration error returned by config loading and validation. +/// +/// This error type is intended to be the primary return type of an engine +/// configuration loader. It covers both: +/// - failures while loading/merging/parsing configuration sources, and +/// - failures after deserialization when validating semantic invariants. +/// +/// Variants +/// -------- +/// - [`ConfigError::ConfigRs`]: +/// error originating from the `config` crate (I/O, parsing, merging, etc.). +/// - [`ConfigError::UnsupportedVersion`]: +/// the configuration declares a schema version that this binary does not support. +/// - [`ConfigError::Invalid`]: +/// a coarse invalid-config marker for invariants that do not have a dedicated error. +/// - [`ConfigError::Seed`]: +/// seeding parameter validation failure (pairs/triplets). +/// - [`ConfigError::Predictor`]: +/// predictor parameter validation failure (kσ, noise coefficients, etc.). +/// - [`ConfigError::Edges`]: +/// edge construction configuration validation failure. +#[derive(Debug, Error)] +pub enum ConfigError { + /// Error produced by the `config` crate while loading configuration sources. + /// + /// This includes (non-exhaustive): + /// - missing or unreadable configuration file, + /// - YAML/TOML/JSON syntax errors, + /// - environment override parse errors, + /// - type mismatch during deserialization. + #[error("config loader error: {0}")] + ConfigRs(#[from] ConfigRsError), + + /// The configuration file declares a schema version not supported by this binary. + /// + /// This variant is typically emitted after reading a version field (e.g. + /// `config_version`) but before attempting to interpret the rest of the file. + #[error("unsupported config version: {0}")] + UnsupportedVersion(u32), + + /// Coarse invalid-config marker. + /// + /// This is useful for simple invariants where creating a dedicated error + /// type would not add much value. + /// + /// Notes + /// ----- + /// The message is `'static` so it can be used as a stable identifier in tests + /// or for downstream mapping to user-facing help. + #[error("invalid config: {msg}")] + Invalid { msg: String }, + + /// Pairs/triplets configuration error. + /// + /// Produced by `PairConfig::validate()` / `TripletConfig::validate()` and other + /// seeding-related validation routines. + #[error("pairs/triplets config error: {0}")] + Seed(#[from] SeedError), + + /// Propagation predictor configuration error. + /// + /// Produced by `PredictorParams::validate()` and related builder checks. + #[error("predictor config error: {0}")] + Predictor(#[from] PredictorParamError), + + /// Inter-night edge configuration error. + /// + /// Produced by `EdgeConfig::validate()` and other edge-related invariants. + #[error("edges config error: {0}")] + Edges(#[from] EdgeConfigError), +} + +/// Edge configuration validation errors. +/// +/// This error enum groups semantic constraints specific to the inter-night edge +/// construction subsystem. +#[derive(Debug, Error)] +pub enum EdgeConfigError { + /// `edges.top_k_per_left` must be strictly positive. + /// + /// In ML Top-K mode, a value of `0` would result in emitting zero edges, + /// silently disabling linking. Enforcing `> 0` keeps the configuration + /// unambiguous. + #[error("edges.top_k_per_left must be > 0")] + TopKPerLeftZero, + + /// `edges.max_total_edges` must be strictly positive. + /// + /// This constraint is relevant if the edge subsystem supports a global + /// cap on the total number of emitted edges (not shown in the `EdgeConfig` + /// snippet). The validator emits this error when that cap is configured + /// but set to `0`. + #[error("edges.max_total_edges must be > 0")] + MaxTotalEdgesZero, + + /// `edges.max_cost_cut` must be strictly positive when set. + /// + /// Since edge costs are strictly positive by construction, a cut value of + /// `0.0` or less would discard every candidate, silently disabling all + /// inter-night linking. Enforcing `> 0` keeps the configuration + /// unambiguous and prevents accidental runs with an empty graph. + #[error("edges.max_cost_cut must be > 0 when set (got {0})")] + MaxCostCutNotPositive(f64), + + /// `edges.ml_post_filter_threshold` must be in `(0.0, 1.0]`. + /// + /// A threshold of `0.0` would keep every candidate (no filtering), and + /// negative values are meaningless. Enforcing `(0.0, 1.0]` keeps the + /// configuration unambiguous. + #[error("edges.ml_post_filter_threshold must be in (0.0, 1.0] (got {0})")] + MlPostFilterThresholdInvalid(f32), + + /// `edges.predictor_config` is invalid. + /// + /// This error wraps any validation failure from the predictor configuration, + /// such as invalid `k_sigma` or noise parameters. + #[error("edges.predictor_config error: {0}")] + PredictorConfig(#[from] PredictorParamError), + + /// Edge Model Error + #[error(transparent)] + EdgeModel(#[from] EdgeModelError), +} diff --git a/crates/fink-fat-engine/src/engine_config/log_level.rs b/crates/fink-fat-engine/src/engine_config/log_level.rs new file mode 100644 index 00000000..7b7571b0 --- /dev/null +++ b/crates/fink-fat-engine/src/engine_config/log_level.rs @@ -0,0 +1,38 @@ +//! Log-level configuration for the engine. +//! +//! A thin serde-friendly enum that maps to [`tracing::Level`] in the CLI layer. +//! The engine only stores this value; the subscriber is installed by the caller. +//! +//! YAML values (case-insensitive): `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`. + +use serde::{Deserialize, Serialize}; + +/// Minimum tracing/log level that should be recorded. +/// +/// This value is read from the `log_level` key in the YAML configuration file. +/// The CLI inspects it when initialising the tracing subscriber. +/// +/// Defaults to [`LogLevel::Info`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Trace, + Debug, + #[default] + Info, + Warn, + Error, +} + +impl std::fmt::Display for LogLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + LogLevel::Trace => "trace", + LogLevel::Debug => "debug", + LogLevel::Info => "info", + LogLevel::Warn => "warn", + LogLevel::Error => "error", + }; + f.write_str(s) + } +} diff --git a/crates/fink-fat-engine/src/engine_config/mod.rs b/crates/fink-fat-engine/src/engine_config/mod.rs new file mode 100644 index 00000000..713d03cc --- /dev/null +++ b/crates/fink-fat-engine/src/engine_config/mod.rs @@ -0,0 +1,1035 @@ +//! # Engine configuration (`EngineConfig`) and loading +//! +//! This module defines the **root configuration** for the engine and the +//! **validated loading** routine used by CLI / applications. +//! +//! The engine configuration is designed around three constraints: +//! - **Schema stability**: a `version` field enables forward compatibility. +//! - **Strictness**: unknown keys are rejected (`deny_unknown_fields`) to catch +//! YAML typos early. +//! - **Ergonomics**: missing fields fall back to Rust defaults +//! (`serde(default)` + `Default` impls). +//! +//! In the runtime pipeline, the main sections map to major engine stages: +//! +//! - [`PairConfig`]: +//! intra-night pair generation pre-filter. +//! - [`TripletConfig`]: +//! intra-night triplet generation, producing higher-quality seeds. +//! - [`EdgeConfig`]: +//! inter-night edge construction (candidate retrieval + features + optional ML Top-K). +//! - [`SolverConfig`]: +//! solver selection and solver-specific policies. +//! +//! Additional global knobs: +//! - `max_gap_nights`: maximum inter-night gap considered for linking. +//! - `storage_path`: root directory for on-disk persistence / artifacts. +//! +//! ----------------------------------------------------------------------------- +//! Configuration sources and precedence +//! ----------------------------------------------------------------------------- +//! +//! The loader [`load_engine_config_validated`] merges multiple sources using +//! the `config` crate, with the following order (later sources override earlier): +//! +//! 1) **Rust defaults** (`EngineConfig::default()`). +//! 2) **YAML file** at the provided path (required). +//! 3) **Environment overrides** (optional), using prefix `FINK_FAT` and separator `__`. +//! +//! This produces a single `EngineConfig` instance which is then validated by +//! [`EngineConfig::validate`]. +//! +//! ----------------------------------------------------------------------------- +//! Environment override naming convention +//! ----------------------------------------------------------------------------- +//! +//! The environment loader is configured as: +//! - prefix: `FINK_FAT` +//! - separator: `__` +//! - parsing: `try_parsing(true)` +//! +//! This implies that nested keys are addressed with double underscores. +//! Example overrides (shell): +//! +//! ```bash +//! # Override an integer field +//! export FINK_FAT__MAX_GAP_NIGHTS=4 +//! +//! # Override a nested field (if it is a plain numeric type) +//! export FINK_FAT__EDGES__TOP_K_PER_LEFT=64 +//! ``` +//! +//! Notes +//! ----- +//! - Some fields use custom deserializers (e.g. unit parsing in pairs/triplets). +//! For those, providing a string value in the env may work, but the actual +//! behavior depends on the serde implementation of the corresponding field. +//! - Because `deny_unknown_fields` is enabled, typos in env keys will fail +//! during deserialization. +//! +//! ----------------------------------------------------------------------------- +//! Validation strategy +//! ----------------------------------------------------------------------------- +//! +//! Validation is intentionally split into: +//! - **schema version check** (`version`), +//! - **local section validation** (pairs, triplets, edges, predictor), +//! - **cross-field consistency** (global invariants). +//! +//! The current implementation performs: +//! - `version == 1` (else [`ConfigError::UnsupportedVersion`]), +//! - `pairs.validate()` and `triplets.validate()` (propagated as [`ConfigError::Seed`]), +//! - `edges.validate()` (propagated as [`ConfigError::Edges`]). +//! +//! Extending validation is expected as new fields are added, e.g.: +//! - validate the predictor configuration (`edges.predictor_config.validate()`), +//! - validate global limits (e.g. `max_gap_nights > 0`), +//! - validate storage path constraints. +//! +//! ----------------------------------------------------------------------------- +//! YAML example (minimal, with solver configuration filled) +//! ----------------------------------------------------------------------------- +//! +//! ```yaml +//! version: 1 +//! +//! storage_path: "./storage" +//! max_gap_nights: 3 +//! compact_graph_every_delta: 20 +//! binary_compression: "None" +//! +//! pairs: +//! max_dt: "86.4 min" +//! max_angular_speed: "35 arcmin/day" +//! max_flux_difference: 5.0 +//! allow_same_timebin: true +//! +//! triplets: +//! max_dt_between: "57.6 min" +//! max_pair_sep: "8.6 arcmin" +//! max_predicted_residual: "2.75 arcmin" +//! enforce_time_order: true +//! max_flux_difference: 5.0 +//! +//! edges: +//! ml_post_filter: false +//! edge_ranking_model_path: "edge_ranker.onnx" +//! top_k_per_left: 32 +//! onnx_batch_size: 128 +//! parallel_left_batches: true +//! parallel_left_batch_size: 512 +//! predictor_config: +//! k_sigma: 3.0 +//! noise: +//! variance_floor: 0.0 +//! drift_per_day: 0.0 +//! curvature_per_day2: 0.0 +//! pad_cell_radius: true +//! time_bin_dt: 0.021 +//! v_slack: 0.0 +//! +//! solver_config: +//! policy: +//! routing: Heuristics +//! trivial_max_nodes: 8 +//! trivial_max_active_edges: 16 +//! mcf_budget_s: 0.05 +//! k_mcf_s_per_edge_logn: 1.0e-8 +//! max_night_span_for_mcf: 4 +//! bounded_beam: +//! max_tracks: 16 +//! min_nodes: 3 +//! beam_width: 64 +//! max_out_per_node: 8 +//! max_tracks_per_source: 8 +//! max_expansions: 50000 +//! +//! pipeline_policy: +//! PersistPolicy: Full +//! ``` +//! +//! Notes +//! ----- +//! - The `policy.routing` field is serialized using Serde’s default enum encoding: +//! - `Heuristics` is written as the plain variant name, +//! - `Force(choice)` is written as a map like `{ Force: BoundedBeam }`. +//! - The `bounded_beam` block corresponds to [`BoundedBeamConfig`](crate::engine_config::solver_config::bounded_beam_config::BoundedBeamConfig) and provides +//! hard guardrails on exploration and output size. +//! - If additional solver families are added later, `solver_config` may grow +//! with extra sub-sections; keep the routing policy independent from solver +//! internal knobs. +//! +//! ----------------------------------------------------------------------------- +//! See also +//! ----------------------------------------------------------------------------- +//! +//! - [`crate::engine_config::units`]: human-friendly unit parsing for YAML fields. +//! - [`crate::engine_config::error::ConfigError`]: unified error type returned by the loader. +//! - [`PairConfig`], [`TripletConfig`], [`EdgeConfig`], [`SolverConfig`] for detailed section docs. + +pub mod edge_config; +pub mod error; +pub mod log_level; +pub mod pair_config; +pub mod pipeline_policy; +pub mod propagator_config; +pub mod solver_config; +pub mod triplet_config; +pub mod units; + +use camino::{Utf8Path, Utf8PathBuf}; +use config::{Config, Environment, File}; +use serde::{Deserialize, Serialize}; + +use crate::{ + MJDTT, + engine_config::{ + edge_config::EdgeConfig, error::ConfigError, log_level::LogLevel, pair_config::PairConfig, + pipeline_policy::PersistPolicy, solver_config::SolverConfig, triplet_config::TripletConfig, + units::de_time_days, + }, + persistence::compression::Compression, +}; + +/// Root configuration for the engine (serde-friendly). +/// +/// This struct is the single entry point for configuration consumed by the +/// engine at runtime. +/// +/// Behavior +/// -------- +/// - Unknown keys are rejected (`deny_unknown_fields`) to catch YAML typos early. +/// - Missing fields are filled from defaults (`serde(default)` + [`Default`]). +/// +/// Fields +/// ------ +/// - `version`: schema version. Must match the expected version in +/// [`EngineConfig::validate`]. +/// - `pairs`: configuration for intra-night pair generation. +/// - `triplets`: configuration for intra-night triplet generation. +/// - `edges`: configuration for inter-night edge construction. +/// - `solver_config`: solver policy and solver-specific knobs. +/// - `max_gap_nights`: maximum inter-night gap considered when linking nights. +/// - `storage_path`: root directory for on-disk artifacts produced by the pipeline. +/// +/// Notes +/// ----- +/// - `max_gap_nights` and `storage_path` are stored as private fields and exposed +/// through accessors to keep the public API stable. +/// - The storage path is stored as a UTF-8 string and exposed as `Utf8Path` +/// for ergonomics and OS-independent handling. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct EngineConfig { + /// Schema version for forward compatibility. + pub version: u32, + + /// Intra-night pair generation configuration. + pub pairs: PairConfig, + + /// Intra-night triplet generation configuration. + pub triplets: TripletConfig, + + /// Inter-night edge construction configuration. + pub edges: EdgeConfig, + + /// Solver selection and solver-specific configuration. + pub solver_config: SolverConfig, + + /// pipeline policy configuration + pub pipeline_policy: PersistPolicy, + + /// Compression algorithm used when writing binary persistence blobs + /// (alerts, seeds, edge journal deltas and snapshots). + /// + /// The choice is stored inside every [`crate::persistence::envelope::DiskEnvelope`] + /// and embedded in the binary frame, so readers never need to know the + /// algorithm in advance. + /// + /// Defaults to [`Compression::None`] (no compression). For production + /// deployments where disk I/O is a bottleneck, [`Compression::Zstd`] is + /// recommended. + /// + /// YAML values: `"None"`, `"Lz4"`, `"Zstd"`, `"Gzip"`. + pub binary_compression: Compression, + + /// Maximum number of nights that can be skipped when linking (`gap` constraint). + /// + /// Interpretation + /// -------------- + /// This parameter limits how far the engine is allowed to link forward in time. + /// For example, if `max_gap_nights = 3`, edges may connect seeds separated by + /// up to 3 night boundaries (implementation-dependent: inclusive/exclusive + /// gap semantics are defined by the edge builder). + /// + /// Notes + /// ----- + /// Keeping this small reduces candidate fan-out and runtime. + max_gap_nights: u8, + + /// Healpix depth used for spatial binning (nested representation). + /// + /// Overview + /// -------- + /// Controls the angular resolution of the HEALPix tessellation used to + /// index sky positions (via `cdshealpix`, nested scheme). + /// + /// The subdivision follows: + /// - `nside = 2^depth` + /// - `npix = 12 × nside²` + /// + /// Increasing `depth`: + /// - increases the number of pixels, + /// - decreases pixel angular size, + /// - improves spatial selectivity. + /// + /// Role in the engine + /// ------------------ + /// Used for: + /// - intra-night alert/seed spatial binning, + /// - spatial pre-filtering during inter-night edge construction. + /// + /// Trade-off + /// --------- + /// - Low depth → coarse grid → more candidates per pixel (higher fan-out). + /// - High depth → fine grid → better pruning but more indexing overhead. + /// + /// Typical values in astronomical use cases are between 5 and 12. + /// + /// Must remain consistent across pipeline stages to preserve deterministic + /// graph construction. + pub healpix_depth: u8, + + /// Time bin width (days, MJD TT) used for temporal binning. + /// + /// Overview + /// -------- + /// Controls the resolution of the uniform time binning scheme + /// (`UniformTimeBinner`) used during inter-night edge construction. + /// + /// Time is partitioned into fixed-width bins: + /// - width = `time_binner_width` (days, MJD TT), + /// - bin index: `k = floor((t - t0) / dt)`. + /// + /// Role in the engine + /// ------------------ + /// Used to: + /// - index seeds by time, + /// - restrict candidate searches to compatible temporal windows, + /// - reduce combinatorial explosion in edge generation. + /// + /// Trade-off + /// --------- + /// - Large width → coarse temporal grouping → more candidates per bin. + /// - Small width → finer pruning → more bins and indexing overhead. + /// + /// Must be strictly positive. Very small values increase index fragmentation + /// without significant gain beyond typical astrometric timing precision. + #[serde(deserialize_with = "de_time_days")] + pub time_binner_width: MJDTT, + + /// Root directory used for persistence (seeds, edges, trajectories, logs). + /// + /// The directory is stored as a UTF-8 string and exposed via: + /// - [`EngineConfig::storage_path`] (`&Utf8Path`) + /// - [`EngineConfig::storage_path_buf`] (`Utf8PathBuf`) + storage_path: String, + + /// Number of delta steps after which the graph is compacted. + /// This is a safeguard to keep load times bounded by preventing an unbounded number of deltas. + /// + /// When the number of delta files in the journal exceeds this threshold, + /// the stage triggers a full compaction (snapshot rebuild + delta pruning). + pub compact_graph_every_delta: usize, + + /// Minimum log level that the CLI subscriber will record. + /// + /// Accepted YAML values: `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`. + /// Defaults to `"info"`. This value is only read by the CLI; the engine + /// itself only emits tracing events and does not install any subscriber. + pub log_level: LogLevel, +} + +impl Default for EngineConfig { + /// Default engine configuration. + /// + /// Defaults are chosen to be safe and conservative for typical pipelines: + /// - version 1 schema, + /// - LSST/ZTF-like seeding defaults for pairs/triplets, + /// - ML Top-K edge construction defaults, + /// - a small `max_gap_nights` for bounded fan-out, + /// - `./storage` as the persistence root. + fn default() -> Self { + Self { + version: 1, + pairs: PairConfig::default(), + triplets: TripletConfig::default(), + edges: EdgeConfig::default(), + solver_config: SolverConfig::default(), + max_gap_nights: 3, + healpix_depth: 8, + time_binner_width: 0.021, // ~30 min in days + storage_path: "./storage".to_string(), + compact_graph_every_delta: 20, + pipeline_policy: PersistPolicy::Full, + binary_compression: Compression::None, + log_level: LogLevel::default(), + } + } +} + +impl EngineConfig { + /// Validate numeric ranges and cross-field consistency. + /// + /// Validation performed + /// -------------------- + /// - Schema version: + /// - `version` must be `1`. + /// - Seeding section: + /// - [`PairConfig::validate`], + /// - [`TripletConfig::validate`]. + /// - Edge section: + /// - [`EdgeConfig::validate`]. + /// + /// Return + /// ------ + /// - `Ok(())` if the configuration is valid. + /// - `Err(ConfigError)` if any validation step fails. + /// + /// Errors + /// ------ + /// - [`ConfigError::UnsupportedVersion`] if `version != 1`. + /// - [`ConfigError::Seed`] for pairs/triplets validation errors. + /// - [`ConfigError::Edges`] for edge validation errors. + /// + /// Notes + /// ----- + /// This function currently does not validate `max_gap_nights` nor the + /// predictor configuration embedded in `edges`. If those are operationally + /// required invariants, add checks here to centralize validation. + pub fn validate(&self) -> Result<(), ConfigError> { + if self.version != 1 { + return Err(ConfigError::UnsupportedVersion(self.version)); + } + + let storage_path = self.storage_path(); + if storage_path.as_str().is_empty() { + return Err(ConfigError::Invalid { + msg: "storage_path must not be empty".to_string(), + }); + } + + if storage_path.is_file() { + return Err(ConfigError::Invalid { + msg: format!( + "storage_path must be a directory, got file path '{}'", + storage_path + ), + }); + } + + if self.healpix_depth > 29 { + return Err(ConfigError::Invalid { + msg: format!( + "healpix_depth must be between 0 and 29, got {}", + self.healpix_depth + ), + }); + } + + if self.time_binner_width <= 0.0 { + return Err(ConfigError::Invalid { + msg: format!( + "time_binner_width must be positive, got {}", + self.time_binner_width + ), + }); + } + + // SeedError -> ConfigError via #[from] + self.pairs.validate()?; + self.triplets.validate()?; + + // EdgeConfigError -> ConfigError via #[from] + self.edges.validate()?; + + Ok(()) + } + + /// Return the storage root as a borrowed UTF-8 path. + /// + /// This is the recommended accessor for read-only operations. + pub fn storage_path(&self) -> &Utf8Path { + Utf8Path::new(&self.storage_path) + } + + /// Return the storage root as an owned UTF-8 path buffer. + /// + /// This is useful when the caller needs to join paths or store the result. + pub fn storage_path_buf(&self) -> Utf8PathBuf { + Utf8PathBuf::from(&self.storage_path) + } + + /// Return the configured maximum inter-night gap (in nights). + pub fn max_gap_nights(&self) -> u8 { + self.max_gap_nights + } +} + +/// Load and validate an [`EngineConfig`] from a YAML file plus optional environment overrides. +/// +/// Behavior +/// -------- +/// This function builds a `config::Config` by merging the following sources: +/// +/// 1) Rust defaults from [`EngineConfig::default`]. +/// 2) A YAML file at `path` (required). +/// 3) Environment overrides with prefix `FINK_FAT`, nested separator `__`. +/// +/// The merged config is deserialized into [`EngineConfig`], then validated with +/// [`EngineConfig::validate`]. +/// +/// Arguments +/// --------- +/// - `path`: path to a required YAML file. +/// +/// Return +/// ------ +/// - `Ok(EngineConfig)` if loading + deserialization + validation succeed. +/// - `Err(ConfigError)` otherwise. +/// +/// Errors +/// ------ +/// - [`ConfigError::ConfigRs`] for load/parse/deserialization failures. +/// - [`ConfigError::UnsupportedVersion`] for version mismatch. +/// - Section validation errors propagated via `ConfigError` (`Seed`, `Predictor`, `Edges`). +/// +/// Notes +/// ----- +/// - Unknown keys in YAML (or env) are rejected because `EngineConfig` and most +/// nested structs use `deny_unknown_fields`. +/// - This function is intended to be the single entry point for config loading +/// in binaries to ensure consistent validation behavior. +pub fn load_engine_config_validated(path: &Utf8Path) -> Result { + let cfg: EngineConfig = Config::builder() + // defaults from Rust + .add_source(Config::try_from(&EngineConfig::default())?) + // YAML file + .add_source(File::from(path.as_std_path()).required(true)) + // optional env overrides + .add_source( + Environment::with_prefix("FINK_FAT") + .separator("__") + .try_parsing(true), + ) + .build()? + .try_deserialize()?; + + cfg.validate()?; + Ok(cfg) +} + +#[cfg(test)] +mod engine_config_tests { + use crate::{engine_config::error::EdgeConfigError, error::PredictorParamError}; + + use super::*; + + use std::{ + collections::HashMap, + env, fs, + sync::{Mutex, OnceLock}, + }; + + use approx::{assert_relative_eq, assert_ulps_eq}; + use camino::Utf8PathBuf; + use proptest::prelude::*; + + /* --------------------------------------------------------------------- */ + /* Global env lock (env vars are process-global; tests must not race) */ + /* --------------------------------------------------------------------- */ + + fn env_lock() -> &'static Mutex<()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) + } + + /// RAII guard: temporarily set env vars and restore previous values on drop. + struct EnvGuard { + saved: HashMap>, + } + + impl EnvGuard { + fn set(vars: &[(&str, &str)]) -> Self { + let mut saved = HashMap::new(); + for (k, v) in vars { + let key = (*k).to_string(); + let prev = env::var(k).ok(); + saved.insert(key.clone(), prev); + unsafe { env::set_var(k, v) }; + } + Self { saved } + } + + /// Clear all env vars with a given prefix (e.g. "FINK_FAT__") and restore on drop. + fn clear(prefix: &str) -> Self { + let mut saved = HashMap::new(); + for (k, v) in env::vars() { + if k.starts_with(prefix) { + saved.insert(k.clone(), Some(v)); + unsafe { env::remove_var(k) }; + } + } + Self { saved } + } + } + + impl Drop for EnvGuard { + fn drop(&mut self) { + for (k, prev) in self.saved.drain() { + match prev { + Some(v) => unsafe { env::set_var(k, v) }, + None => unsafe { env::remove_var(k) }, + } + } + } + } + + /* --------------------------------------------------------------------- */ + /* Helpers */ + /* --------------------------------------------------------------------- */ + + fn write_tmp_yaml(contents: &str) -> Utf8PathBuf { + let mut p = std::env::temp_dir(); + let fname = format!( + "fink_fat_engine_config_test_{}_{}.yaml", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + p.push(fname); + fs::write(&p, contents).expect("write temp yaml"); + Utf8PathBuf::from_path_buf(p).expect("temp path should be valid UTF-8") + } + + fn load_from_yaml_str(yaml: &str) -> Result { + let _guard = env_lock().lock().unwrap(); + let _clear = EnvGuard::clear("FINK_FAT__"); + let path = write_tmp_yaml(yaml); + load_engine_config_validated(&path) + } + + /* --------------------------------------------------------------------- */ + /* Unit helpers (expected values) */ + /* --------------------------------------------------------------------- */ + + fn days_from_minutes(min: f64) -> f64 { + min / 1440.0 + } + + fn rad_from_arcmin(arcmin: f64) -> f64 { + (arcmin / 60.0).to_radians() + } + + fn rad_per_day_from_arcsec_per_hour(arcsec_per_hour: f64) -> f64 { + // arcsec/hour * 24 = arcsec/day ; /3600 = deg/day ; deg -> rad + ((arcsec_per_hour * 24.0) / 3600.0).to_radians() + } + + /* --------------------------------------------------------------------- */ + /* Deterministic unit tests */ + /* --------------------------------------------------------------------- */ + + #[test] + fn default_engine_config_is_valid() { + let cfg = EngineConfig::default(); + cfg.validate() + .expect("EngineConfig::default() must be valid"); + } + + #[test] + fn load_yaml_minimal_uses_defaults_and_validates() { + let cfg = load_from_yaml_str( + r#" +version: 1 +"#, + ) + .expect("config should load"); + + assert_eq!(cfg.version, 1); + + // A few stable default checks: + assert_eq!(cfg.max_gap_nights(), 3); + assert_eq!(cfg.storage_path(), Utf8Path::new("./storage")); + + assert_ulps_eq!(cfg.pairs.max_dt, 0.06, max_ulps = 0); + assert_eq!(cfg.edges.top_k_per_left, Some(32)); + + // Predictor defaults are expected valid. + assert!(cfg.edges.predictor_config.k_sigma > 0.0); + cfg.edges + .predictor_config + .validate() + .expect("default predictor_config must validate"); + } + + #[test] + fn load_yaml_rejects_unknown_top_level_keys() { + let err = load_from_yaml_str( + r#" +version: 1 +unknown_key: 123 +"#, + ) + .unwrap_err(); + + matches!(err, ConfigError::ConfigRs(_)) + .then_some(()) + .expect("expected ConfigError::ConfigRs (serde deny_unknown_fields)"); + } + + #[test] + fn load_yaml_rejects_unknown_nested_keys() { + // `pairs` has `deny_unknown_fields`. + let err = load_from_yaml_str( + r#" +version: 1 +pairs: + max_dt: "86.4 min" + not_a_real_field: 1 +"#, + ) + .unwrap_err(); + + matches!(err, ConfigError::ConfigRs(_)) + .then_some(()) + .expect("expected ConfigError::ConfigRs (nested deny_unknown_fields)"); + } + + #[test] + fn validate_rejects_unsupported_version() { + let err = load_from_yaml_str( + r#" +version: 2 +"#, + ) + .unwrap_err(); + + match err { + ConfigError::UnsupportedVersion(2) => {} + _ => panic!("expected UnsupportedVersion(2), got {err:?}"), + } + } + + #[test] + fn validate_rejects_invalid_pairs() { + // max_dt < 0 + let err = load_from_yaml_str( + r#" +version: 1 +pairs: + max_dt: -0.01 +"#, + ) + .unwrap_err(); + + match err { + ConfigError::Seed(_) => {} + _ => panic!("expected ConfigError::Seed, got {err:?}"), + } + } + + #[test] + fn validate_rejects_invalid_triplets_inconsistent_residual() { + // max_predicted_residual > max_pair_sep + let err = load_from_yaml_str( + r#" +version: 1 +triplets: + max_pair_sep: "1 arcmin" + max_predicted_residual: "2 arcmin" +"#, + ) + .unwrap_err(); + + match err { + ConfigError::Seed(_) => {} + _ => panic!("expected ConfigError::Seed, got {err:?}"), + } + } + + #[test] + fn validate_rejects_invalid_edge_config() { + // top_k_per_left == 0 is rejected by EdgeConfig::validate() + let err = load_from_yaml_str( + r#" +version: 1 +edges: + top_k_per_left: 0 +"#, + ) + .unwrap_err(); + + match err { + ConfigError::Edges(_) => {} + _ => panic!("expected ConfigError::Edges, got {err:?}"), + } + } + + #[test] + fn validate_rejects_invalid_predictor_config() { + // requires EngineConfig::validate() to call predictor_config.validate() + let err = load_from_yaml_str( + r#" +version: 1 +edges: + predictor_config: + k_sigma: 0.0 +"#, + ) + .unwrap_err(); + + match err { + ConfigError::Edges(EdgeConfigError::PredictorConfig( + PredictorParamError::InvalidKSigma(0.0), + )) => {} + _ => panic!("expected ConfigError::Predictor, got {err:?}"), + } + } + + #[test] + fn env_overrides_yaml_and_defaults() { + let _guard = env_lock().lock().unwrap(); + let _clear = EnvGuard::clear("FINK_FAT__"); + + let yaml = r#" +version: 1 +pairs: + max_dt: 0.06 +edges: + top_k_per_left: 10 +"#; + let path = write_tmp_yaml(yaml); + + // env > yaml > defaults + let _env = EnvGuard::set(&[ + ("FINK_FAT__PAIRS__MAX_DT", "0.05"), + ("FINK_FAT__EDGES__TOP_K_PER_LEFT", "42"), + ("FINK_FAT__PAIRS__ALLOW_SAME_TIMEBIN", "false"), + ]); + + let cfg = + load_engine_config_validated(&path).expect("config should load with env overrides"); + + assert_relative_eq!(cfg.pairs.max_dt, 0.05, epsilon = 1e-15); + assert_eq!(cfg.edges.top_k_per_left, Some(42)); + assert_eq!(cfg.pairs.allow_same_timebin, false); + } + + #[test] + fn env_bad_value_fails_to_load() { + let _guard = env_lock().lock().unwrap(); + let _clear = EnvGuard::clear("FINK_FAT__"); + + let yaml = r#" +version: 1 +"#; + let path = write_tmp_yaml(yaml); + + let _env = EnvGuard::set(&[("FINK_FAT__PAIRS__MAX_DT", "not-a-number")]); + + let err = load_engine_config_validated(&path).unwrap_err(); + match err { + ConfigError::ConfigRs(_) => {} + _ => panic!("expected ConfigError::ConfigRs, got {err:?}"), + } + } + + #[test] + fn yaml_accepts_units_for_pairs_and_triplets() { + // pairs.max_dt: "86.4 min" = 0.06 day + // pairs.max_angular_speed: "180 arcsec/hour" + // = 180 arcsec * 24 = 4320 arcsec/day = 1.2 deg/day + let cfg = load_from_yaml_str( + r#" +version: 1 + +pairs: + max_dt: "86.4 min" + max_angular_speed: "180 arcsec/hour" + allow_same_timebin: true + +triplets: + max_dt_between: "57.6 min" + max_pair_sep: "9 arcmin" + max_predicted_residual: "48 arcsec" + enforce_time_order: true +"#, + ) + .expect("config should load with unit strings"); + + // Time → days + assert_relative_eq!(cfg.pairs.max_dt, 0.06, epsilon = 1e-15); + assert_relative_eq!(cfg.triplets.max_dt_between, 0.04, epsilon = 1e-15); + + // Angles → radians + let expected_pair_sep = (9.0_f64 / 60.0).to_radians(); + assert_relative_eq!( + cfg.triplets.max_pair_sep, + expected_pair_sep, + max_relative = 1e-13 + ); + + let expected_residual = (48.0_f64 / 3600.0).to_radians(); + assert_relative_eq!( + cfg.triplets.max_predicted_residual, + expected_residual, + max_relative = 1e-13 + ); + + // Angular speed → rad/day + let expected_speed = (1.2_f64).to_radians(); + assert_relative_eq!( + cfg.pairs.max_angular_speed, + expected_speed, + max_relative = 1e-13 + ); + } + + #[test] + fn yaml_rejects_invalid_unit_strings() { + let err = load_from_yaml_str( + r#" +version: 1 +triplets: + max_pair_sep: "10 parsec" +"#, + ) + .unwrap_err(); + + match err { + ConfigError::ConfigRs(_) => {} + _ => panic!("expected ConfigError::ConfigRs, got {err:?}"), + } + } + + #[test] + fn yaml_rejects_invalid_rate_syntax() { + // Missing "/day" or "/hour" etc. + let err = load_from_yaml_str( + r#" +version: 1 +pairs: + max_angular_speed: "10 arcmin" +"#, + ) + .unwrap_err(); + + match err { + ConfigError::ConfigRs(_) => {} + _ => panic!("expected ConfigError::ConfigRs, got {err:?}"), + } + } + + #[test] + fn numeric_values_still_pass_through_unchanged() { + // For numeric YAML, we expect an exact float parse for this literal in practice; + // but to avoid brittle parsing edge cases, we validate at 0 ulps for the exact literal. + let cfg = load_from_yaml_str( + r#" +version: 1 +pairs: + max_dt: 0.123456 +"#, + ) + .expect("numeric values should still be accepted"); + + assert_ulps_eq!(cfg.pairs.max_dt, 0.123456, max_ulps = 0); + } + + /* --------------------------------------------------------------------- */ + /* Property-based tests (proptest) */ + /* --------------------------------------------------------------------- */ + + proptest! { + // Keep the number of cases reasonable to avoid slow CI. + #![proptest_config(ProptestConfig { cases: 64, .. ProptestConfig::default() })] + + #[test] + fn prop_units_minutes_to_days_pairs_max_dt(minutes in 0u32..(10_000u32)) { + let minutes_f = minutes as f64; + + let yaml = format!(r#" +version: 1 +pairs: + max_dt: "{} min" +"#, minutes); + + let cfg = load_from_yaml_str(&yaml).expect("config should load"); + let expected = days_from_minutes(minutes_f); + + // Small epsilon: exact rational / 1440 in f64. + assert_relative_eq!(cfg.pairs.max_dt, expected, epsilon = 1e-15); + } + + #[test] + fn prop_units_arcsec_per_hour_to_rad_per_day_pairs_max_angular_speed(arcsec_per_hour in 0u32..(50_000u32)) { + let x = arcsec_per_hour as f64; + + let yaml = format!(r#" +version: 1 +pairs: + max_angular_speed: "{} arcsec/hour" +"#, arcsec_per_hour); + + let cfg = load_from_yaml_str(&yaml).expect("config should load"); + let expected = rad_per_day_from_arcsec_per_hour(x); + + assert_relative_eq!(cfg.pairs.max_angular_speed, expected, max_relative = 1e-13); + } + + #[test] + fn prop_units_arcmin_to_rad_triplets_max_pair_sep(arcmin in 0u32..(60_000u32)) { + let arcmin_f = arcmin as f64; + + // Ensure config remains valid by also setting residual <= pair_sep. + // We use half the sep (integer division ok). + let residual_arcmin = (arcmin / 2) as u32; + + let yaml = format!(r#" +version: 1 +triplets: + max_pair_sep: "{} arcmin" + max_predicted_residual: "{} arcmin" +"#, arcmin, residual_arcmin); + + let cfg = load_from_yaml_str(&yaml).expect("config should load"); + let expected = rad_from_arcmin(arcmin_f); + + assert_relative_eq!(cfg.triplets.max_pair_sep, expected, max_relative = 1e-13); + } + + #[test] + fn prop_env_overrides_yaml_top_k_per_left(top_k_yaml in 1usize..512usize, top_k_env in 1usize..512usize) { + let _guard = env_lock().lock().unwrap(); + let _clear = EnvGuard::clear("FINK_FAT__"); + + let yaml = format!(r#" +version: 1 +edges: + top_k_per_left: {} +"#, top_k_yaml); + + let path = write_tmp_yaml(&yaml); + + let _env = EnvGuard::set(&[ + ("FINK_FAT__EDGES__TOP_K_PER_LEFT", &top_k_env.to_string()), + ]); + + let cfg = load_engine_config_validated(&path).expect("config should load"); + prop_assert_eq!(cfg.edges.top_k_per_left, Some(top_k_env)); + } + } +} diff --git a/crates/fink-fat-engine/src/engine_config/pair_config.rs b/crates/fink-fat-engine/src/engine_config/pair_config.rs new file mode 100644 index 00000000..ba16283c --- /dev/null +++ b/crates/fink-fat-engine/src/engine_config/pair_config.rs @@ -0,0 +1,370 @@ +//! # Pair generation configuration (`PairConfig`) +//! +//! This module defines the configuration parameters used to generate **pairs** +//! of alerts `(a, b)` within a single night (or within a short time window). +//! +//! A **pair** is the smallest seeding unit in the engine: two detections that +//! are close in time, consistent with a maximum on-sky **angular speed**, and +//! compatible in photometry. +//! +//! Pair generation is designed as a **cheap, conservative pre-filter**: +//! it should keep most plausible moving-object candidates while limiting the +//! combinatorial explosion that would occur if every alert could pair with many +//! others. +//! +//! ----------------------------------------------------------------------------- +//! Conceptual model +//! ----------------------------------------------------------------------------- +//! +//! For two alerts `a` (anchor) and `b` (candidate), with `t_b > t_a`: +//! +//! - Temporal constraint: +//! - `Δt = t_b - t_a` must be within `max_dt`. +//! - Kinematic constraint (angular speed): +//! - Let `Δθ = ang_sep(a, b)` be the on-sky angular separation (radians). +//! - The candidate must satisfy: +//! `Δθ / Δt ≤ max_angular_speed`. +//! - Photometric constraint: +//! - The candidate must satisfy a configurable brightness / flux similarity +//! test controlled by `max_flux_difference`. +//! +//! The exact photometry metric depends on the pairing implementation (flux space, +//! magnitude space, normalized flux difference, etc.). This configuration +//! parameter is intentionally **unit-agnostic** at the config level: it must +//! match what the pairing kernel expects. +//! +//! ----------------------------------------------------------------------------- +//! Spatial bucket search and `sep_cap()` +//! ----------------------------------------------------------------------------- +//! +//! The pair builder typically queries a spatio-temporal index (bucket grid) to +//! avoid scanning all alerts. To do that, it needs an **upper bound** on the +//! maximum possible separation between two alerts that could pass the kinematic +//! constraint. +//! +//! A conservative bound is: +//! +//! ```text +//! sep_cap = max_angular_speed * max_dt +//! ``` +//! +//! This bound is used only for **index traversal** (which buckets to visit). +//! The actual acceptance test remains the per-candidate inequality +//! `Δθ / Δt ≤ max_angular_speed`. +//! +//! ----------------------------------------------------------------------------- +//! Serialization and units +//! ----------------------------------------------------------------------------- +//! +//! This configuration is `serde`-deserializable (YAML / TOML / JSON) and uses +//! project-level unit parsers to make configuration files human-friendly. +//! +//! ## Numeric vs string quantities +//! +//! For fields that use `deserialize_with = ...` from `engine_config::units`: +//! +//! - A **numeric YAML scalar** is accepted and is interpreted as already being +//! in **canonical engine units**. +//! - A **string YAML scalar** is accepted and is parsed as a `` +//! quantity. +//! +//! Concretely for [`PairConfig`]: +//! +//! - `max_dt` uses [`de_time_days`] and is stored as **days (TT)**. +//! - Numeric form: `0.06` means `0.06 days`. +//! - String form: `"86.4 min"`, `"1.44 h"`, `"30 sec"`, `"0.06 day"` are accepted. +//! - `max_angular_speed` uses [`de_ang_speed_rad_per_day`] and is stored as +//! **radians per day**. +//! - Numeric form: `5.0e-2` means `0.05 rad/day`. +//! - String form: must be written as an explicit rate `/