diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fe7099..97be47e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ on: pull_request: branches: - main + - develop workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index 24c241f..8d9f389 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ htmlcov/ .cache nosetests.xml coverage.xml +cov.xml *.cover *.py,cover .hypothesis/ @@ -194,4 +195,10 @@ charmm36.ff/ !docs/public/diagrams/**/*.png !docs/public/mdfactory-logo-v1.0.png +# Allow GROMACS test fixtures +!mdfactory/tests/fixtures/** + .pixi/ + +# local proteinbox end-to-end build outputs for inspection (not for commit) +proteinbox_e2e_outputs/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c9ae49d..e0da9b4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,3 +2,4 @@ - Marc Siggel — Merck KGaA, Darmstadt, Germany - Maximilian Scheurer — Merck KGaA, Darmstadt, Germany +- Gregor Weiss — Merck KGaA, Darmstadt, Germany diff --git a/README.md b/README.md index cab96a3..14cc91e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ MDFactory is a high-throughput library for setting up molecular dynamics (MD) si - **System building** — Mixedbox, bilayer, and LNP build types from YAML or CSV input - **Parametrization** — OpenFF/SMIRNOFF and CGenFF with automatic per-molecule caching -- **Batch processing** — CSV-driven Nextflow pipelines for parallel builds and GROMACS runs on HPC +- **Batch processing** — CSV-driven Parsl pipelines for parallel builds and GROMACS runs on HPC - **Analysis** — Registered analysis and artifact types with parquet output, local or SLURM execution - **Data integration** — Push/pull/sync across SQLite, CSV, and Palantir Foundry backends @@ -83,7 +83,7 @@ pip install -e .[dev] mdfactory build system.yaml simulation_dir ``` -See the [Quick Start guide](https://emdgroup.github.io/mdfactory/docs/quick-start) for details on bulk CSV input and Nextflow pipelines. +See the [Quick Start guide](https://emdgroup.github.io/mdfactory/docs/quick-start) for details on bulk CSV input and running simulations. ## Configuration diff --git a/config_templates/config.ini b/config_templates/config.ini index e8573a1..d495fc8 100644 --- a/config_templates/config.ini +++ b/config_templates/config.ini @@ -13,6 +13,12 @@ [cgenff] SILCSBIODIR = +[gromacs] +; GMX_PATH defaults to looking up "gmx" on PATH +GMX_PATH = +; FORCEFIELD_DIR defaults to /forcefields +FORCEFIELD_DIR = + [storage] ; PARAMETERS defaults to /parameters PARAMETERS = @@ -40,3 +46,10 @@ ANALYSIS_NAME = analysis RUN_DB_PATH = /Group Functions/mdfactory/runs ANALYSIS_DB_PATH = /Group Functions/mdfactory/analysis ARTIFACT_DB_PATH = /Group Functions/mdfactory/artifacts + +[slurm] +; Optional manual overrides. When empty, values are autodiscovered via sinfo/sacctmgr. +ACCOUNT = +PARTITION_CPU = +PARTITION_GPU = +DEFAULT_QOS = diff --git a/docs/README.md b/docs/README.md index ea26aae..2e966b1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,24 @@ This folder contains the Fumadocs + Next.js application that renders the MDFacto documentation. The site mixes hand-written guides with API pages generated straight from the Python package. -## Prerequisites +## Quick start with pixi + +The root `pyproject.toml` defines a `docs` environment that installs Bun, the JS +dependencies, and `fumapy-generate` automatically: + +```bash +pixi run -e docs docs-dev # dev server with hot-reload +pixi run -e docs docs-build # full static build to docs/out/ +pixi run -e docs docs-generate # regenerate API docs only +``` + +Each command chains through `docs-install` → `docs-fumapy` automatically, so a +single command handles all setup. On subsequent runs the dependency steps are +fast (no-op when already satisfied). + +## Manual setup (without pixi) + +### Prerequisites - [Bun](https://bun.sh/) 1.0+ (fast JavaScript runtime and package manager) - Python 3.11 (to install `mdfactory` and `fumapy`) @@ -18,7 +35,7 @@ python3.11 -m pip install -e .. # expose the local packa python3.11 -m pip install ./node_modules/fumadocs-python ``` -## Commands +### Commands ```bash bun run docs:generate # runs fumapy-generate + converts JSON into MDX diff --git a/docs/content/docs/developer-guide/architecture.mdx b/docs/content/docs/developer-guide/architecture.mdx index 0e6e121..61d92c5 100644 --- a/docs/content/docs/developer-guide/architecture.mdx +++ b/docs/content/docs/developer-guide/architecture.mdx @@ -29,7 +29,7 @@ graph TD flowchart LR A[CSV or YAML inputs] --> B[prepare-build or build] B --> C[Simulation directories] - C --> D[Local run or Nextflow] + C --> D[Local run or Parsl orchestration] D --> E[Trajectory and output files] E --> F[Simulation.run_analysis / run_artifact] F --> G[.analysis parquet, artifacts, metadata.json] @@ -164,12 +164,26 @@ For analysis and artifact sync, the code uses: ## Workflow orchestration -The repository contains Nextflow scripts under `workflows/`. The scripts that most directly align with the current CLI are: +The `mdfactory/orchestration/` package provides Parsl-based parallel orchestration for both building and simulating systems on local machines or SLURM clusters. -- `build.nf` -- `simulate.nf` +### Architecture layers -These scripts wrap the checked-in `mdfactory prepare-build` and `mdfactory build` commands plus a fixed GROMACS run chain. +```mermaid +graph TD + A[CLI: build --slurm / simulate --slurm] --> B[config.py: ExecutorConfig / SlurmExecutorConfig] + B --> C[session.py: parsl_session context manager] + C --> D[apps.py: Parsl python_app / bash_app definitions] + D --> E[stages.py: STAGE_REGISTRY + run_stage dispatch] + E --> F[build.py: build_systems / simulate.py: run_simulations] + F --> G[Rich progress UI + result collection] +``` + +- **`config.py`**: Pydantic models for local and SLURM execution. `ExecutorConfig` handles local runs; `SlurmExecutorConfig` adds SLURM-specific fields (account, partition, walltime, gres, per-stage overrides). Both serialize to/from YAML via `from_yaml()` and `model_dump()`. +- **`session.py`**: `parsl_session()` context manager that owns the Parsl DataFlowKernel lifecycle. On exit it calls `parsl.clear()` and explicitly `scancel`s any lingering SLURM job IDs. +- **`apps.py`**: Defines `get_build_app()` (python\_app for system building), `get_grompp_app()` and `get_mdrun_app()` (bash\_apps for GROMACS). Nearly all conditional logic (GPU, MPI, thread counts, restart flags) is resolved in Python before generating the bash script — the only permitted runtime branch is a small `if/elif/else/fi` block for binary detection when `gmx_binary` is set to `auto`. +- **`stages.py`**: `STAGE_REGISTRY` declares the canonical pipeline (EM → NVT → NPT → Production) as `StageSpec` dataclasses. `run_stage()` is the single generic dispatch function for all stages. +- **`build.py`** / **`simulate.py`**: Top-level entry points. `build_systems()` submits parallel build tasks; `run_simulations()` chains stages sequentially per simulation with checkpoint detection and trajectory validation. +- **`tui.py`**: Interactive SLURM configuration wizard using `questionary`. Auto-discovers cluster hardware via `sinfo`/`sacctmgr` and persists the result as a reusable YAML file. ## Extension points diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index 1a0e570..da97308 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -19,7 +19,7 @@ MDFactory is an open source, end-to-end simulation manager for high-throughput m /> - + diff --git a/docs/content/docs/quick-start.mdx b/docs/content/docs/quick-start.mdx index b8422cf..65de8a1 100644 --- a/docs/content/docs/quick-start.mdx +++ b/docs/content/docs/quick-start.mdx @@ -78,29 +78,29 @@ This writes GROMACS-ready build output into `simulation_dir`. -## Run with Nextflow (optional) +## Run simulations (optional) -For high-throughput runs, use the checked-in workflows: +Run the GROMACS simulation chain (EM → NVT → NPT → Production) locally: -Build many systems from CSV: +```bash +mdfactory simulate output_systems/ +``` + +For high-throughput runs on a SLURM cluster, generate a config interactively and pass it: ```bash -nextflow run workflows/build.nf \ - --csv_file sample_input.csv \ - --output_dir output_systems +mdfactory config slurm # interactive wizard → saves slurm_executor.yaml +mdfactory simulate output_systems/ --slurm slurm_executor.yaml ``` -Run the GROMACS chain using the generated summary YAML: +Or use `--slurm tui` to launch the wizard inline: ```bash -nextflow run workflows/simulate.nf \ - -c workflows/simulate.config \ - --base_dir output_systems \ - --config_yaml output_systems/sample_input.yaml +mdfactory simulate output_systems/ --slurm tui ``` - -The shipped `simulate.config` contains SLURM settings tuned for a specific cluster. Edit this file to match your HPC environment before running. See [Running on HPC Clusters](/docs/user-guide/running-on-hpc) for details. + +SLURM orchestration requires the `parsl` extra: `pip install "mdfactory[parsl]"`. See [Running on HPC Clusters](/docs/user-guide/running-on-hpc) for details. diff --git a/docs/content/docs/user-guide/cli-reference.mdx b/docs/content/docs/user-guide/cli-reference.mdx index 68d8335..b16a7d6 100644 --- a/docs/content/docs/user-guide/cli-reference.mdx +++ b/docs/content/docs/user-guide/cli-reference.mdx @@ -17,12 +17,22 @@ mdfactory prepare-build [output_dir] ### `mdfactory build` -Build one simulation from a YAML file into the chosen output directory. +Build MD system(s) from YAML, CSV, or summary input. Supports three input modes: + +- **Single YAML**: builds one system locally +- **CSV file**: builds systems from each row (parallel with `--slurm`, sequential without) +- **Summary YAML**: dispatches builds for previously prepared systems ```bash -mdfactory build [output_dir] +mdfactory build [options] ``` +Options: + +- `--output` — output directory (default: `.`) +- `--slurm` — SLURM executor config: path to YAML file, or `'tui'` for interactive setup +- `--dry-run` — print what would be built without executing + ### `mdfactory check-csv` Validate a CSV by converting each row into a `BuildInput` model and, for bilayers, running the current buildability checks. @@ -31,6 +41,79 @@ Validate a CSV by converting each row into a `BuildInput` model and, for bilayer mdfactory check-csv ``` +## Simulation commands + +### `mdfactory simulate` + +Run GROMACS MD simulations via Parsl. The simulation pipeline runs four stages by default: EM → NVT → NPT → Production. + +```bash +mdfactory simulate [options] +``` + +Options: + +- `--slurm` — SLURM config YAML or `'tui'` for interactive setup +- `--stages` — stages to run (EM, NVT, NPT, Production); defaults to all +- `--checkpoint` — checkpoint mode: `auto` (default), `skip`, `force` +- `--hash` — filter by hash prefix (can specify multiple) +- `--dry-run` — preview plan without executing +- `--clean` — remove simulation outputs before running (respects `--stages` filter) +- `--max-rescue` — max rescue tiers for physics failures (default: 3; set to 0 to disable) + +Usage examples: + +```bash +# Local execution (all stages) +mdfactory simulate output_dir/ + +# SLURM with interactive config +mdfactory simulate output_dir/ --slurm tui + +# Resume from checkpoint with existing config +mdfactory simulate output_dir/ --slurm gpu.yaml --checkpoint auto + +# Equilibration only +mdfactory simulate output_dir/ --stages EM NVT NPT + +# Filter specific simulations +mdfactory simulate output_dir/ --hash abc123 def456 + +# Dry-run preview +mdfactory simulate output_dir/ --slurm gpu.yaml --dry-run + +# Clean outputs and re-run (e.g. after a killed job left stale checkpoints) +mdfactory simulate output_dir/ --clean --slurm gpu.yaml + +# Clean only Production stage before re-running it +mdfactory simulate output_dir/ --clean --stages Production --slurm gpu.yaml + +# Preview what --clean would delete without acting +mdfactory simulate output_dir/ --clean --dry-run +``` + +## Configuration commands + +### `mdfactory config slurm` + +Interactive wizard to configure a SLURM executor and save to YAML. Queries the local SLURM scheduler for available accounts, partitions, and hardware, then walks through resource selection interactively. + +The result is saved to a YAML file that can be reused with `mdfactory build --slurm ` or `mdfactory simulate --slurm `. + +On non-SLURM machines, falls back to manual text entry. + +```bash +mdfactory config slurm +``` + +### `mdfactory config cluster` + +Show discovered SLURM cluster information. Queries the local SLURM scheduler and displays available partitions, accounts, and QOS policies. Useful for verifying autodiscovery before submitting jobs. + +```bash +mdfactory config cluster [--json] +``` + ## Utility commands ### `mdfactory clean` diff --git a/docs/content/docs/user-guide/running-on-hpc.mdx b/docs/content/docs/user-guide/running-on-hpc.mdx index d4e97d2..64d30fa 100644 --- a/docs/content/docs/user-guide/running-on-hpc.mdx +++ b/docs/content/docs/user-guide/running-on-hpc.mdx @@ -3,13 +3,14 @@ title: Running on HPC Clusters description: Deploy MDFactory workflows on SLURM-based HPC clusters --- -MDFactory works well on SLURM-based HPC clusters for large-scale simulation campaigns. +MDFactory works well on SLURM-based HPC clusters for large-scale simulation campaigns. The orchestration layer uses [Parsl](https://parsl-project.org/) to submit and manage GROMACS jobs through SLURM. ## Prerequisites - SSH access to your HPC cluster - SLURM workload manager -- Python 3.11+, Nextflow, and Apptainer/Singularity available (via modules or otherwise) +- Python 3.11+ +- GROMACS available (via modules or otherwise) ## Installation on HPC @@ -28,19 +29,19 @@ ssh your-cluster.example.com Load required modules (names may vary by site): ```bash -module load nextflow apptainer python/3.11 +module load python/3.11 gromacs ``` -Install MDFactory in your project directory: +Install MDFactory with the Parsl extra: ```bash cd /path/to/your/project git clone https://github.com/emdgroup/mdfactory.git cd mdfactory -pip install -e . +pip install -e ".[parsl]" ``` @@ -51,158 +52,179 @@ pip install -e . Most SLURM clusters offer several partitions. Choose based on your workload: - **CPU partition**: CPU-only MD engines, preprocessing, analysis -- **GPU partition**: GPU-accelerated GROMACS/LAMMPS runs +- **GPU partition**: GPU-accelerated GROMACS runs - **Large-memory partition**: Very large systems or multi-GPU runs - **Interactive partition**: Testing, visualization, debugging -Consult your cluster's documentation for available partitions, GPU types, and resource limits. +Use `mdfactory config cluster` to discover available partitions, accounts, and QOS policies on your cluster: -## Nextflow configuration +```bash +mdfactory config cluster +mdfactory config cluster --json # machine-readable output +``` + +## SLURM executor configuration -Create `nextflow.config` in your pipeline directory. Adjust partition names, GPU types, and resource limits to match your cluster: +MDFactory uses a YAML file to configure SLURM resources. Generate one interactively: + +```bash +mdfactory config slurm +``` -```groovy -process { - executor = 'slurm' - queue = 'gpu' +The wizard auto-discovers your cluster's partitions, accounts, and hardware (GPU types, memory, CPU counts) and walks through resource selection. The result is saved to a YAML file (default: `slurm_executor.yaml`). - withName: 'minimization' { - cpus = 8 - memory = '16 GB' - time = '2h' - clusterOptions = '--gres=gpu:1' - } + +The wizard is organized into three distinct sections: - withName: 'equilibration' { - cpus = 16 - memory = '32 GB' - time = '8h' - clusterOptions = '--gres=gpu:2' - } +1. **SLURM Allocation** — account, partition, walltime, CPUs, GPUs, memory +2. **Execution Environment** — auto-detected pixi/conda activation, GROMACS modules +3. **Per-Stage Tuning** — resource overrides for individual simulation stages (EM, NVT, etc.) - withName: 'production' { - cpus = 32 - memory = '64 GB' - time = '24h' - clusterOptions = '--gres=gpu:4' - } -} +GROMACS modules are discovered via `module avail` on the login node and offered as a selection. The pixi/conda environment is auto-detected from the filesystem. + -executor { - $slurm { - queueSize = 50 - submitRateLimit = '10 sec' - } -} +### Manual configuration -singularity { - enabled = true - autoMounts = true -} +You can also write the YAML file directly. See [`examples/slurm_executor.yaml`](https://github.com/emdgroup/mdfactory/blob/main/examples/slurm_executor.yaml) for an annotated reference with all available fields. + +A minimal GPU configuration with the structured `environment` section: + +```yaml +provider: slurm +account: my-account +partition: gpu +walltime: "8h" +cpus_per_node: 16 +gres: "gpu:1" +max_workers_per_node: 1 +max_blocks: 4 +environment: + modules: + - gromacs/2024 + pixi_manifest: /path/to/project ``` -## SLURM job template -Create a wrapper script `run_mdfactory.sh`. Adjust paths and module names for your site: + +### Per-stage resource overrides + +Override `cpus_per_node`, `gres`, or `gmx_binary` for individual simulation stages: + +```yaml +stage_overrides: + EM: + gres: null # CPU-only minimization + cpus_per_node: 4 + Production: + gres: "gpu:2" # more GPUs for production + cpus_per_node: 32 +``` + +### Key configuration fields + +| Field | Description | Default | +|-------|-------------|---------| +| `provider` | `"local"` or `"slurm"` | `"local"` | +| `account` | SLURM account (`--account`) | — | +| `partition` | SLURM partition (`--partition`) | — | +| `walltime` | Wall-clock time (`--time`) | `"2h"` | +| `nodes` | Nodes per job (`--nodes`) | `1` | +| `cpus_per_node` | CPUs per node | `12` | +| `gres` | GPU resources (`--gres`) | — | +| `mem` | Memory per node (`--mem`) | — | +| `max_workers_per_node` | Parallel tasks per node | `1` | +| `max_blocks` | Max concurrent SLURM allocations | `1` | +| `available_accelerators` | GPU count or device IDs for `CUDA_VISIBLE_DEVICES` | `0` | +| `retries` | Infrastructure retry count (OOM, timeout, preemption) | `3` | +| `gmx_binary` | `"auto"`, `"gmx"`, or `"gmx_mpi"` | `"auto"` | +| `environment.modules` | Module loads for workers (e.g. `["gromacs/2024"]`) | `[]` | +| `environment.pixi_manifest` | Path to pixi project root | auto-detected | +| `environment.conda_env` | Conda env name to activate | — | +| `environment.extra_init` | Custom shell commands | `""` | + +## Running simulations on SLURM + +Once your config is ready, build and simulate with: + +```bash +# Build systems (parallel on SLURM) +mdfactory build systems.csv --output results --slurm slurm_executor.yaml + +# Run the GROMACS chain (parallel on SLURM) +mdfactory simulate results/ --slurm slurm_executor.yaml +``` + + +MDFactory has two independent retry mechanisms on SLURM: + +- **`retries`** (executor config) — Parsl re-submits the same SLURM job on infrastructure failures (OOM, timeout, node preemption). The command is unchanged. +- **`--max-rescue`** (CLI flag, default 3) — Automatically halves the step size and doubles `nsteps` for physics failures (LINCS errors, exploding box) in EM/NVT/NPT stages. Use `--max-rescue 0` to disable. + +When sizing walltime, account for potential rescue tiers — each tier roughly doubles the wall-clock cost of a rescue-eligible stage. + + +### SLURM job wrapper + +For batch scheduling, wrap the commands in a lightweight SLURM script. The controller job only needs a CPU allocation — Parsl submits the compute-heavy GROMACS work as separate SLURM jobs: ```bash #!/usr/bin/env bash #SBATCH --job-name=mdfactory #SBATCH -A #SBATCH --partition=cpu -#SBATCH --cpus-per-task=4 -#SBATCH --mem=8G +#SBATCH --cpus-per-task=2 +#SBATCH --mem=4G #SBATCH --time=24:00:00 #SBATCH --output=mdfactory_%j.out #SBATCH --error=mdfactory_%j.err -#SBATCH --export=ALL set -euo pipefail -module load nextflow apptainer python/3.11 - -SHARED_SCRATCH="${SCRATCH}" -SRC_DIR="$(realpath "${SRC_DIR:-${SLURM_SUBMIT_DIR:-$PWD}}")" -JOB_TAG="${SLURM_JOB_NAME}_jobid_${SLURM_JOB_ID}" -JOB_DIR="$SHARED_SCRATCH/$JOB_TAG" +module load python/3.11 -APPT_CACHE="$JOB_DIR/nf_apptainer_cache" -APPT_TMP="${TMPDIR:-/tmp}/${USER}/appt_tmp" +CSV_FILE="${CSV_FILE:-systems.csv}" +OUTPUT_DIR="${OUTPUT_DIR:-results}" +SLURM_CONFIG="${SLURM_CONFIG:-slurm_executor.yaml}" -mkdir -p "$APPT_CACHE" "$APPT_TMP" +# Stage 1: build +mdfactory build "$CSV_FILE" --output "$OUTPUT_DIR" --slurm "$SLURM_CONFIG" -export APPTAINER_TMPDIR="$APPT_TMP" -export APPTAINER_CACHEDIR="$APPT_CACHE" -export NXF_SINGULARITY_CACHEDIR="$APPT_CACHE" -export NXF_HOME="$JOB_DIR/.nextflow" -export NXF_OPTS=${NXF_OPTS:-'-Xms1g -Xmx8g'} +# Stage 2: simulate +mdfactory simulate "$OUTPUT_DIR" --slurm "$SLURM_CONFIG" -echo "[INFO] Job directory: $JOB_DIR" -echo "[INFO] Source directory: $SRC_DIR" -echo "[INFO] Controller node: $(hostname)" +echo "[INFO] Pipeline completed" +``` -mkdir -p "$JOB_DIR"/{work,results} -rsync -a --delete \ - --exclude '.git' \ - --exclude 'work' \ - --exclude 'results' \ - --exclude '.nextflow*' \ - "$SRC_DIR/" "$JOB_DIR/" +Submit with: -rsync -a "$HOME/nf_apptainer_cache/" "$APPT_CACHE/" 2>/dev/null || true +```bash +sbatch run_mdfactory.sh +``` -DEST_DIR="${DEST_DIR:-$HOME/mdfactory_results/$JOB_TAG}" -mkdir -p "$DEST_DIR" +### Preview before submitting -CSV_FILE="${CSV_FILE:-systems.csv}" -CSV_BASENAME="$(basename "$CSV_FILE")" -SUMMARY_YAML="${CSV_BASENAME%.csv}.yaml" - -finish() { - set +e - echo "[INFO] Copying results to $DEST_DIR" - - rsync -a "$JOB_DIR/results/" "$DEST_DIR/results/" - rsync -a "$JOB_DIR"/.nextflow* "$DEST_DIR/" 2>/dev/null || true - rsync -a "$JOB_DIR"/mdfactory_*.out "$DEST_DIR/" 2>/dev/null || true - rsync -a "$APPT_CACHE/" "$HOME/nf_apptainer_cache/" - - if [[ "${CLEANUP:-true}" == "true" ]]; then - rm -rf "$JOB_DIR" - fi -} -trap finish EXIT - -cd "$JOB_DIR" - -nextflow run workflows/build.nf \ - --csv_file "$CSV_FILE" \ - --output_dir "$JOB_DIR/results" \ - -work-dir "$JOB_DIR/work/build" \ - "$@" - -nextflow run workflows/simulate.nf \ - -c workflows/simulate.config \ - --base_dir "$JOB_DIR/results" \ - --config_yaml "$JOB_DIR/results/$SUMMARY_YAML" \ - -work-dir "$JOB_DIR/work/simulate" \ - "$@" +Use `--dry-run` to verify the execution plan without launching any jobs: -echo "[INFO] Pipeline completed" +```bash +mdfactory simulate results/ --slurm slurm_executor.yaml --dry-run ``` -## Submit the job +### Checkpoint and restart + +Simulations automatically resume from checkpoints. If a job is interrupted, re-run the same command — completed stages are skipped: ```bash -sbatch run_mdfactory.sh +mdfactory simulate results/ --slurm slurm_executor.yaml --checkpoint auto ``` -Check status: +If a job was killed mid-flight and left stale checkpoints (e.g. `.cpt` files without matching trajectory files), use `--clean` to reset before re-running: ```bash -squeue -u $USER +mdfactory simulate results/ --clean --slurm slurm_executor.yaml ``` +This removes simulation outputs while preserving build inputs. Use `--clean --dry-run` to preview what would be deleted. The `--stages` filter is respected — `--clean --stages Production` only cleans Production files. + ## Submit analyses with submitit For a full walkthrough of running analyses, see the @@ -327,40 +349,36 @@ tail -f mdfactory_.out Use your cluster's storage tiers appropriately: - **Project/home storage**: Input files, final results, code -- **Scratch storage** (`$SCRATCH`): Temporary files, work directories (typically auto-cleaned) +- **Scratch storage** (`$SCRATCH`): Temporary files, Parsl run directories (typically auto-cleaned) - **Local node storage** (`$TMPDIR` or similar): Fast node-local disk for I/O intensive tasks +Configure the Parsl run directory to use scratch: + +```yaml +run_dir: /scratch/$USER/parsl/mdfactory +``` + ## Workflow diagram ```mermaid graph LR A[Submit Job] --> B[Controller] - B --> C[Spawn Tasks] - C --> D[GPU Queue] + B --> C[Parsl submits tasks] + C --> D[GPU/CPU Queues] D --> E[Results] E --> F[Copy to Project] ``` ## Best practices -**Use scratch for work directories** - -Temporary Nextflow work files should go to scratch: - -```groovy -workDir = "${System.getenv('SCRATCH')}/work" -``` - **Request appropriate resources** Match resource requests to actual needs: -```groovy -process { - // Don't over-request - cpus = 8 // not 256 - memory = '16 GB' // not 1 TB -} +```yaml +# Don't over-request +cpus_per_node: 8 # not 256 +mem: "16G" # not 1TB ``` **Monitor job efficiency** @@ -397,10 +415,10 @@ Common reasons: **Out of scratch space** -Clean old work directories: +Clean old Parsl run directories: ```bash -rm -rf $SCRATCH/work/ +rm -rf /scratch/$USER/parsl/mdfactory/ ``` **Permission errors** diff --git a/docs/content/docs/user-guide/troubleshooting.mdx b/docs/content/docs/user-guide/troubleshooting.mdx index b3acd83..7a03d33 100644 --- a/docs/content/docs/user-guide/troubleshooting.mdx +++ b/docs/content/docs/user-guide/troubleshooting.mdx @@ -79,14 +79,87 @@ The build code uses OpenMM-based compression or equilibration steps. For difficu ## Workflow issues - + -`workflows/simulate.nf` expects: +`mdfactory simulate` requires each simulation directory to contain the standard build outputs (`system.pdb`, `topology.top`, and `.mdp` files). Common causes: -- a `--base_dir` containing the hash directories -- a `--config_yaml` summary YAML produced by `mdfactory prepare-build` or `workflows/build.nf` +- The build step (`mdfactory build`) did not complete successfully for some hashes. +- The `--source` path does not point to the directory containing the hash subdirectories. +- Use `--dry-run` to preview which directories would be simulated and which are skipped. -Make sure both point to the same stage-1 output tree. +```bash +mdfactory simulate output_dir/ --slurm gpu.yaml --dry-run +``` + + + + +In `--checkpoint auto` mode (the default), MDFactory validates trajectory integrity using frame counts. A stage is skipped when its output `.gro` file and prerequisite checkpoint (`.cpt`) both exist and pass validation. + +To force re-execution of all stages: + +```bash +mdfactory simulate output_dir/ --slurm gpu.yaml --checkpoint force +``` + +To skip validation and rely only on file existence: + +```bash +mdfactory simulate output_dir/ --slurm gpu.yaml --checkpoint skip +``` + + + + +When a SLURM job is cancelled mid-flight, checkpoint files (`.cpt`) may be left behind +without corresponding trajectory files. Use `--clean` to reset simulation directories to +post-build state before re-running: + +```bash +# Clean all stages and re-run +mdfactory simulate output_dir/ --clean --slurm gpu.yaml + +# Clean only Production and re-run it +mdfactory simulate output_dir/ --clean --stages Production --slurm gpu.yaml + +# Preview what would be deleted +mdfactory simulate output_dir/ --clean --dry-run +``` + +The `--clean` flag removes simulation outputs (`.tpr`, `.cpt`, `.log`, `.edr`, `.gro`, +`.xtc`, `.trr`, rescue MDPs, GROMACS backup files) while preserving build inputs +(`system.pdb`, `topology.top`, template `.mdp` files, `.itp`, `.yaml`). It respects +the `--stages` filter — only files belonging to requested stages are deleted. + + + + +MDFactory automatically rescues EM, NVT, and NPT stages that fail with physics errors +(LINCS warnings, exploding box, coordinate constraint failures). On each rescue tier the +step size is halved and `nsteps` is doubled to maintain the same elapsed simulation time, +up to 3 tiers by default. + +You will see log messages like: + +``` +WARNING — RESCUE tier 1/3 for abc123/EM: using em_rescue_t1.mdp +WARNING — RESCUE tier 1 for EM: emstep = 0.01 → 0.005 +``` + +If all rescue tiers are exhausted the simulation is marked as failed. To increase or +decrease the retry budget: + +```bash +# Allow up to 5 rescue tiers +mdfactory simulate output_dir/ --max-rescue 5 + +# Disable rescue entirely +mdfactory simulate output_dir/ --max-rescue 0 +``` + +Production stages are never rescued — by that point the system should be stable. +Infrastructure failures (OOM, SLURM timeout, preemption) are handled separately by +Parsl's built-in retry mechanism (`retries` in the executor config). diff --git a/docs/content/docs/user-guide/tutorial-mixedbox-campaign.mdx b/docs/content/docs/user-guide/tutorial-mixedbox-campaign.mdx index 4f77c09..c5d5f32 100644 --- a/docs/content/docs/user-guide/tutorial-mixedbox-campaign.mdx +++ b/docs/content/docs/user-guide/tutorial-mixedbox-campaign.mdx @@ -17,7 +17,7 @@ For each alcohol, the tutorial uses four solvent fractions. That gives a total o 1. prepare 20 systems from one CSV 2. build all simulation directories -3. optionally run the checked-in GROMACS Nextflow workflow +3. optionally run the GROMACS simulation chain 4. run the current mixed-box analysis 5. push systems and analysis results into SQLite 6. pull the data back out @@ -135,24 +135,30 @@ After this step, each hash directory should contain the standard build outputs s - `npt.mdp` - `md.mdp` -## 4. Optional: run the checked-in GROMACS workflow +## 4. Optional: run the GROMACS simulation chain -If you want to continue through a full simulation campaign, use the stage-2 Nextflow workflow: +If you want to continue through a full simulation campaign, run the simulation pipeline: ```bash -nextflow run workflows/simulate.nf \ - -c workflows/simulate.config \ - --base_dir tutorial_campaign \ - --config_yaml tutorial_campaign/mixedbox_chain_length_series_20.yaml +mdfactory simulate tutorial_campaign/mixedbox_chain_length_series_20.yaml ``` - -The shipped `simulate.config` contains SLURM settings tuned for a specific cluster. Edit this file to match your HPC environment before running. See [Running on HPC Clusters](/docs/user-guide/running-on-hpc) for details. +For SLURM clusters, generate a config and pass it: + +```bash +mdfactory config slurm +mdfactory simulate \ + tutorial_campaign/mixedbox_chain_length_series_20.yaml \ + --slurm slurm_executor.yaml +``` + + +The `mdfactory config slurm` wizard auto-discovers your cluster's partitions, accounts, and hardware. See [Running on HPC Clusters](/docs/user-guide/running-on-hpc) for details. -This executes the fixed sequence implemented in `workflows/simulate.nf`: +This executes the fixed simulation sequence: -1. minimization +1. minimization (EM) 2. NVT equilibration 3. NPT equilibration 4. production diff --git a/docs/content/docs/user-guide/workflows.mdx b/docs/content/docs/user-guide/workflows.mdx index 4be6dce..98d3a73 100644 --- a/docs/content/docs/user-guide/workflows.mdx +++ b/docs/content/docs/user-guide/workflows.mdx @@ -1,86 +1,167 @@ --- title: Workflows -description: Nextflow scripts that are currently checked into the repository +description: Build and simulate systems at scale with the mdfactory CLI --- -The repository contains Nextflow scripts under `workflows/`. The two-stage flow is built around: +MDFactory provides a two-command workflow for running simulation campaigns, from building systems to executing multi-stage GROMACS simulations. The orchestration layer uses [Parsl](https://parsl-project.org/) to parallelize work across local CPUs or SLURM cluster nodes. -- `workflows/build.nf` — generate system folders and build files from CSV -- `workflows/simulate.nf` — run the GROMACS simulation chain +## Overview -Those scripts match the current `mdfactory prepare-build` and `mdfactory build` commands plus the GROMACS file layout produced by the builders. +The workflow has two stages: -## Full pipeline +1. **Build** — generate simulation-ready directories from YAML or CSV input +2. **Simulate** — run the GROMACS pipeline (EM → NVT → NPT → Production) for each built system -The easiest way to run both stages is the provided shell script: +Each stage can run locally or on a SLURM cluster. With `--slurm`, Parsl submits many systems concurrently — multiple builds run in parallel, and multiple GROMACS simulations execute simultaneously across cluster nodes. + +## Prerequisites ```bash -cd workflows/ -bash run_full_pipeline.sh +pip install "mdfactory[parsl]" # SLURM orchestration requires the parsl extra ``` -Edit the variables at the top of `run_full_pipeline.sh` to set your CSV input and output directory. The script runs the build stage followed by the simulation stage. - -## Stage 1: generate build folders +## Stage 1: build systems -`build.nf` expects a CSV input file and an output directory: +Build from CSV (batch) or summary YAML: ```bash -nextflow run workflows/build.nf \ - --csv_file systems.csv \ - --output_dir results +# Sequential (local, no Parsl) +mdfactory build systems.csv --output results + +# Parallel on SLURM +mdfactory build systems.csv --output results --slurm slurm_executor.yaml ``` What it does: -1. Runs `mdfactory prepare-build`. -2. Reads the summary YAML written into the output directory. -3. Builds each generated hash directory with `mdfactory build`. +1. Runs `mdfactory prepare-build` internally (CSV → per-hash YAML + summary YAML). +2. Builds each hash directory using the appropriate builder (mixedbox, bilayer, LNP). +3. With `--slurm`, builds run as parallel Parsl `python_app` tasks on allocated nodes. The summary YAML filename is derived from the CSV stem. For example, `systems.csv` produces `results/systems.yaml`. ## Stage 2: run the GROMACS chain -`simulate.nf` consumes the stage-1 output directory plus the summary YAML: +```bash +# Local execution (all stages, all systems) +mdfactory simulate results/ + +# SLURM execution with config file +mdfactory simulate results/ --slurm slurm_executor.yaml + +# Interactive SLURM setup +mdfactory simulate results/ --slurm tui +``` + +`simulate` executes this sequence for each simulation directory: + +1. **EM** — energy minimization using `em.mdp` +2. **NVT** — NVT equilibration using `nvt.mdp` +3. **NPT** — NPT equilibration using `npt.mdp` +4. **Production** — production run using `md.mdp` + +### Stage selection + +Run only a subset of stages: ```bash -nextflow run workflows/simulate.nf \ - -c workflows/simulate.config \ - --base_dir results \ - --config_yaml results/systems.yaml +mdfactory simulate results/ --stages EM NVT NPT # skip production +mdfactory simulate results/ --stages Production # production only (requires prior stages) ``` - -The shipped `simulate.config` contains SLURM settings (queue name, module loads, GPU/CPU/memory requests) tuned for a specific cluster. Edit this file to match your HPC environment before running. See [Running on HPC Clusters](/docs/user-guide/running-on-hpc) for guidance. - +### Checkpoint and restart + +The `--checkpoint` flag controls how existing progress is handled: -`simulate.nf` executes this fixed sequence for each hash listed in the summary YAML: +| Mode | Behavior | +|------|----------| +| `auto` (default) | Validates output files and trajectory frame counts. Resumes from the last valid checkpoint. | +| `skip` | Skips stages where the output file exists (no validation). | +| `force` | Re-runs all stages from scratch, ignoring existing outputs. | -1. Minimization using `em.mdp` -2. NVT equilibration using `nvt.mdp` -3. NPT equilibration using `npt.mdp` -4. Production using `md.mdp` +```bash +mdfactory simulate results/ --slurm gpu.yaml --checkpoint auto +``` -Outputs are published back into each simulation directory under `--base_dir`. +### Hash filtering + +Run only specific simulations by hash prefix: + +```bash +mdfactory simulate results/ --hash abc123 def456 +``` + +### Dry-run + +Preview the execution plan without submitting anything: + +```bash +mdfactory simulate results/ --slurm gpu.yaml --dry-run +``` + +## SLURM executor configuration + +Generate a config interactively (auto-discovers cluster resources): + +```bash +mdfactory config slurm +``` + +Or write a YAML file manually. See [`examples/slurm_executor.yaml`](https://github.com/emdgroup/mdfactory/blob/main/examples/slurm_executor.yaml) for an annotated reference. + +Key fields: + +```yaml +provider: slurm +account: my-account +partition: gpu +walltime: "8h" +nodes: 1 +cpus_per_node: 16 +gres: "gpu:1" +max_workers_per_node: 1 +max_blocks: 4 +gmx_binary: auto +environment: + modules: + - gromacs/2024 +``` + +### Per-stage resource overrides + +Override `cpus_per_node`, `gres`, or `gmx_binary` for individual stages: + +```yaml +stage_overrides: + EM: + gres: null # CPU-only minimization + cpus_per_node: 4 + Production: + gres: "gpu:2" + cpus_per_node: 32 +``` ## Expected directory shape - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/examples/proteinbox/lysozyme_charmm36m.yaml b/examples/proteinbox/lysozyme_charmm36m.yaml new file mode 100644 index 0000000..a7e0201 --- /dev/null +++ b/examples/proteinbox/lysozyme_charmm36m.yaml @@ -0,0 +1,26 @@ +simulation_type: proteinbox +engine: gromacs +parametrization: pdb2gmx +parametrization_config: + type: pdb2gmx + forcefield: charmm36m + water_model: tip3p + ignore_hydrogens: true + +system: + protein: + resname: LYZ + count: 1 + pdb_path: ./1aki.pdb + chains: [A] + disulfide_bonds: + - [CYS6, CYS127] + - [CYS30, CYS115] + - [CYS64, CYS80] + - [CYS76, CYS94] + protonation_states: + HIS15: HIE + padding: 12.0 + ionization: + neutralize: true + concentration: 0.15 diff --git a/examples/proteinbox/proteins.csv b/examples/proteinbox/proteins.csv new file mode 100644 index 0000000..868cc44 --- /dev/null +++ b/examples/proteinbox/proteins.csv @@ -0,0 +1,3 @@ +simulation_type,engine,parametrization,parametrization_config.type,parametrization_config.forcefield,parametrization_config.water_model,parametrization_config.ignore_hydrogens,system.protein.resname,system.protein.pdb_path,system.protein.chains,system.protein.disulfide_bonds,system.protein.protonation_states.HIS15,system.padding,system.ionization.neutralize,system.ionization.concentration,system.relax_steps +proteinbox,gromacs,pdb2gmx,pdb2gmx,charmm36m,tip3p,True,LYZ,1aki.pdb,A,CYS6-CYS127;CYS30-CYS115;CYS64-CYS80;CYS76-CYS94,HIE,12.0,True,0.15,100 +proteinbox,gromacs,pdb2gmx,pdb2gmx,charmm36m,tip3p,True,INS,1zni.pdb,A;B;C;D,,,12.0,True,0.15,100 diff --git a/examples/slurm_executor.yaml b/examples/slurm_executor.yaml new file mode 100644 index 0000000..3477e86 --- /dev/null +++ b/examples/slurm_executor.yaml @@ -0,0 +1,91 @@ +# SLURM executor configuration for mdfactory build/simulate. +# +# Generate interactively with: mdfactory config slurm +# Use with: mdfactory simulate output_dir/ --slurm slurm_executor.yaml +# mdfactory build systems.csv --slurm slurm_executor.yaml + +# --- Provider ---------------------------------------------------------------- +# "local" runs on the current machine; "slurm" submits to a SLURM cluster. +provider: slurm + +# --- SLURM account and partition --------------------------------------------- +# Required. Use `mdfactory config cluster` to discover available options. +account: my-account +partition: gpu + +# Optional quality-of-service and node constraint. +# qos: normal +# constraint: a100 + +# --- Resource requests ------------------------------------------------------- +# Wall-clock time per SLURM allocation (HH:MM:SS or shorthand like "8h"). +walltime: "8h" + +# Nodes per SLURM job. +nodes: 1 + +# CPUs per node. Sizes the Parsl worker pool and sets thread counts for mdrun. +cpus_per_node: 16 + +# GPU generic resource string (passed as --gres to sbatch). +gres: "gpu:1" + +# Memory per node (passed as --mem to sbatch). Omit to use partition default. +# mem: "32G" + +# --- Execution environment --------------------------------------------------- +# Structured environment setup for compute workers. The wizard auto-detects +# pixi/conda/venv and GROMACS modules. +environment: + # Module loads (each becomes "module load ") + modules: + - gromacs/2024 + + # Python environment activation (first match wins: pixi > conda > venv). + # Auto-detected by the wizard; set explicitly for manual configs. + pixi_manifest: /path/to/project # generates pixi shell-hook command + # conda_env: gromacs_env # alternative: conda activate + # venv_path: /home/user/.venvs/md # alternative: source activate + + # Extra shell commands appended after modules + activation. + # extra_init: "export OMP_NUM_THREADS=4" + +# --- Parsl tuning ------------------------------------------------------------ +# Max parallel tasks per compute node. +max_workers_per_node: 1 + +# Max SLURM allocations Parsl may hold at once. +max_blocks: 4 + +# GPU device IDs exposed to each worker via CUDA_VISIBLE_DEVICES. +# Set to the number of GPUs per node, or list specific device IDs. +available_accelerators: 1 + +# Times Parsl retries a failed task before raising an error. +retries: 3 + +# --- GROMACS settings -------------------------------------------------------- +# "auto" detects gmx vs gmx_mpi at runtime. Override to force one. +gmx_binary: auto + +# --- Per-stage overrides (optional) ------------------------------------------ +# Override cpus_per_node, gres, or gmx_binary for individual stages. +# Stage names: EM, NVT, NPT, Production. +# stage_overrides: +# EM: +# cpus_per_node: 4 +# gres: null # run minimisation on CPU only +# Production: +# cpus_per_node: 32 +# gres: "gpu:2" + +# --- Advanced (rarely needed) ------------------------------------------------ +# Directory where Parsl writes runinfo logs and monitoring DB. +# run_dir: ~/.parsl/mdfactory + +# Extra raw #SBATCH lines appended to the job script. +# scheduler_options: "#SBATCH --exclusive" + +# Extra flags passed to srun when launching workers. +# launch_options: "--mpi=pmix" + diff --git a/mdfactory/analysis/submit.py b/mdfactory/analysis/submit.py index b14caf9..55c1648 100644 --- a/mdfactory/analysis/submit.py +++ b/mdfactory/analysis/submit.py @@ -6,7 +6,6 @@ import os import shutil -from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import Iterable @@ -16,42 +15,15 @@ from mdfactory.analysis.artifacts import ARTIFACT_REGISTRY from mdfactory.analysis.simulation import ANALYSIS_REGISTRY, Simulation - -@dataclass(frozen=True) -class SlurmConfig: - """Configuration for submitit/SLURM execution.""" - - account: str - partition: str = "cpu" - time: str = "2h" - cpus_per_task: int = 4 - mem_gb: int = 8 - qos: str | None = None - constraint: str | None = None - job_name_prefix: str = "mdfactory-analysis" - - -def normalize_slurm_time(value: str) -> str: - """Normalize SLURM time strings to accepted formats.""" - raw = value.strip() - if ":" in raw: - return raw - lowered = raw.lower() - if lowered.endswith("d"): - days = int(lowered[:-1]) - return f"{days}-00:00:00" - if lowered.endswith("h"): - hours = int(lowered[:-1]) - return f"{hours:02d}:00:00" - if lowered.endswith("m"): - minutes = int(lowered[:-1]) - hours, minutes = divmod(minutes, 60) - return f"{hours:02d}:{minutes:02d}:00" - if lowered.isdigit(): - minutes = int(lowered) - hours, minutes = divmod(minutes, 60) - return f"{hours:02d}:{minutes:02d}:00" - return raw +# SlurmConfig and normalize_slurm_time live in the performance package so that +# every SLURM-facing backend (submitit, Parsl, Nextflow) can share them. +# Re-exported here for backward compatibility: +# from mdfactory.analysis.submit import SlurmConfig # still works +# from mdfactory.analysis.submit import normalize_slurm_time # still works +from mdfactory.performance.slurm_config import ( # noqa: F401 (re-export) + SlurmConfig, + normalize_slurm_time, +) def resolve_simulation_paths( diff --git a/mdfactory/build.py b/mdfactory/build.py index 6f728b3..b0d0a67 100644 --- a/mdfactory/build.py +++ b/mdfactory/build.py @@ -1,9 +1,10 @@ # ABOUTME: Core build pipeline for constructing MD simulation systems -# ABOUTME: Dispatches to mixedbox, bilayer, and LNP build routines +# ABOUTME: Dispatches to mixedbox, bilayer, LNP, and proteinbox build routines """Core build pipeline for constructing MD simulation systems.""" import os from functools import partial +from pathlib import Path import MDAnalysis as mda import numpy as np @@ -682,3 +683,167 @@ def ionize_solvated_system(ion_config, u_solvated, total_charge): na_spec = SingleMoleculeSpecies(count=num_na, smiles="[Na+]", resname="NA") cl_spec = SingleMoleculeSpecies(count=num_cl, smiles="[Cl-]", resname="CL") return u_ionized, [na_spec, cl_spec] + + +def _center_in_cubic_box(u: mda.Universe, padding: float) -> float: + """Center atoms in a cubic box leaving at least ``padding`` on every face. + + The box is centered on the atoms' bounding-box center (not the center of + geometry), so an asymmetric molecule keeps the requested clearance to each + face. Returns the cubic box edge length in Angstroms. + """ + bbox_min = u.atoms.positions.min(axis=0) + bbox_max = u.atoms.positions.max(axis=0) + extent = bbox_max - bbox_min + box_size = float(extent.max() + 2 * padding) + u.dimensions = [box_size, box_size, box_size, 90, 90, 90] + box_center = np.array([box_size / 2, box_size / 2, box_size / 2]) + bbox_center = (bbox_min + bbox_max) / 2 + u.atoms.translate(box_center - bbox_center) + return box_size + + +@validate_call +def build_proteinbox(inp: BuildInput): + """Build a protein-in-waterbox system: clean, parametrize, solvate, ionize, relax.""" + import shutil + + from loguru import logger + + from .models.composition import ProteinBoxComposition + from .models.parametrization import Pdb2gmxConfig + from .setup.protein import ( + bundle_forcefield_into_topology, + check_gmx_available, + clean_pdb, + run_pdb2gmx, + update_topology_molecules, + validate_with_grompp, + ) + + if not isinstance(inp.system, ProteinBoxComposition): + raise TypeError("System must specify a ProteinBoxComposition.") + + if not isinstance(inp.parametrization_config, Pdb2gmxConfig): + raise TypeError("Parametrization config must be Pdb2gmxConfig for proteinbox.") + + # 1. Verify GROMACS is available + gmx_path = check_gmx_available() + logger.info(f"Using GROMACS: {gmx_path}") + + protein = inp.system.protein + config = inp.parametrization_config + + # 2. Clean PDB + pdb_input = protein.pdb_path + if not pdb_input.is_absolute(): + pdb_input = pdb_input.resolve() + + cleaned_pdb = Path("cleaned.pdb") + clean_pdb(pdb_input, cleaned_pdb) + + # 3. Run pdb2gmx + pdb2gmx_dir = Path("pdb2gmx_output") + params = run_pdb2gmx( + pdb_path=cleaned_pdb, + config=config, + disulfide_bonds=protein.disulfide_bonds, + protonation_states=protein.protonation_states, + output_dir=pdb2gmx_dir, + chains=protein.chains, + ) + logger.info(f"pdb2gmx output: {params.structure_file}") + + # 4. Center protein in cubic box + u = mda.Universe(str(params.structure_file)) + box_size = _center_in_cubic_box(u, inp.system.padding) + logger.info(f"Protein centered in cubic box: {box_size:.1f} A") + + # 5. Solvate (reuse existing solvation) + u_solvated = solvate(u, prune_in_z=False) + u_solvated.dimensions = u.dimensions + n_water = len(u_solvated.select_atoms("water").residues) + logger.info(f"Solvation added {n_water} water molecules.") + + if n_water == 0: + raise ValueError("Solvation did not add any water molecules.") + + # 6. Ionize (reuse existing ionization) + protein_charge = params.total_charge + u_ionized, ion_species = ionize_solvated_system( + inp.system.ionization, u_solvated, protein_charge + ) + num_na = ion_species[0].count if len(ion_species) > 0 else 0 + num_cl = ion_species[1].count if len(ion_species) > 1 else 0 + n_water_final = len(u_ionized.select_atoms("water").residues) + + # 7. Update topology [ molecules ] section + topology_dest = Path("topology.top") + shutil.copy(params.topology_file, topology_dest) + # topol.top references its per-chain and posre .itp files by bare name, so they + # must sit alongside it. + for include_file in params.topology_include_files: + shutil.copy(include_file, Path(include_file.name)) + ff_dir_name = bundle_forcefield_into_topology(topology_dest) + update_topology_molecules(topology_dest, n_water_final, num_na, num_cl) + + # 8. Write coordinates for OpenMM relaxation + pre_relax_structure = Path("system_pre_relax.pdb").resolve() + u_ionized.atoms.write(str(pre_relax_structure)) + + # 9. OpenMM relaxation with protein position restraints + protein_atoms = u_ionized.select_atoms("protein and not name H*") + protein_indices = protein_atoms.indices.tolist() + topology_dest = topology_dest.resolve() + include_files = [f.resolve() for f in params.topology_include_files] + + with working_directory("relaxation", create=True, cleanup=True) as wd: + shutil.copy(pre_relax_structure, wd / "system.pdb") + shutil.copy(topology_dest, wd / "topology.top") + for include_file in include_files: + shutil.copy(include_file, wd / include_file.name) + + # OpenMM's GromacsTopFile resolves #includes relative to the topology's + # directory, so bundle the force field next to the copied topology. + if ff_dir_name is not None: + shutil.copytree( + topology_dest.parent / ff_dir_name, wd / ff_dir_name, dirs_exist_ok=True + ) + + from .simulation.openmm_utils import relax_with_protein_restraints + + u_relaxed = relax_with_protein_restraints( + u_ionized, + wd / "system.pdb", + wd / "topology.top", + protein_indices=protein_indices, + steps=inp.system.relax_steps, + ) + + logger.info("OpenMM relaxation complete.") + + # 10. Write final output + u_relaxed.atoms.write("system.pdb") + logger.info("Final system written to system.pdb") + + # 11. Validate with grompp (if em.mdp is available) + manager = RunScheduleManager() + manager.copy_run_files_with_check( + engine=inp.engine, system_type="proteinbox", target_folder=os.getcwd(), force_copy=True + ) + logger.info("Run schedule files copied.") + + em_mdp = Path("em.mdp") + if em_mdp.is_file(): + validate_with_grompp(topology_dest, Path("system.pdb"), em_mdp, Path.cwd()) + + # Remove the pdb2gmx-specific loose intermediates, which have no counterpart in + # the small-molecule builds. solvated.pdb and the relaxation/ working dir are + # left in place, matching how the bilayer build leaves solvated.pdb and its + # bilayer_squeeze/ dir. Only runs on success; a failure earlier keeps the + # intermediates for debugging. + Path(cleaned_pdb).unlink(missing_ok=True) + pre_relax_structure.unlink(missing_ok=True) + shutil.rmtree(pdb2gmx_dir, ignore_errors=True) + + logger.info("Proteinbox build complete.") diff --git a/mdfactory/cli.py b/mdfactory/cli.py index 478015e..3c31fc4 100644 --- a/mdfactory/cli.py +++ b/mdfactory/cli.py @@ -8,7 +8,7 @@ import sys from datetime import datetime from pathlib import Path -from typing import Annotated, Literal +from typing import TYPE_CHECKING, Annotated, Literal import pandas as pd import questionary @@ -33,7 +33,10 @@ from .utils.data_manager import check_run_exists from .utils.push import push_systems from .utils.utilities import working_directory -from .workflows import run_build_from_file +from .workflows import run_build_from_dict, run_build_from_file + +if TYPE_CHECKING: + from .orchestration import ExecutorConfig app = App(name="MDFactory", version=__version__) @@ -82,52 +85,612 @@ def prepare_build(input: Path, output: Path = Path(".")): logger.info("Created DataFrame for systems:\n{}", df) - dirs = [] - for _, row in df.iterrows(): - out = output / Path(row.hash) - out.mkdir(parents=True, exist_ok=False) - yml_path = out / f"{row.hash}.yaml" - dirs.append(str(out.resolve())) - with open(yml_path, "w") as fb: - yaml.safe_dump(row.model.model_dump(), fb) - - summary = { - "n_systems": df.shape[0], - "input": str(input), - "output": str(output), - "hash": df.hash.values.tolist(), - "simulation_type": [x.simulation_type for x in df.model.values], - "system_directory": dirs, - "date": datetime.now(), - } - with open(yml_build_path, "w") as fb: - yaml.safe_dump(summary, fb) + _prepare_system_directories(models, input, output, exist_ok=False) def df_models_from_input_csv(input): df = pd.read_csv(input) # remove 'unnamed' CSV columns df = df.loc[:, ~df.columns.str.contains("^Unnamed")] + # Resolve relative protein PDB paths against the CSV's own directory so the + # emitted per-system YAMLs carry absolute paths regardless of where the build + # later runs. + pdb_col = "system.protein.pdb_path" + if pdb_col in df.columns: + df[pdb_col] = df[pdb_col].apply( + lambda p: p + if pd.isna(p) or Path(p).is_absolute() + else str((input.parent / p).resolve()) + ) models, errors = df_to_build_input_models(df) return df, models, errors +def _prepare_system_directories( + models: list, + input_path: Path, + output: Path, + *, + exist_ok: bool = True, +) -> tuple[list[str], Path]: + """Create per-hash build directories, write system YAMLs, and generate summary. + + Parameters + ---------- + models : list + List of BuildInput models. + input_path : Path + Original input file path (used for summary YAML naming). + output : Path + Base output directory. + exist_ok : bool + Whether to allow existing directories. + + Returns + ------- + tuple[list[str], Path] + List of system directory paths, and path to the summary YAML. + + """ + dirs = [] + for model in models: + build_dir = output / model.hash + build_dir.mkdir(parents=True, exist_ok=exist_ok) + yml_path = build_dir / f"{model.hash}.yaml" + if not yml_path.exists(): + with open(yml_path, "w") as f: + # mode="json" so Path and tuple fields (e.g. proteinbox pdb_path and + # disulfide_bonds) serialize to plain str/list that yaml.safe_dump can write. + yaml.safe_dump(model.model_dump(mode="json"), f) + dirs.append(str(build_dir.resolve())) + + summary_path = output / f"{input_path.stem}.yaml" + summary = { + "n_systems": len(models), + "input": str(input_path), + "output": str(output), + "hash": [m.hash for m in models], + "simulation_type": [m.simulation_type for m in models], + "system_directory": dirs, + "date": datetime.now(), + } + with open(summary_path, "w") as f: + yaml.safe_dump(summary, f) + return dirs, summary_path + + +def _resolve_slurm_flag( + slurm: str | None, + *, + stages: tuple[str, ...] | None = None, +) -> "ExecutorConfig | Path | None": + """Resolve the ``--slurm`` CLI flag to an executor config or YAML path. + + Parameters + ---------- + slurm : str or None + Raw value from the ``--slurm`` flag. ``"tui"`` launches the + interactive wizard (which returns a config object directly). + Any other non-None value is treated as a file path. + stages : tuple[str, ...] or None, optional + Simulation stages to configure overrides for. Passed through to + the TUI wizard. Use an empty tuple for workflows without + simulation stages (e.g. ``build``). ``None`` (default) uses the + full simulation pipeline. + + Returns + ------- + ExecutorConfig, Path, or None + An ExecutorConfig when using TUI mode, a Path to executor config + YAML, or None if no SLURM config requested. + + """ + if slurm is None: + return None + + if slurm.strip().lower() == "tui": + from mdfactory.orchestration.tui import UserCancelledError, configure_slurm_interactive + + try: + return configure_slurm_interactive(stages=stages) + except UserCancelledError: + logger.info("SLURM configuration cancelled.") + sys.exit(0) + + path = Path(slurm) + if not path.exists(): + logger.error(f"SLURM config file not found: {path}") + sys.exit(1) + return path + + +def _load_executor_config(config: "ExecutorConfig | Path | None") -> "ExecutorConfig": + """Load executor config from path or return directly if already loaded. + + Parameters + ---------- + config : ExecutorConfig, Path, or None + An already-loaded config object, a path to a YAML file, or None + for default local execution. + + Returns + ------- + ExecutorConfig + The resolved executor configuration. + + """ + if config is None: + from mdfactory.orchestration import EnvironmentConfig, ExecutorConfig + + global_env = EnvironmentConfig.load_global() + if global_env is not None: + return ExecutorConfig(environment=global_env) + return ExecutorConfig() + if isinstance(config, Path): + from mdfactory.orchestration import ExecutorConfig + + return ExecutorConfig.from_yaml(config) + return config # already an ExecutorConfig instance + + @app.command(name="build", group="Build") -def build_system(input: Path, output: Path = Path(".")): - """Build MD system from YAML input file. +def build_system( + input: Path, + output: Path = Path("."), + slurm: Annotated[ + str | None, + Parameter( + help=("SLURM executor config: path to YAML file, or 'tui' for interactive setup.") + ), + ] = None, + dry_run: Annotated[ + bool, Parameter(help="Print what would be built without executing.") + ] = False, +): + """Build MD system(s) from YAML, CSV, or summary input. + + Supports three input modes: + + - Single YAML: builds one system locally (original behavior) + - CSV file: builds systems from each row (parallel with --slurm, sequential without) + - Summary YAML: dispatches builds for previously prepared systems Parameters ---------- input : Path - Path to the YAML file specifying the `BuildInput` + Path to input file (YAML for single build, CSV for batch, summary YAML for prepared). output : Path, optional - Output directory, by default Path(".") + Output directory, by default Path("."). + slurm : str, optional + Path to SLURM executor config YAML, or ``"tui"`` to launch the + interactive configuration wizard. + dry_run : bool, optional + Print what would be built without executing. """ input = input.resolve() - logger.info(f"Building system from YAML file {input} and output directory: {output}.") - with working_directory(output, create=True): - run_build_from_file(input) + output = output.resolve() + output.mkdir(parents=True, exist_ok=True) + + # Resolve --slurm flag: "tui" launches the interactive wizard, + # anything else is treated as a path to a YAML config file. + # Build has no simulation stages, so skip the stage-override prompts. + config: "ExecutorConfig | Path | None" = _resolve_slurm_flag(slurm, stages=()) + + suffix = input.suffix.lower() + + if suffix == ".csv": + _build_from_csv(input, output, config=config, dry_run=dry_run) + elif suffix in (".yaml", ".yml"): + _build_from_yaml(input, output, config=config, dry_run=dry_run) + else: + logger.error(f"Unsupported input file format: {suffix}. Use .csv, .yaml, or .yml.") + sys.exit(1) + + +def _build_from_csv( + input: Path, + output: Path, + *, + config: "ExecutorConfig | Path | None" = None, + dry_run: bool = False, +): + """Handle CSV input for the build command.""" + df, models, errors = df_models_from_input_csv(input) + if errors: + logger.error("Errors building models from CSV:") + for k, v in errors.items(): + logger.error(f" Row {k}: {v}") + sys.exit(1) + + if dry_run: + from mdfactory.orchestration import build_systems + + executor_config = _load_executor_config(config) + build_systems(models, executor_config, output_dir=output, dry_run=True) + return + + # Create per-hash directories, write per-system YAMLs, and generate summary + dirs, summary_path = _prepare_system_directories(models, input, output, exist_ok=True) + logger.info(f"Summary YAML written to {summary_path}") + + if config is not None: + # Parallel builds via Parsl + from mdfactory.orchestration import build_systems + + executor_config = _load_executor_config(config) + results = build_systems(models, executor_config, output_dir=output) + _report_build_results(results, output_dir=output) + else: + # Sequential local builds + logger.info(f"Building {len(models)} system(s) sequentially.") + for model in models: + build_dir = output / model.hash + logger.info(f"Building {model.hash} ({model.simulation_type})...") + with working_directory(build_dir, create=True): + run_build_from_dict(model) + + +def _build_from_yaml( + input: Path, + output: Path, + *, + config: "ExecutorConfig | Path | None" = None, + dry_run: bool = False, +): + """Handle YAML input for the build command.""" + with open(input) as f: + data = yaml.safe_load(f) + + if not isinstance(data, dict): + logger.error(f"Invalid YAML input file (empty or not a mapping): {input}") + sys.exit(1) + + if "system_directory" in data: + # Summary YAML -- dispatch builds for prepared systems + _build_from_summary_yaml(data, output, config=config, dry_run=dry_run) + else: + # Single build YAML + from mdfactory.models.input import BuildInput + + model = BuildInput(**data) + + if config is not None or dry_run: + from mdfactory.orchestration import build_systems + + executor_config = _load_executor_config(config) + results = build_systems([model], executor_config, output_dir=output, dry_run=dry_run) + if not dry_run: + _report_build_results(results, output_dir=output) + else: + build_dir = output / model.hash + logger.info(f"Building {model.hash} ({model.simulation_type}) -> {build_dir}") + with working_directory(build_dir, create=True): + run_build_from_file(input) + logger.info(f"Output: {output}") + logger.info("Next step — run simulations:") + logger.info(f" mdfactory simulate {output}") + logger.info( + f" # with SLURM: mdfactory simulate {output} --slurm " + ) + + +def _build_from_summary_yaml( + data: dict, + output: Path, + *, + config: "ExecutorConfig | Path | None" = None, + dry_run: bool = False, +): + """Handle summary YAML (from prepare-build) for the build command.""" + dirs = data.get("system_directory", []) + hashes = data.get("hash", []) + + if not dirs: + logger.error("Summary YAML contains no system_directory entries.") + sys.exit(1) + + if len(dirs) != len(hashes): + logger.error( + f"Summary YAML is malformed: system_directory has {len(dirs)} entries " + f"but hash has {len(hashes)}." + ) + sys.exit(1) + + from mdfactory.models.input import BuildInput + + models = [] + for sim_dir, sim_hash in zip(dirs, hashes): + yml_path = Path(sim_dir) / f"{sim_hash}.yaml" + if not yml_path.exists(): + logger.error(f"Build YAML not found: {yml_path}") + sys.exit(1) + with open(yml_path) as f: + model_data = yaml.safe_load(f) + models.append(BuildInput(**model_data)) + + if config is not None or dry_run: + from mdfactory.orchestration import build_systems + + executor_config = _load_executor_config(config) + results = build_systems(models, executor_config, output_dir=output, dry_run=dry_run) + if not dry_run: + _report_build_results(results, output_dir=output) + else: + # Sequential local builds + logger.info(f"Building {len(models)} prepared system(s) sequentially.") + for model, sim_dir in zip(models, dirs): + logger.info(f"Building {model.hash} ({model.simulation_type})...") + with working_directory(Path(sim_dir), create=True): + run_build_from_dict(model) + logger.info(f"Output: {output}") + logger.info("Next step — run simulations:") + logger.info(f" mdfactory simulate {output}") + logger.info(f" # with SLURM: mdfactory simulate {output} --slurm ") + + +def _report_build_results(results: list[dict], output_dir: Path | None = None): + """Report build results to the user. + + Parameters + ---------- + results : list[dict] + Build result dicts from build_systems(). + output_dir : Path, optional + Output directory used for the build. When provided, a "next steps" + hint pointing to ``mdfactory simulate`` is printed so users know + exactly what to run next. + + """ + succeeded = sum(1 for r in results if r.get("status") == "success") + failed = sum(1 for r in results if r.get("status") == "failed") + logger.info(f"Build complete: {succeeded} succeeded, {failed} failed.") + for r in results: + if r.get("status") == "failed": + logger.error(f" {r.get('hash', 'unknown')}: {r.get('error', 'unknown error')}") + if succeeded and output_dir is not None: + logger.info(f"Output: {output_dir}") + logger.info("Next step — run simulations:") + logger.info(f" mdfactory simulate {output_dir}") + logger.info( + f" # with SLURM: mdfactory simulate {output_dir} --slurm " + ) + + +def _filter_sim_paths_by_hash_prefix(sim_paths: list[Path], prefixes: list[str]) -> list[Path]: + """Filter simulation paths by hash prefix without requiring completed trajectories. + + Unlike :func:`~mdfactory.analysis.submit.filter_paths_by_hash`, this + helper works purely on directory names and therefore works on freshly + built directories that have not yet been simulated. + + Hash matching is case-insensitive so short prefixes typed in lower-case + still resolve correctly. + + Parameters + ---------- + sim_paths : list[Path] + Candidate simulation directories (already validated by + :func:`_discover_sim_dirs`). + prefixes : list[str] + Hash strings or prefix fragments to match against directory names. + + Returns + ------- + list[Path] + Subset of *sim_paths* whose directory names start with at least one + requested prefix. + + Raises + ------ + ValueError + If none of the requested prefixes match any discovered directory. + + """ + upper_prefixes = [p.upper() for p in prefixes] + matched = [ + p for p in sim_paths if any(p.name.upper().startswith(pref) for pref in upper_prefixes) + ] + if not matched: + available = sorted(p.name for p in sim_paths) + raise ValueError(f"No simulation directories match {prefixes}.\nAvailable: {available}") + return sorted(matched) + + +def _discover_sim_dirs(root: Path) -> list[Path]: + """Find simulation-ready directories under *root*. + + A directory is considered simulation-ready when it contains ``system.pdb``. + Unlike :func:`~mdfactory.analysis.utils.discover_simulations` (which is + designed for post-run analysis), this helper does **not** require a + ``BuildInput`` YAML or a completed trajectory — it works on freshly + prepared directories. + + Rules + ----- + - If *root* itself contains ``system.pdb`` it is returned as a single-item + list (direct simulation directory). + - Otherwise the immediate children of *root* that contain ``system.pdb`` + are returned (build-output layout where each hash subdirectory is one + simulation). + + Raises + ------ + ValueError + If no simulation-ready directories are found under *root*. + + """ + if (root / "system.pdb").exists(): + return [root] + + sims = sorted(d for d in root.iterdir() if d.is_dir() and (d / "system.pdb").exists()) + if not sims: + raise ValueError( + f"No simulation directories found under {root}.\n" + "Each simulation directory must contain system.pdb. " + "Run 'mdfactory build' first, or point directly to a prepared " + "simulation directory." + ) + return sims + + +def _resolve_sim_paths_for_simulate(source: Path) -> list[Path]: + """Resolve *source* to a list of simulation-ready directories. + + Handles both directory trees (delegating to :func:`_discover_sim_dirs`) + and YAML summary files. + + Parameters + ---------- + source : Path + Resolved absolute path to a build output directory or summary YAML. + + Returns + ------- + list[Path] + Simulation directories ready for submission. + + Raises + ------ + ValueError + If *source* is neither a directory nor a recognised YAML file, or if + no simulation directories are found. + + """ + if source.is_dir(): + return _discover_sim_dirs(source) + if source.suffix in (".yaml", ".yml"): + with open(source) as f: + summary = yaml.safe_load(f) + return [Path(p) for p in summary.get("system_directory", [])] + raise ValueError(f"Invalid source: {source}. Provide a directory or YAML.") + + +@app.command(name="simulate", group="Simulation") +def simulate_systems( + source: Annotated[Path, Parameter(help="Simulation directory or summary YAML")], + slurm: Annotated[ + str | None, + Parameter(help="SLURM config YAML or 'tui' for interactive setup"), + ] = None, + stages: Annotated[ + list[str] | None, + Parameter(help="Stages to run (EM, NVT, NPT, Production)", consume_multiple=True), + ] = None, + checkpoint: Annotated[ + str, + Parameter(help="Checkpoint mode: auto (default), skip, force"), + ] = "auto", + hash: Annotated[ + list[str] | None, + Parameter(help="Filter by hash prefix", consume_multiple=True), + ] = None, + dry_run: Annotated[ + bool, + Parameter(help="Preview plan without executing"), + ] = False, + clean: Annotated[ + bool, + Parameter(help="Remove simulation outputs before running"), + ] = False, + max_rescue: Annotated[ + int, + Parameter( + help="Max rescue tiers for physics failures (0 to disable)", + ), + ] = 3, +): + """Run GROMACS MD simulations via Parsl. + + Examples + -------- + Local execution (all stages):: + + mdfactory simulate output_dir/ + + SLURM with interactive config:: + + mdfactory simulate output_dir/ --slurm tui + + Resume from checkpoint:: + + mdfactory simulate output_dir/ --slurm gpu.yaml --checkpoint auto + + Equilibration only:: + + mdfactory simulate output_dir/ --stages EM NVT NPT + + Filter specific simulations:: + + mdfactory simulate output_dir/ --hash abc123 def456 + + Dry-run preview:: + + mdfactory simulate output_dir/ --slurm gpu.yaml --dry-run + + Clean before running:: + + mdfactory simulate output_dir/ --clean --stages Production + + """ + source = source.resolve() + + try: + sim_paths = _resolve_sim_paths_for_simulate(source) + except ValueError as exc: + logger.error(str(exc)) + sys.exit(1) + + if hash: + try: + sim_paths = _filter_sim_paths_by_hash_prefix(sim_paths, list(hash)) + except ValueError as exc: + logger.error(str(exc)) + sys.exit(1) + + if not sim_paths: + logger.error("No simulations found.") + sys.exit(1) + + logger.info(f"Found {len(sim_paths)} simulation(s)") + + # Pass the user's --stages selection to the TUI so it only shows + # relevant stage-override prompts. None = full pipeline. + tui_stages = tuple(stages) if stages else None + config = _resolve_slurm_flag(slurm, stages=tui_stages) + executor_config = _load_executor_config(config) + + from mdfactory.orchestration import run_simulations + + try: + results = run_simulations( + sim_paths, + executor_config, + stages=stages, + checkpoint_mode=checkpoint, + dry_run=dry_run, + clean=clean, + max_rescue=max_rescue, + ) + except ValueError as exc: + logger.error(str(exc)) + sys.exit(1) + + if not dry_run: + _report_simulation_results(results) + + +def _report_simulation_results(results: list[dict]): + """Report simulation results to user.""" + succeeded = sum(1 for r in results if r.get("status") == "success") + failed = sum(1 for r in results if r.get("status") == "failed") + skipped = sum(1 for r in results if r.get("status") == "skipped") + + logger.info(f"Simulation complete: {succeeded} succeeded, {failed} failed, {skipped} skipped") + + for r in results: + if r.get("status") == "failed": + logger.error(f" {r.get('hash', 'unknown')}: {r.get('error', 'unknown error')}") @app.command(name="clean") @@ -1142,6 +1705,89 @@ def _resolve_sim_paths( return sim_paths +def _resolve_analysis_slurm_config( + account: "str | None", + partition: "str | None", + time: str, + cpus: int, + mem_gb: int, + qos: "str | None", + constraint: "str | None", + job_name_prefix: str, +) -> "SlurmConfig": + """Resolve a :class:`~mdfactory.analysis.submit.SlurmConfig` for analysis/artifact jobs. + + Tries autodiscovery when *account* is ``None``; falls back to an explicit + config when *account* is given. Used by :func:`analysis_run` and + :func:`analysis_artifacts_run` to avoid repeating the autodiscovery block. + + Parameters + ---------- + account : str or None + SLURM account name. ``None`` triggers ``SlurmConfig.from_cluster()``. + partition : str or None + SLURM partition. ``None`` lets ``from_cluster`` choose; ``"cpu"`` + is used as the fallback when *account* is explicit. + time : str + Wall-time limit (e.g. ``"2h"``). + cpus : int + CPUs per task. + mem_gb : int + Memory per node in GiB. + qos : str or None + SLURM QOS string. + constraint : str or None + SLURM constraint string. + job_name_prefix : str + Prefix for the SLURM job name. + + Returns + ------- + SlurmConfig + Resolved SLURM configuration. + + Raises + ------ + ValueError + If autodiscovery fails and *account* was not provided. + + """ + if account is None: + try: + slurm_cfg = SlurmConfig.from_cluster( + needs_gpu=False, + min_cpus=cpus, + min_mem_gb=mem_gb, + time=time, + cpus_per_task=cpus, + mem_gb=mem_gb, + qos=qos, + constraint=constraint, + job_name_prefix=job_name_prefix, + **({"partition": partition} if partition is not None else {}), + ) + logger.info( + f"Using autodiscovered SLURM config: account={slurm_cfg.account}, " + f"partition={slurm_cfg.partition}" + ) + except RuntimeError as e: + raise ValueError( + f"SLURM autodiscovery failed: {e}\nPlease specify --account explicitly." + ) from e + else: + slurm_cfg = SlurmConfig( + account=account, + partition=partition or "cpu", + time=time, + cpus_per_task=cpus, + mem_gb=mem_gb, + qos=qos, + constraint=constraint, + job_name_prefix=job_name_prefix, + ) + return slurm_cfg + + @analysis_app.command(name="run") def analysis_run( source: Path | None = None, @@ -1153,7 +1799,7 @@ def analysis_run( skip_existing: bool = True, slurm: bool = False, account: str | None = None, - partition: str = "cpu", + partition: str | None = None, time: str = "2h", cpus: int = 4, mem_gb: int = 8, @@ -1187,8 +1833,6 @@ def analysis_run( if source is None: raise ValueError("Provide SOURCE as a simulation directory or build summary YAML.") - if slurm and account is None: - raise ValueError("--account is required when using --slurm.") if analysis_workers is not None and analysis_workers < 1: raise ValueError("--analysis-workers must be >= 1.") @@ -1233,15 +1877,8 @@ def analysis_run( print(result_df) return - slurm_cfg = SlurmConfig( - account=account or "", - partition=partition, - time=time, - cpus_per_task=cpus, - mem_gb=mem_gb, - qos=qos, - constraint=constraint, - job_name_prefix=job_name_prefix, + slurm_cfg = _resolve_analysis_slurm_config( + account, partition, time, cpus, mem_gb, qos, constraint, job_name_prefix ) if log_dir is None: log_dir = determine_log_dir(sim_paths) @@ -1473,7 +2110,7 @@ def analysis_artifacts_run( skip_existing: bool = True, slurm: bool = False, account: str | None = None, - partition: str = "cpu", + partition: str | None = None, time: str = "2h", cpus: int = 4, mem_gb: int = 8, @@ -1520,18 +2157,8 @@ def analysis_artifacts_run( print(summary) return - if account is None: - raise ValueError("--account is required when using --slurm.") - - slurm_cfg = SlurmConfig( - account=account, - partition=partition, - time=time, - cpus_per_task=cpus, - mem_gb=mem_gb, - qos=qos, - constraint=constraint, - job_name_prefix=job_name_prefix, + slurm_cfg = _resolve_analysis_slurm_config( + account, partition, time, cpus, mem_gb, qos, constraint, job_name_prefix ) if log_dir is None: log_dir = determine_log_dir(sim_paths) @@ -1750,6 +2377,151 @@ def config_edit(): subprocess.run([editor, str(config_path)], check=False) +@config_app.command(name="slurm") +def config_slurm(): + """Interactive wizard to configure a SLURM executor and save to YAML. + + Queries the local SLURM scheduler for available accounts, partitions, + and hardware, then walks through resource selection interactively. + The result is saved to a YAML file that can be reused with + ``mdfactory build --slurm ``. + + On non-SLURM machines, falls back to manual text entry. + """ + from mdfactory.orchestration.tui import UserCancelledError, configure_and_save_slurm + + try: + configure_and_save_slurm() + except UserCancelledError: + logger.info("SLURM configuration cancelled.") + + +@config_app.command(name="environment") +def config_environment(): + """Interactive wizard to configure the execution environment. + + Detects the current Python environment (pixi, conda, or virtualenv), + prompts for GROMACS modules and additional init commands, then saves + the result to the global config directory. + + The saved environment config is automatically used by SLURM executor + YAMLs that don't include an ``environment:`` section, so you only + need to configure this once per machine. + """ + from mdfactory.orchestration.tui import UserCancelledError, configure_and_save_environment + + try: + configure_and_save_environment() + except UserCancelledError: + logger.info("Environment configuration cancelled.") + + +@config_app.command(name="cluster") +def config_cluster( + json_output: Annotated[bool, Parameter("--json", help="Output in JSON format.")] = False, +): + """Show discovered SLURM cluster information. + + Queries the local SLURM scheduler and displays available partitions, + accounts, and QOS policies. Useful for verifying autodiscovery works + and understanding cluster resources before submitting jobs. + + On non-SLURM machines, prints a helpful message instead of failing. + """ + import json as json_module + + from mdfactory.performance.cluster import discover_cluster + + cluster = discover_cluster() + + if cluster is None: + if json_output: + print(json_module.dumps({"error": "SLURM not available", "cluster": None})) + else: + print("SLURM cluster not detected.") + print() + print("This machine does not appear to be a SLURM cluster node,") + print("or SLURM commands (sinfo, sacctmgr) are not in PATH.") + print() + print("To use SLURM submission, run this command on a cluster login node.") + return + + if json_output: + # Build JSON-serializable structure + data = { + "default_account": cluster.default_account, + "accounts": cluster.accounts, + "qos_policies": cluster.qos_policies, + "partitions": [ + { + "name": p.name, + "state": p.state, + "is_default": p.is_default, + "max_time": p.max_time, + "default_time": p.default_time, + "total_nodes": p.total_nodes, + "node_types": [ + { + "cpus": nt.cpus, + "memory_mb": nt.memory_mb, + "gpu_specs": [ + {"count": count, "type": gtype} for count, gtype in nt.gpu_specs + ], + "features": list(nt.features), + "count": nt.count, + } + for nt in p.node_types + ], + } + for p in cluster.partitions + ], + } + print(json_module.dumps(data, indent=2)) + return + + # Human-readable output + print("SLURM Cluster Information") + print("=" * 50) + print() + + # Account info + print(f"Default Account: {cluster.default_account or '(none)'}") + if cluster.accounts: + print(f"Available Accounts: {', '.join(cluster.accounts)}") + print() + + # QOS info + if cluster.qos_policies: + print(f"QOS Policies: {', '.join(cluster.qos_policies)}") + print() + + # Partition info + print("Partitions:") + print("-" * 50) + for partition in cluster.partitions: + default_marker = " (default)" if partition.is_default else "" + state_marker = f" [{partition.state}]" if partition.state != "up" else "" + print(f"\n {partition.name}{default_marker}{state_marker}") + print(f" Nodes: {partition.total_nodes}") + print(f" Max Time: {partition.max_time}") + if partition.default_time != partition.max_time: + print(f" Default Time: {partition.default_time}") + + # Summarize node types + for nt in partition.node_types: + gpu_info = "" + if nt.gpu_specs: + # Format multiple GPU types like "7x b200, 7x 1g.23gb" + gpu_parts = [f"{count}x {gtype or 'unknown'}" for count, gtype in nt.gpu_specs] + gpu_info = f", {', '.join(gpu_parts)}" + mem_gb = nt.memory_mb // 1024 + features_str = "" + if nt.features: + features_str = f" [{', '.join(nt.features)}]" + count_str = f" ({nt.count} node{'s' if nt.count != 1 else ''})" + print(f" - {nt.cpus} CPUs, {mem_gb} GB{gpu_info}{features_str}{count_str}") + + def main(): app() diff --git a/mdfactory/models/composition.py b/mdfactory/models/composition.py index 8488d1d..2e0cb0e 100644 --- a/mdfactory/models/composition.py +++ b/mdfactory/models/composition.py @@ -1,13 +1,13 @@ -# ABOUTME: Pydantic models for system composition (mixedbox, bilayer, LNP) +# ABOUTME: Pydantic models for system composition (mixedbox, bilayer, LNP, proteinbox) # ABOUTME: Defines species counts, ionization, and composition validation -"""Pydantic models for system composition (mixedbox, bilayer, LNP).""" +"""Pydantic models for system composition (mixedbox, bilayer, LNP, proteinbox).""" from typing import Optional import numpy as np from pydantic import BaseModel, ConfigDict, Field, model_validator -from .species import LipidSpecies, SingleMoleculeSpecies, Species +from .species import LipidSpecies, ProteinSpecies, SingleMoleculeSpecies, Species def distribute_counts(fractions: list[float], total: int) -> list[int]: @@ -375,3 +375,37 @@ def get_species_with_counts(self) -> list[Species]: else: species_map[key] = spec.model_copy() return list(species_map.values()) + + +class ProteinBoxComposition(BaseModel): + """Define a protein-in-waterbox system: one protein centered in a cubic box with ions.""" + + model_config = ConfigDict(extra="forbid") + + protein: ProteinSpecies + padding: float = Field( + 10.0, description="Minimum distance from protein to box edge in Angstroms.", ge=0.0 + ) + relax_steps: int = Field( + 10000, + description="OpenMM steps for the position-restrained relaxation of the solvated system.", + ge=0, + ) + ionization: IonizationConfig = Field( + default_factory=IonizationConfig, description="Configuration for ionization." + ) + + @property + def species(self) -> list[ProteinSpecies]: + """Return protein as a single-element species list for metadata compatibility.""" + return [self.protein] + + @property + def total_count(self) -> int: + """Return 1 (one protein) for metadata compatibility.""" + return 1 + + @property + def charge(self) -> int: + """Protein charge is not pre-computable; return 0 as placeholder.""" + return 0 diff --git a/mdfactory/models/input.py b/mdfactory/models/input.py index e24497a..c699cab 100644 --- a/mdfactory/models/input.py +++ b/mdfactory/models/input.py @@ -8,8 +8,18 @@ from pydantic import BaseModel, Field, model_validator -from .composition import BilayerComposition, LNPComposition, MixedBoxComposition -from .parametrization import CgenffConfig, ParametrizationConfig, SmirnoffConfig +from .composition import ( + BilayerComposition, + LNPComposition, + MixedBoxComposition, + ProteinBoxComposition, +) +from .parametrization import ( + CgenffConfig, + ParametrizationConfig, + Pdb2gmxConfig, + SmirnoffConfig, +) # Map system_type to the corresponding model class # TODO: StrEnum? @@ -17,15 +27,25 @@ "mixedbox": MixedBoxComposition, "bilayer": BilayerComposition, "lnp": LNPComposition, + "proteinbox": ProteinBoxComposition, +} + +# Parametrizations each simulation type accepts. Proteins are parametrized with +# gmx pdb2gmx; small-molecule systems use CGenFF or SMIRNOFF. +allowed_parametrizations = { + "mixedbox": {"cgenff", "smirnoff"}, + "bilayer": {"cgenff", "smirnoff"}, + "lnp": {"cgenff", "smirnoff"}, + "proteinbox": {"pdb2gmx"}, } class BuildInput(BaseModel): """Represent a complete simulation build specification with composition and parametrization.""" - simulation_type: Literal["mixedbox", "bilayer", "lnp"] - system: MixedBoxComposition | BilayerComposition | LNPComposition - parametrization: Literal["cgenff", "smirnoff"] = Field( + simulation_type: Literal["mixedbox", "bilayer", "lnp", "proteinbox"] + system: MixedBoxComposition | BilayerComposition | LNPComposition | ProteinBoxComposition + parametrization: Literal["cgenff", "smirnoff", "pdb2gmx"] = Field( "cgenff", description="Parametrization to use." ) parametrization_config: ParametrizationConfig | None = Field( @@ -74,6 +94,10 @@ def metadata(self) -> dict[str, Any]: elif self.simulation_type == "mixedbox": system_specific["target_density"] = self.system.target_density system_specific["ionization"] = self.system.ionization.model_dump() + elif self.simulation_type == "proteinbox": + system_specific["padding"] = self.system.padding + system_specific["ionization"] = self.system.ionization.model_dump() + system_specific["pdb_path"] = str(self.system.protein.pdb_path) return { "hash": self.hash, @@ -162,4 +186,41 @@ def set_default_parametrization_config(self) -> "BuildInput": object.__setattr__(self, "parametrization_config", SmirnoffConfig()) elif self.parametrization == "cgenff": object.__setattr__(self, "parametrization_config", CgenffConfig()) + elif self.parametrization == "pdb2gmx": + object.__setattr__(self, "parametrization_config", Pdb2gmxConfig()) + return self + + @model_validator(mode="after") + def validate_parametrization_consistency(self) -> "BuildInput": + """Ensure simulation type, parametrization, and config discriminator agree.""" + allowed = allowed_parametrizations[self.simulation_type] + if self.parametrization not in allowed: + raise ValueError( + f"Parametrization '{self.parametrization}' is not valid for " + f"simulation type '{self.simulation_type}'. Allowed: {sorted(allowed)}." + ) + config_type = self.parametrization_config.type + if config_type != self.parametrization: + raise ValueError( + f"Parametrization config type '{config_type}' does not match " + f"parametrization '{self.parametrization}'." + ) + return self + + @model_validator(mode="after") + def validate_proteinbox_chain_config(self) -> "BuildInput": + """Reject declaring chains while also merging them into one moleculetype. + + merge_all fuses every chain into a single ``Protein`` moleculetype, which + contradicts protein.chains declaring subunits to build separately. Catching + this here avoids a confusing chain-mismatch failure deep inside pdb2gmx. + """ + if self.simulation_type != "proteinbox": + return self + if getattr(self.parametrization_config, "merge_all", False) and self.system.protein.chains: + raise ValueError( + "protein.chains declares subunits to build as separate moleculetypes, but " + "parametrization_config.merge_all merges all chains into one. Set " + "merge_all=false to keep per-chain subunits, or drop protein.chains to merge." + ) return self diff --git a/mdfactory/models/parametrization.py b/mdfactory/models/parametrization.py index 13b9298..f1607e0 100644 --- a/mdfactory/models/parametrization.py +++ b/mdfactory/models/parametrization.py @@ -6,7 +6,20 @@ from pathlib import Path from typing import Annotated, Literal, Optional -from pydantic import AfterValidator, BaseModel, ConfigDict, Discriminator, Field, FilePath, Tag +from pydantic import ( + AfterValidator, + BaseModel, + ConfigDict, + Discriminator, + Field, + FilePath, + Tag, + model_validator, +) + +# The proteinbox pipeline solvates with 3-site spc216 coordinates and uses +# CHARMM Force-switch LJ in every mdp, so only these combinations are supported. +SUPPORTED_WATER_MODELS = frozenset({"tip3p", "spc", "spce"}) def validate_absolute_path(path: Path): @@ -50,8 +63,49 @@ class CgenffConfig(BaseModel): # CGenFF uses SILCSBIODIR from config.ini, no additional settings needed +class Pdb2gmxConfig(BaseModel): + """Configuration for pdb2gmx protein parametrization.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + type: Literal["pdb2gmx"] = Field("pdb2gmx", description="Config type discriminator.") + forcefield: str = Field( + "charmm36m", description="Force field name as recognized by gmx pdb2gmx." + ) + water_model: str = Field("tip3p", description="Water model name as recognized by gmx pdb2gmx.") + ignore_hydrogens: bool = Field( + True, description="Ignore hydrogens in input PDB (regenerate with pdb2gmx)." + ) + merge_all: bool = Field(False, description="Merge all chains into a single moleculetype.") + + @model_validator(mode="after") + def check_supported_forcefield_and_water(self) -> "Pdb2gmxConfig": + """Reject force fields and water models the proteinbox pipeline cannot honor.""" + if not self.forcefield.lower().startswith("charmm"): + raise ValueError( + f"Force field '{self.forcefield}' is not supported. The proteinbox " + "pipeline uses CHARMM Force-switch LJ settings, so only CHARMM force " + "fields (e.g. charmm36m, charmm36, charmm27) are supported." + ) + if "ljpme" in self.forcefield.lower(): + raise ValueError( + f"Force field '{self.forcefield}' is not supported. The proteinbox " + "run schedule uses cutoff/Force-switch LJ, which is incompatible with " + "an LJ-PME force field. Use the standard CHARMM variant (e.g. charmm36m)." + ) + if self.water_model.lower() not in SUPPORTED_WATER_MODELS: + raise ValueError( + f"Water model '{self.water_model}' is not supported. Solvation uses " + "3-site spc216 coordinates, so only 3-site water models are supported: " + f"{sorted(SUPPORTED_WATER_MODELS)}." + ) + return self + + ParametrizationConfig = Annotated[ - Annotated[SmirnoffConfig, Tag("smirnoff")] | Annotated[CgenffConfig, Tag("cgenff")], + Annotated[SmirnoffConfig, Tag("smirnoff")] + | Annotated[CgenffConfig, Tag("cgenff")] + | Annotated[Pdb2gmxConfig, Tag("pdb2gmx")], Discriminator("type"), ] @@ -91,3 +145,17 @@ def from_data_row(cls, data_row): raise ValueError("Invalid parameter_data_type for GromacsSingleMoleculeParameterSet.") data = json.loads(data_row["parameter_data"]) return cls(**data) + + +class GromacsProteinParameterSet(BaseModel): + """Store GROMACS topology output from pdb2gmx for a protein system.""" + + model_config = ConfigDict(extra="forbid") + + topology_file: Path + structure_file: Path + topology_include_files: list[Path] + chains: list[str] + forcefield: str + water_model: str + total_charge: int diff --git a/mdfactory/models/species.py b/mdfactory/models/species.py index 1912ce5..37685fe 100644 --- a/mdfactory/models/species.py +++ b/mdfactory/models/species.py @@ -1,11 +1,12 @@ -# ABOUTME: Pydantic models for molecular species (small molecules and lipids) +# ABOUTME: Pydantic models for molecular species (small molecules, lipids, proteins) # ABOUTME: Provides SMILES-based identity, charge, and molecular object properties -"""Pydantic models for molecular species (small molecules and lipids).""" +"""Pydantic models for molecular species (small molecules, lipids, proteins).""" import hashlib import io import warnings from functools import cached_property +from pathlib import Path from typing import Optional from pydantic import BaseModel, Field, model_validator @@ -80,7 +81,7 @@ def rdkit_molecule(self) -> "Chem.rdchem.Mol": f"Attempted to build lipid, but it didn't work: {e}. Continuing with normal code..." ) if head_index is not None and len(tail_indices): - from ..utils.setup_utilities import generate_lipid_structure + from ..utils.setup_utilities import generate_lipid_structure # noqa: PLC0415 try: mol = generate_lipid_structure( @@ -119,7 +120,7 @@ def rdkit_molecule(self) -> "Chem.rdchem.Mol": @property def openff_molecule(self): """Return an OpenFF Molecule with residue name set.""" - from openff.toolkit.topology import Molecule + from openff.toolkit.topology import Molecule # noqa: PLC0415 ret = Molecule.from_rdkit( self.rdkit_molecule, @@ -132,7 +133,7 @@ def openff_molecule(self): @property def universe(self): """Return an MDAnalysis Universe built from the molecule's PDB representation.""" - import MDAnalysis as mda + import MDAnalysis as mda # noqa: PLC0415 pdb = Chem.MolToPDBBlock(self.rdkit_molecule) u = mda.Universe(io.StringIO(pdb), format="pdb") @@ -179,7 +180,7 @@ def model_post_init(self, _) -> None: @cached_property def rdkit_molecule(self) -> "Chem.rdchem.Mol": """Return an RDKit molecule with lipid-specific 3D coordinate generation.""" - from ..utils.setup_utilities import generate_lipid_structure + from ..utils.setup_utilities import generate_lipid_structure # noqa: PLC0415 mol = generate_lipid_structure( self.smiles, head_indices=self.head_atoms, tail_indices=self.tail_atoms @@ -196,3 +197,62 @@ def rdkit_molecule(self) -> "Chem.rdchem.Mol": # u, tail_atom_ids=self.tail_atoms, head_atom_ids=self.head_atoms, z_axis=[1, 0, 0] # ) # return u + + +class ProteinSpecies(Species): + """Represent a protein species identified by a PDB structure file.""" + + count: Optional[int] = Field(1, description="Number of protein copies (always 1).") + fraction: Optional[float] = Field(1.0, description="Fraction (always 1.0 for protein).") + pdb_path: Path = Field(..., description="Path to the input PDB file.") + chains: list[str] = Field( + default_factory=list, + description=( + "Chain identifiers to build, e.g. ['A'] or ['H', 'L', 'Y']. Required for " + "multi-chain structures and validated against the chains pdb2gmx produces; " + "optional for a single-chain structure." + ), + ) + disulfide_bonds: list[tuple[str, str]] = Field( + default_factory=list, + description=( + "Pairs of cysteine residues forming disulfide bonds, " + "e.g. [('CYS6', 'CYS127'), ('CYS30', 'CYS115')]. Each residue may be " + "chain-qualified and may include a PDB insertion code (e.g. 'A:CYS6' or " + "'H:CYS100A'); a partially qualified reference matching multiple residues is " + "rejected as ambiguous." + ), + ) + protonation_states: dict[str, str] = Field( + default_factory=dict, + description=( + "Residue-specific protonation states, e.g. {'HIS15': 'HIE'}. Keys may be " + "chain-qualified and may include a PDB insertion code (e.g. " + "{'A:HIS15': 'HIE'} or {'H:HIS100A': 'HIE'}); a partially qualified key " + "matching multiple residues is rejected as ambiguous. For CHARMM only " + "3-character states are expressible (histidine tautomers, neutral lysine)." + ), + ) + + @model_validator(mode="after") + def check_single_copy(self) -> "ProteinSpecies": + """Enforce that a protein species describes exactly one copy. + + The builder produces a single protein structure and metadata reports a + total count of 1, so a count or fraction other than one would silently + contradict what is built. + """ + if self.count != 1: + raise ValueError( + f"ProteinSpecies represents one protein; count must be 1, got {self.count}." + ) + if self.fraction != 1.0: + raise ValueError( + f"ProteinSpecies represents one protein; fraction must be 1.0, got {self.fraction}." + ) + return self + + @property + def charge(self) -> int | None: + """Return None; protein charge comes from the pdb2gmx topology, not the species.""" + return None diff --git a/mdfactory/orchestration/__init__.py b/mdfactory/orchestration/__init__.py new file mode 100644 index 0000000..b8bed49 --- /dev/null +++ b/mdfactory/orchestration/__init__.py @@ -0,0 +1,27 @@ +# ABOUTME: Public API for Parsl-based parallel build and simulation orchestration +# ABOUTME: Exports executor configs, build dispatch, and simulation dispatch functions +"""Parsl-based parallel build and simulation orchestration for mdfactory.""" + +from .build import build_systems +from .config import ExecutorConfig, SlurmExecutorConfig +from .environment import EnvironmentConfig, get_global_environment_path +from .simulate import clean_simulation_outputs, find_structure_file, run_simulations +from .tui import ( + configure_and_save_environment, + configure_and_save_slurm, + configure_slurm_interactive, +) + +__all__ = [ + "EnvironmentConfig", + "ExecutorConfig", + "SlurmExecutorConfig", + "build_systems", + "clean_simulation_outputs", + "configure_and_save_environment", + "configure_and_save_slurm", + "configure_slurm_interactive", + "find_structure_file", + "get_global_environment_path", + "run_simulations", +] diff --git a/mdfactory/orchestration/apps.py b/mdfactory/orchestration/apps.py new file mode 100644 index 0000000..25b6be6 --- /dev/null +++ b/mdfactory/orchestration/apps.py @@ -0,0 +1,823 @@ +# ABOUTME: Parsl application definitions for build orchestration +# ABOUTME: Wraps run_build_from_dict as a @python_app with runtime decoration +"""Parsl application definitions for build orchestration.""" + +from __future__ import annotations + +# --------------------------------------------------------------------------- +# Layer 1: Single-responsibility decision resolvers +# --------------------------------------------------------------------------- + + +def _resolve_thread_count_expr(ntasks: int) -> str: + """Return the bash expression used to assign NTHR. + + Parameters + ---------- + ntasks : int + Explicit thread count. When > 0, returns a literal decimal string + so that no shell expansion is needed. When 0, returns a three-level + fallback expression: SLURM_CPUS_PER_TASK > OMP_NUM_THREADS > nproc. + + Returns + ------- + str + Literal integer string or shell parameter expansion. + + Notes + ----- + The three-level fallback + ``${SLURM_CPUS_PER_TASK:-${OMP_NUM_THREADS:-$(nproc)}}`` is load-bearing. + Parsl's SlurmProvider injects OMP_NUM_THREADS= into worker + environments; the two-level form ``${SLURM_CPUS_PER_TASK:-$(nproc)}`` + would silently use *all* cores on the node when SLURM_CPUS_PER_TASK is + absent, causing a thread-count mismatch that GROMACS aborts on. + + """ + if ntasks > 0: + return str(ntasks) + return "${SLURM_CPUS_PER_TASK:-${OMP_NUM_THREADS:-$(nproc)}}" + + +def _resolve_is_mpi(gmx_binary: str) -> "bool | None": + """Return whether the GROMACS binary is an MPI build. + + Parameters + ---------- + gmx_binary : str + One of ``"gmx"``, ``"gmx_mpi"``, or ``"auto"``. + + Returns + ------- + bool or None + ``True`` for ``gmx_mpi``, ``False`` for ``gmx``, ``None`` for auto + (binary unknown until runtime). + + """ + if gmx_binary == "gmx_mpi": + return True + if gmx_binary == "gmx": + return False + return None + + +def _resolve_thread_flags(is_mpi: "bool | None", has_gpu: bool) -> str: + """Return the thread-count flags for the mdrun invocation. + + Parameters + ---------- + is_mpi : bool or None + ``True`` for MPI build, ``False`` for thread-MPI build, ``None`` when + the binary is unknown (auto mode). + has_gpu : bool + Whether GPU execution is active. + + Returns + ------- + str + Thread flag string to embed literally in the mdrun command line. + + """ + if is_mpi is None: + return "$MDRUN_THREAD_FLAGS" + if is_mpi: + return "-ntomp $NTHR" + if has_gpu: + return "-ntmpi 1 -ntomp $NTHR" + return "-nt $NTHR" + + +def _resolve_gpu_flags(has_gpu: bool, pme_gpu: bool) -> str: + """Return the GPU offload flags for the mdrun invocation. + + Parameters + ---------- + has_gpu : bool + Whether GPU execution is active. + pme_gpu : bool + Whether PME should run on GPU. Must be ``False`` for non-dynamical + integrators (e.g. EM/steep) which GROMACS rejects with PME-GPU. + + Returns + ------- + str + GPU flag string, or ``""`` for CPU-only runs. + + """ + if not has_gpu: + return "" + pme_flag = "-pme gpu" if pme_gpu else "-pme cpu" + return f"-nb gpu {pme_flag} -gpu_id $GPU_ID" + + +def _resolve_restart_flags(restart_from_cpt: str) -> str: + """Return checkpoint restart flags for the mdrun invocation. + + Parameters + ---------- + restart_from_cpt : str + Path to checkpoint file, or ``""`` for a fresh run. + + Returns + ------- + str + ``"-cpi -append"`` when a checkpoint is given, else ``""``. + + """ + if not restart_from_cpt: + return "" + return f"-cpi {restart_from_cpt} -append" + + +def _resolve_binary_token(gmx_binary: str) -> str: + """Return the executable token for use in the mdrun command line. + + Parameters + ---------- + gmx_binary : str + One of ``"gmx"``, ``"gmx_mpi"``, or ``"auto"``. + + Returns + ------- + str + Literal executable name (``"gmx"`` / ``"gmx_mpi"``) or the shell + variable reference ``"$GMX_BIN"`` for auto mode. + + """ + if gmx_binary == "gmx": + return "gmx" + if gmx_binary == "gmx_mpi": + return "gmx_mpi" + return "$GMX_BIN" + + +# --------------------------------------------------------------------------- +# Layer 2: Command assembler +# --------------------------------------------------------------------------- + + +def _assemble_mdrun_command( + binary: str, + deffnm: str, + restart_flags: str, + thread_flags: str, + gpu_flags: str, +) -> str: + """Compose the single concrete mdrun command line. + + All inputs are already-resolved strings (literals or shell variable + references). Joins non-empty parts with single spaces; no branching. + + Parameters + ---------- + binary : str + Executable token: ``"gmx"``, ``"gmx_mpi"``, or ``"$GMX_BIN"``. + deffnm : str + Default filename prefix (min, nvt, npt, prod). + restart_flags : str + Checkpoint flags (``"-cpi -append"`` or ``""``). + thread_flags : str + Thread-count flags (``"-ntomp $NTHR"``, ``"-nt $NTHR"``, etc.). + gpu_flags : str + GPU offload flags (``"-nb gpu -pme gpu -gpu_id $GPU_ID"`` or ``""``). + + Returns + ------- + str + The complete mdrun command as a single line. + + Examples + -------- + >>> _assemble_mdrun_command("gmx", "min", "", "-nt $NTHR", "") + 'gmx mdrun -deffnm min -nt $NTHR' + >>> _assemble_mdrun_command("gmx_mpi", "prod", "-cpi prod.cpt -append", "-ntomp $NTHR", "") + 'gmx_mpi mdrun -deffnm prod -cpi prod.cpt -append -ntomp $NTHR' + + """ + parts = [binary, "mdrun", f"-deffnm {deffnm}"] + for flag in (restart_flags, thread_flags, gpu_flags): + if flag: + parts.append(flag) + return " ".join(parts) + + +# --------------------------------------------------------------------------- +# Layer 3: Script section builders +# --------------------------------------------------------------------------- + + +def _build_env_preamble(work_dir: str, nthr_expr: str, has_gpu: bool) -> str: + """Emit the environment-setup lines for the bash script. + + No conditional branching — all lines are unconditional assignments. + + Parameters + ---------- + work_dir : str + Absolute path to simulation directory (``cd`` target). + nthr_expr : str + Expression to assign to NTHR (from :func:`_resolve_thread_count_expr`). + has_gpu : bool + When ``True``, include the ``GPU_ID`` assignment. + + Returns + ------- + str + Multi-line preamble string with no leading or trailing newline. + + Notes + ----- + Emit order: + + 1. ``set -euo pipefail`` + 2. ``cd `` + 3. ``NTHR=`` + 4. ``GPU_ID=${CUDA_VISIBLE_DEVICES%%,*}`` — only when ``has_gpu=True`` + 5. ``export OMP_NUM_THREADS=$NTHR`` + + The GPU_ID assignment precedes the export so the export line is always the + final line of the preamble section. + + """ + lines = [ + "set -euo pipefail", + f"cd {work_dir}", + f"NTHR={nthr_expr}", + ] + if has_gpu: + # Use :- instead of %%,* so CUDA_VISIBLE_DEVICES unset → empty string + # rather than aborting under set -u on CPU-only hosts. + lines.append("GPU_ID=${CUDA_VISIBLE_DEVICES:-}") + lines.append("export OMP_NUM_THREADS=$NTHR") + return "\n".join(lines) + + +def _build_gmx_detect_block(gmx_extra: str = "", gmx_mpi_extra: str = "") -> str: + """Emit the core GROMACS binary detection if/elif/else/fi block. + + Assigns ``$GMX_BIN`` to ``gmx`` or ``gmx_mpi`` depending on what is + found in ``PATH``, and exits with an error if neither is available. + Optional extra shell assignments (e.g. setting ``$MDRUN_THREAD_FLAGS``) + are appended with a semicolon to the respective branch lines. + + Parameters + ---------- + gmx_extra : str, optional + Additional assignment appended to the ``gmx`` branch, e.g. + ``'MDRUN_THREAD_FLAGS="-nt $NTHR"'``. Empty string means no extra. + gmx_mpi_extra : str, optional + Additional assignment appended to the ``gmx_mpi`` branch. + + Returns + ------- + str + Multi-line if/elif/else/fi block with no leading or trailing newline. + + Notes + ----- + This is the single source of truth for the detection skeleton shared by + :func:`_build_binary_detection_preamble` (mdrun) and + :func:`_get_gromacs_detect_script` (grompp). + + """ + gmx_line = f" GMX_BIN=gmx{'; ' + gmx_extra if gmx_extra else ''}" + gmx_mpi_line = f" GMX_BIN=gmx_mpi{'; ' + gmx_mpi_extra if gmx_mpi_extra else ''}" + return ( + "if command -v gmx &>/dev/null; then\n" + f"{gmx_line}\n" + "elif command -v gmx_mpi &>/dev/null; then\n" + f"{gmx_mpi_line}\n" + "else\n" + ' echo "ERROR: Neither gmx nor gmx_mpi found in PATH" >&2; exit 1\n' + "fi" + ) + + +def _build_binary_detection_preamble(has_gpu: bool) -> str: + """Emit the GROMACS binary detection block for auto mode. + + This is the only permitted runtime branching in the mdrun script. It + assigns both ``$GMX_BIN`` and ``$MDRUN_THREAD_FLAGS`` so that the mdrun + command line can reference both and remain a single unconditional line. + + Parameters + ---------- + has_gpu : bool + When ``True``, the thread-MPI branch uses ``-ntmpi 1 -ntomp $NTHR`` + (GPU requires thread-MPI pinning); when ``False``, uses ``-nt $NTHR`` + (let GROMACS allocate threads freely on CPU). + + Returns + ------- + str + Multi-line if/elif/else/fi block with no leading or trailing newline. + + Notes + ----- + The block is absent when ``gmx_binary`` is set to ``"gmx"`` or + ``"gmx_mpi"`` — only auto mode needs runtime binary detection. + Delegates to :func:`_build_gmx_detect_block` for the shared skeleton. + + """ + tmpi_flags = '"-ntmpi 1 -ntomp $NTHR"' if has_gpu else '"-nt $NTHR"' + return _build_gmx_detect_block( + gmx_extra=f"MDRUN_THREAD_FLAGS={tmpi_flags}", + gmx_mpi_extra='MDRUN_THREAD_FLAGS="-ntomp $NTHR"', + ) + + +def _build_output_check(gro_out: str, traj_files: "tuple[str, ...]") -> str: + """Emit output existence verification for the bash script. + + Parameters + ---------- + gro_out : str + Expected output ``.gro`` file, or ``""`` when not applicable. + traj_files : tuple of str + Expected trajectory files (accept any of them), or ``()`` when not + applicable. Non-empty for the Production stage only. + + Returns + ------- + str + An if-block that exits with code 1 when outputs are missing, or ``""`` + when no output verification is required. + + """ + if traj_files: + traj_conds = " && ".join(f'[ ! -f "{f}" ]' for f in traj_files) + traj_list = " or ".join(traj_files) + return ( + f"if {traj_conds}; then\n" + f' echo "ERROR: mdrun completed but none of ({traj_list}) was created" >&2\n' + f" exit 1\n" + f"fi" + ) + if gro_out: + return ( + f'if [ ! -f "{gro_out}" ]; then\n' + f' echo "ERROR: mdrun completed but {gro_out} was not created" >&2\n' + f" exit 1\n" + f"fi" + ) + return "" + + +def _get_gromacs_detect_script() -> str: + """Return bash snippet for GROMACS binary auto-detection (grompp use). + + Wraps :func:`_build_gmx_detect_block` with a comment header and + surrounding newlines so it embeds cleanly inside the grompp f-string. + + Returns + ------- + str + Bash block that sets ``$GMX_BIN`` to ``gmx`` or ``gmx_mpi``, + with a leading blank line, comment, and trailing newline. + + Notes + ----- + Prefers ``gmx`` (thread-MPI) for local execution; falls back to + ``gmx_mpi``. This avoids PMI/srun issues when using LocalProvider + with MPI builds. Only ``$GMX_BIN`` is set here — no thread-flag + assignment is needed for grompp. + + """ + block = _build_gmx_detect_block() + return ( + f"\n# Auto-detect GROMACS binary (prefer gmx thread-MPI, fall back to gmx_mpi)\n{block}\n" + ) + + +def _require_bash_app(): + """Import and return Parsl's ``bash_app`` decorator. + + Factored out of :func:`get_grompp_app` and :func:`get_mdrun_app` to avoid + repeating the same try/except import guard in every factory function. + + Returns + ------- + callable + The ``parsl.bash_app`` decorator. + + Raises + ------ + ImportError + If parsl is not installed. + + """ + try: + from parsl import bash_app # type: ignore[import-not-found] + except ImportError as exc: + raise ImportError( + "parsl is required for simulation orchestration. " + "Install with `pip install 'mdfactory[parsl]'`." + ) from exc + return bash_app + + +def _build_system_impl(build_input_dict: dict) -> dict: + """Run a single system build inside a Parsl worker. + + All heavy imports (OpenMM, MDAnalysis, OpenFF) happen inside the + worker process — only the plain dict crosses the serialization boundary. + + Parameters + ---------- + build_input_dict : dict + Serialized BuildInput as a plain dictionary. May contain a special + ``_build_dir`` key specifying the output directory. + + Returns + ------- + dict + Result dictionary with hash, status, and directory. + + """ + import os + from pathlib import Path + + from mdfactory.models.input import BuildInput + from mdfactory.workflows import run_build_from_dict + + # Extract and remove internal keys before validation + input_copy = {k: v for k, v in build_input_dict.items() if not k.startswith("_")} + model = BuildInput(**input_copy) + build_dir = Path(build_input_dict.get("_build_dir", model.hash)) + build_dir.mkdir(parents=True, exist_ok=True) + original_dir = os.getcwd() + try: + os.chdir(build_dir) + run_build_from_dict(model) + finally: + os.chdir(original_dir) + return {"hash": model.hash, "status": "success", "directory": str(build_dir.resolve())} + + +def get_build_app(): + """Create and return the Parsl python_app for building systems. + + The ``@python_app`` decorator is applied here (not at module level) + because it requires an active Parsl DataFlowKernel. + + Returns + ------- + callable + A Parsl ``@python_app`` wrapping the build implementation. + + Raises + ------ + ImportError + If parsl is not installed. + + """ + try: + from parsl import python_app # type: ignore[import-not-found] + except ImportError as exc: + raise ImportError( + "parsl is required for build orchestration. " + "Install with `pip install 'mdfactory[parsl]'`." + ) from exc + return python_app(_build_system_impl) + + +def _build_grompp_script( + mdp_file: str, + gro_file: str, + top_file: str, + tpr_file: str, + work_dir: str, + ref_file: str = "", + cpt_file: str = "", + maxwarn: int = 1, +) -> str: + """Build the bash script for ``gmx grompp``. + + Pure function — no Parsl dependency. Fully testable without a + DataFlowKernel by calling it directly with test arguments. + + Parameters + ---------- + mdp_file : str + MDP parameter file (em.mdp, nvt.mdp, npt.mdp, md.mdp). + gro_file : str + Input structure file (system.pdb, min.gro, etc). + top_file : str + Topology file (topology.top). + tpr_file : str + Output TPR file (min.tpr, nvt.tpr, etc). + work_dir : str + Absolute path to simulation directory. + ref_file : str, optional + Reference structure for position restraints (-r flag). + cpt_file : str, optional + Checkpoint file for velocities (-t flag). + maxwarn : int + Maximum number of warnings to allow. + + Returns + ------- + str + Bash script to execute. + + """ + ref_flag = f"-r {ref_file}" if ref_file else "" + cpt_flag = f"-t {cpt_file}" if cpt_file else "" + + return f""" + set -euo pipefail # Exit on error, undefined vars, pipe failures + + cd {work_dir} + + {_get_gromacs_detect_script()} + + echo "GROMACS grompp: {mdp_file} -> {tpr_file} (using $GMX_BIN)" >&2 + + # Run grompp with explicit error checking + if ! $GMX_BIN grompp -f {mdp_file} -c {gro_file} -p {top_file} -o {tpr_file} \\ + {ref_flag} {cpt_flag} -maxwarn {maxwarn}; then + echo "ERROR: grompp failed for {tpr_file}" >&2 + echo "Check that input files ({mdp_file}, {gro_file}, {top_file}) are valid" >&2 + exit 1 + fi + + # Verify TPR was created + if [ ! -f {tpr_file} ]; then + echo "ERROR: grompp succeeded but {tpr_file} was not created" >&2 + exit 1 + fi + + echo "GROMACS grompp: SUCCESS - {tpr_file} created" >&2 + """ + + +def _build_mdrun_script( + deffnm: str, + work_dir: str, + restart_from_cpt: str = "", + ntasks: int = 0, + disable_gpu: bool = False, + pme_gpu: bool = True, + gro_out: str = "", + traj_files: "tuple[str, ...]" = (), + gmx_binary: str = "auto", +) -> str: + """Build the bash script for ``gmx mdrun``. + + Pure function — no Parsl dependency. Fully testable without a + DataFlowKernel by calling it directly with test arguments. + + All conditional logic is resolved in Python before any bash is generated. + The resulting script contains no if-clauses in the command section; only + variable assignments and a single unconditional ``mdrun`` command. The + only permitted runtime branch is the binary-detection preamble emitted + when ``gmx_binary="auto"``. + + Output validation is spec-driven via ``gro_out`` / ``traj_files``: + no hardcoded ``deffnm`` string comparisons are needed. + + Parameters + ---------- + deffnm : str + Default filename prefix (min, nvt, npt, prod). + work_dir : str + Absolute path to simulation directory. + restart_from_cpt : str, optional + Path to checkpoint file (.cpt) to resume from. When non-empty, + ``-cpi -append`` flags are added. + ntasks : int, optional + Explicit thread count override. When > 0 used directly instead + of reading ``$SLURM_CPUS_PER_TASK``. + disable_gpu : bool, optional + When ``True``, force CPU-only execution. + pme_gpu : bool, optional + When ``True`` and GPU mode is active, run PME on GPU (``-pme gpu``). + Set to ``False`` for stages whose integrator is non-dynamical (EM), + which GROMACS rejects with PME-GPU. + gro_out : str, optional + Expected output ``.gro`` file to verify after mdrun. Empty string + means no ``.gro`` check (e.g. Production stage writes trajectory). + traj_files : tuple of str, optional + Trajectory output files to verify — at least one must exist. + Non-empty only for Production: ``("prod.xtc", "prod.trr")``. + gmx_binary : str, optional + GROMACS binary selection: ``"gmx"`` (thread-MPI build), + ``"gmx_mpi"`` (pure MPI build), or ``"auto"`` (detect at runtime). + Defaults to ``"auto"`` for backward compatibility. + + Returns + ------- + str + Bash script to execute. + + """ + # Layer 1: resolve all decisions in Python before any bash is generated. + has_gpu = not disable_gpu + is_mpi = _resolve_is_mpi(gmx_binary) + nthr_expr = _resolve_thread_count_expr(ntasks) + thread_flags = _resolve_thread_flags(is_mpi, has_gpu) + gpu_flags = _resolve_gpu_flags(has_gpu, pme_gpu) + restart_flags = _resolve_restart_flags(restart_from_cpt) + binary = _resolve_binary_token(gmx_binary) + cpt_msg = f" (resuming from {restart_from_cpt})" if restart_from_cpt else "" + + # Layer 2: assemble the single unconditional mdrun command. + command = _assemble_mdrun_command(binary, deffnm, restart_flags, thread_flags, gpu_flags) + + # Layer 3 + 4: assemble script sections; join with newlines. + sections = [_build_env_preamble(work_dir, nthr_expr, has_gpu)] + if gmx_binary == "auto": + sections.append(_build_binary_detection_preamble(has_gpu)) + sections.append( + f'echo "GROMACS mdrun: Starting {deffnm} with $NTHR threads (using {binary}){cpt_msg}" >&2' + ) + sections.append(command) + output_check = _build_output_check(gro_out, traj_files) + if output_check: + sections.append(output_check) + sections.append(f'echo "GROMACS mdrun: SUCCESS - {deffnm} completed" >&2') + return "\n".join(sections) + + +def get_grompp_app(): + """Create and return the Parsl bash_app for GROMACS preprocessing. + + The ``@bash_app`` decorator is applied here (not at module level) + because it requires an active Parsl DataFlowKernel. + + The underlying bash script is built by :func:`_build_grompp_script`, a + pure function that can be tested independently of Parsl. + + Returns + ------- + callable + A Parsl ``@bash_app`` wrapping gmx grompp. + + Raises + ------ + ImportError + If parsl is not installed. + + """ + bash_app = _require_bash_app() + + @bash_app + def run_grompp( + mdp_file: str, + gro_file: str, + top_file: str, + tpr_file: str, + work_dir: str, + ref_file: str = "", + cpt_file: str = "", + maxwarn: int = 1, + stdout=None, + stderr=None, + inputs=None, + ): + """Generate GROMACS .tpr file via grompp. + + Parameters + ---------- + mdp_file : str + MDP parameter file (em.mdp, nvt.mdp, npt.mdp, md.mdp). + gro_file : str + Input structure file (system.pdb, min.gro, etc). + top_file : str + Topology file (topology.top). + tpr_file : str + Output TPR file (min.tpr, nvt.tpr, etc). + work_dir : str + Absolute path to simulation directory. + ref_file : str, optional + Reference structure for position restraints (-r flag). + cpt_file : str, optional + Checkpoint file for velocities (-t flag). + maxwarn : int + Maximum number of warnings to allow. + stdout : str, optional + File path for stdout. + stderr : str, optional + File path for stderr. + inputs : list, optional + Parsl input dependencies. + + Returns + ------- + str + Bash script to execute. + + """ + return _build_grompp_script( + mdp_file, gro_file, top_file, tpr_file, work_dir, ref_file, cpt_file, maxwarn + ) + + return run_grompp + + +def get_mdrun_app(): + """Create and return the Parsl bash_app for GROMACS simulation. + + The ``@bash_app`` decorator is applied here (not at module level) + because it requires an active Parsl DataFlowKernel. + + The underlying bash script is built by :func:`_build_mdrun_script`, a + pure function that can be tested independently of Parsl. Output + validation is spec-driven via ``gro_out`` / ``traj_files`` — no + hardcoded ``deffnm`` string comparisons in the generated bash. + + Returns + ------- + callable + A Parsl ``@bash_app`` wrapping gmx mdrun. + + Raises + ------ + ImportError + If parsl is not installed. + + """ + bash_app = _require_bash_app() + + @bash_app + def run_mdrun( + deffnm: str, + work_dir: str, + restart_from_cpt: str = "", + ntasks: int = 0, + disable_gpu: bool = False, + pme_gpu: bool = True, + gro_out: str = "", + traj_files: "tuple[str, ...]" = (), + gmx_binary: str = "auto", + stdout=None, + stderr=None, + inputs=None, + ): + """Run GROMACS simulation with resolved resources. + + All conditional logic (binary selection, GPU mode, thread count, restart + flags) is resolved in Python by :func:`_build_mdrun_script` before any + bash is generated. The resulting bash script contains a single + unconditional ``mdrun`` command. + + Output validation is spec-driven: pass ``gro_out`` for stages that + write a ``.gro`` file, or ``traj_files`` for Production (which writes + ``.xtc``/``.trr``). + + Resource resolution priority: + - Thread count: ``ntasks`` arg (if >0) > SLURM_CPUS_PER_TASK > + OMP_NUM_THREADS > nproc + - GPU: active when ``disable_gpu=False`` (GPU mode decided at submission + time, not at runtime). + - Binary: ``gmx_binary`` selects the executable; ``"auto"`` enables + runtime detection via a single isolated if-block. + - PME-on-GPU: only when ``pme_gpu=True`` AND GPU mode is active. + Must be ``False`` for non-dynamical integrators (e.g. EM/steep). + + Parameters + ---------- + deffnm : str + Default filename prefix (min, nvt, npt, prod). + work_dir : str + Absolute path to simulation directory. + restart_from_cpt : str, optional + Path to checkpoint file (.cpt) to resume from. + ntasks : int, optional + Explicit thread count override. When > 0, overrides auto-detect. + disable_gpu : bool, optional + Force CPU-only execution. + pme_gpu : bool, optional + Run PME on GPU when GPU mode is active. Set to ``False`` for EM. + gro_out : str, optional + Expected output ``.gro`` file to verify after mdrun. + traj_files : tuple of str, optional + Trajectory output files to verify (at least one must exist). + gmx_binary : str, optional + GROMACS binary: ``"gmx"``, ``"gmx_mpi"``, or ``"auto"``. + stdout : str, optional + File path for stdout. + stderr : str, optional + File path for stderr. + inputs : list, optional + Parsl input dependencies. + + Returns + ------- + str + Bash script to execute. + + """ + return _build_mdrun_script( + deffnm, + work_dir, + restart_from_cpt, + ntasks, + disable_gpu, + pme_gpu, + gro_out, + traj_files, + gmx_binary, + ) + + return run_mdrun diff --git a/mdfactory/orchestration/build.py b/mdfactory/orchestration/build.py new file mode 100644 index 0000000..16f5b10 --- /dev/null +++ b/mdfactory/orchestration/build.py @@ -0,0 +1,353 @@ +# ABOUTME: Build orchestration dispatch via Parsl +# ABOUTME: Submits parallel builds and provides dry-run preview +"""Build orchestration via Parsl.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from loguru import logger + +from mdfactory.models.input import BuildInput + +from .apps import get_build_app +from .session import ( + _get_slurm_job_ids, + _scancel_jobs, + _shutdown_parsl, + parsl_session, +) + +if TYPE_CHECKING: + from .config import ExecutorConfig + +# Re-exported for backward compatibility — the canonical home is ``session``. +__all__ = [ + "build_systems", + "parsl_session", + "_shutdown_parsl", + "_get_slurm_job_ids", + "_scancel_jobs", +] + + +def build_systems( + build_inputs: list, + config: "ExecutorConfig", + *, + output_dir: Path | None = None, + wait: bool = True, + dry_run: bool = False, +) -> list: + """Submit parallel builds via Parsl (or preview with dry_run). + + Parameters + ---------- + build_inputs : list + List of BuildInput models or dicts. + config : ExecutorConfig + Executor configuration for Parsl. + output_dir : Path, optional + Base output directory for builds. Defaults to current directory. + wait : bool, optional + Whether to wait for all futures to complete. Default True. + If False, returns raw AppFutures and the caller is responsible + for calling ``parsl.clear()`` after all futures complete. + dry_run : bool, optional + If True, log what would be built and return descriptions without + loading Parsl or submitting any work. Default False. + + Returns + ------- + list + If dry_run=True, list of description dicts. + If wait=True, list of result dicts. + If wait=False, list of AppFutures. + + """ + output_dir = Path(output_dir) if output_dir else Path.cwd() + + # Resolve all inputs upfront + resolved: list[tuple[BuildInput, dict]] = [] + for inp in build_inputs: + if isinstance(inp, BuildInput): + model = inp + input_dict = inp.model_dump() + elif isinstance(inp, dict): + model = BuildInput(**inp) + input_dict = inp.copy() + else: + raise TypeError(f"Expected BuildInput or dict, got {type(inp)}") + input_dict["_build_dir"] = str(output_dir / model.hash) + resolved.append((model, input_dict)) + + # Dry-run: log plan and return without loading Parsl + if dry_run: + descriptions = [] + for model, _ in resolved: + desc = { + "hash": model.hash, + "simulation_type": model.simulation_type, + "parametrization": model.parametrization, + "engine": model.engine, + "output_directory": str(output_dir / model.hash), + } + descriptions.append(desc) + logger.info( + f"[dry-run] {model.hash} | {model.simulation_type} | " + f"{model.parametrization} | {model.engine} -> {output_dir / model.hash}" + ) + logger.info(f"[dry-run] {len(descriptions)} system(s) would be built") + logger.info(f"[dry-run] Provider: {config.provider}") + return descriptions + + # parsl_session owns the full DFK lifecycle: guard, load, and shutdown + # (including scancel of lingering SLURM jobs) on exit. + with parsl_session(config) as session: + build_app = get_build_app() + + # Submit all builds + futures = [] + input_hashes = [] + for model, input_dict in resolved: + input_hashes.append(model.hash) + futures.append(build_app(input_dict)) + + logger.info(f"Submitted {len(futures)} build(s) to Parsl") + + if not wait: + logger.warning("Returning raw futures — caller must call parsl.clear() when done.") + session.detach() + return futures + + # Poll futures with live status reporting + return _wait_with_progress(futures, hashes=input_hashes, label="Parsl Builds") + + +from .errors import _describe_failure # noqa: E402 + + +def _collect_results(results: list, hashes: list[str]) -> list[dict]: + """Return all captured results, raising if any slot is still uncaptured. + + The polling loop only exits once every future has been recorded, so a + ``None`` slot here signals an internal bug rather than a normal outcome. + Failing explicitly is safer than silently returning a shorter list than + the caller submitted (which would corrupt any ``len()``-based bookkeeping). + + Parameters + ---------- + results : list + Per-future result slots; ``None`` marks an uncaptured future. + hashes : list[str] + Display hashes aligned with ``results``, used for the error message. + + Returns + ------- + list[dict] + The complete list of result dicts. + + Raises + ------ + RuntimeError + If one or more result slots were never captured. + + """ + missing = [hashes[i] for i, r in enumerate(results) if r is None] + if missing: + raise RuntimeError( + f"{len(missing)} build result(s) were never captured " + f"({', '.join(h[:12] for h in missing)}); the polling loop exited " + "prematurely. This is an internal error." + ) + return list(results) + + +from .progress import _get_block_status # noqa: E402 + + +def _is_running(future) -> bool: + """Return ``True`` when a Parsl future is actively executing. + + Parameters + ---------- + future : AppFuture + Parsl future to inspect. + + Returns + ------- + bool + + """ + try: + return future.task_status() in ("launched", "running", "running_ended") + except Exception: + return False + + +def _poll_future(future, display_hash: str, result_transform) -> "tuple[dict, object]": + """Collect the result (or error dict) from a completed future. + + Separates per-future result extraction from the polling loop, making + both independently testable. + + Parameters + ---------- + future : AppFuture + A *completed* Parsl future (caller must check ``future.done()``). + display_hash : str + Short identifier used in the activity-log line. + result_transform : callable or None + ``(raw_result, display_hash) -> dict`` applied on success. + + Returns + ------- + result : dict + Result dict (success or failure). + line : rich.text.Text + Activity-log line to append to the live display. + + """ + from rich.text import Text + + try: + raw = future.result() + result = result_transform(raw, display_hash) if result_transform is not None else raw + line = Text() + line.append(" ✓ ", style="bold green") + line.append(display_hash[:12], style="cyan") + line.append(" done", style="dim") + return result, line + except Exception as exc: + failure_type, error_detail = _describe_failure(exc) + result = { + "hash": display_hash, + "status": "failed", + "error": error_detail, + "failure_type": failure_type, + "error_detail": error_detail, + } + line = Text() + line.append(" ✗ ", style="bold red") + line.append(display_hash[:12], style="cyan") + line.append(f" {error_detail}", style="red") + return result, line + + +def _wait_with_progress( + futures: list, + *, + hashes: list[str] | None = None, + label: str = "Parsl Builds", + result_transform=None, + poll_interval: float = 2.0, +) -> list[dict]: + """Wait for futures with a live terminal progress display. + + Shows a progress bar with summary counts and a scrolling activity log + of recent completions/failures. Scales to any number of builds. + + Per-future result handling is delegated to :func:`_poll_future`; SLURM + block status querying to :func:`_get_block_status`. + + Parameters + ---------- + futures : list + List of Parsl AppFutures. + hashes : list[str], optional + Known hashes for each build (displayed in activity log). + label : str + Heading shown next to the progress bar. + result_transform : callable or None, optional + ``(raw_result, display_hash) -> dict`` applied to each successful + future's return value before it is stored. + poll_interval : float + Seconds between status polls. + + Returns + ------- + list[dict] + Result dicts for each future. + + """ + import time + + from rich.console import Console, Group + from rich.live import Live + from rich.progress import BarColumn, MofNCompleteColumn, Progress, TextColumn + from rich.text import Text + + total = len(futures) + results: list[dict | None] = [None] * total + display_hashes = hashes or [f"build-{i}" for i in range(total)] + console = Console() + + succeeded = 0 + failed = 0 + max_activity = 12 + activity: list[Text] = [] + + progress = Progress( + TextColumn(f"⚒ [bold]{label}[/]"), + BarColumn(bar_width=40), + MofNCompleteColumn(), + TextColumn("·"), + TextColumn("[green]{task.fields[succeeded]} ✓[/]"), + TextColumn("[red]{task.fields[failed]} ✗[/]"), + TextColumn("[yellow]{task.fields[running]} ●[/]"), + console=console, + transient=False, + ) + task_id = progress.add_task("builds", total=total, succeeded=0, failed=0, running=0) + + def _render(): + done_count = succeeded + failed + running = sum(1 for i, f in enumerate(futures) if results[i] is None and _is_running(f)) + progress.update( + task_id, + completed=done_count, + succeeded=succeeded, + failed=failed, + running=running, + ) + parts = [progress] + block_info = _get_block_status() + if block_info: + parts.append(Text.from_markup(f" ▸ SLURM: {block_info}")) + if activity: + parts.append(Text("")) + for line in activity[-max_activity:]: + parts.append(line) + return Group(*parts) + + try: + with Live(_render(), console=console, refresh_per_second=2) as live: + while True: + done_count = 0 + for i, future in enumerate(futures): + if results[i] is not None: + done_count += 1 + continue + if future.done(): + result, line = _poll_future(future, display_hashes[i], result_transform) + results[i] = result + activity.append(line) + if result.get("status") == "failed": + failed += 1 + else: + succeeded += 1 + done_count += 1 + + live.update(_render()) + + if done_count == total: + break + + time.sleep(poll_interval) + + except KeyboardInterrupt: + console.print("\n[bold yellow]Interrupted[/]") + raise + + return _collect_results(results, display_hashes) diff --git a/mdfactory/orchestration/config.py b/mdfactory/orchestration/config.py new file mode 100644 index 0000000..59087bc --- /dev/null +++ b/mdfactory/orchestration/config.py @@ -0,0 +1,444 @@ +# ABOUTME: Pydantic models for Parsl executor configuration +# ABOUTME: Supports local and SLURM providers with YAML serialization +"""Executor configuration models for Parsl workflows.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING, Any, Literal + +import yaml +from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator + +from mdfactory.orchestration.environment import EnvironmentConfig +from mdfactory.performance.slurm_config import BaseSlurmConfig + +if TYPE_CHECKING: + import parsl + + +def _import_parsl(): + """Import parsl with a clear error message if not installed. + + Returns + ------- + module + The parsl module. + + Raises + ------ + ImportError + If parsl is not installed. + + """ + try: + import parsl # type: ignore[import-not-found] + except ImportError as exc: + raise ImportError( + "parsl is required for build orchestration. " + "Install with `pip install 'mdfactory[parsl]'`." + ) from exc + return parsl + + +class ExecutorConfig(BaseModel): + """Base executor configuration for Parsl workflows. + + Parameters + ---------- + provider : str + Execution provider type ("local" or "slurm"). + environment : EnvironmentConfig + Structured execution-environment configuration. Defines module + loads, Python environment activation, and extra shell init for + compute workers. + working_directory : Path or None + Working directory for the executor. + max_workers_per_node : int + Maximum number of parallel tasks per compute node. + max_blocks : int + Maximum number of execution blocks (for local: process groups). + available_accelerators : int or list[str] + GPU pinning for Parsl workers. An integer count (or explicit list of + device IDs) makes Parsl assign each worker a distinct accelerator and + set ``CUDA_VISIBLE_DEVICES`` accordingly. Defaults to ``0`` (no pinning, + all workers share the node's uncontrolled GPU context). Set this when + running ``max_workers_per_node > 1`` on GPU nodes to avoid silent + wrong-GPU contention. + run_dir : Path + Directory where Parsl writes its ``runinfo`` logs and database. + Defaults to ``~/.parsl/mdfactory`` so logs land in a predictable, + controllable location instead of scattering ``runinfo/`` into the + current working directory (typically the login-node home on HPC). + retries : int + Number of times Parsl re-runs a failed app before propagating the + exception. Defaults to 3 to handle transient SLURM / network faults. + Set to 0 to disable retries. + gmx_binary : str + GROMACS binary selection for mdrun: ``"gmx"`` (thread-MPI build), + ``"gmx_mpi"`` (pure MPI build), or ``"auto"`` (detect at runtime, + default). Setting an explicit value eliminates all runtime if-blocks + from the generated bash script. Can be overridden per-stage via + ``stage_overrides`` on :class:`SlurmExecutorConfig`. + + """ + + provider: Literal["local", "slurm"] = "local" + environment: EnvironmentConfig = Field(default_factory=EnvironmentConfig) + working_directory: Path | None = None + max_workers_per_node: int = 1 + max_blocks: int = 1 + available_accelerators: int | list[str] = 0 + run_dir: Path = Field(default_factory=lambda: Path("~/.parsl/mdfactory").expanduser()) + retries: int = Field(default=3, ge=0, description="Number of times Parsl retries a failed app.") + gmx_binary: Literal["auto", "gmx", "gmx_mpi"] = Field( + default="auto", + description=( + "GROMACS binary selection for mdrun. " + "``'gmx'`` — thread-MPI build; ``'gmx_mpi'`` — pure MPI build; " + "``'auto'`` — detect at runtime (default, backward compatible). " + "Setting an explicit value eliminates all runtime if-blocks from " + "the generated bash script." + ), + ) + max_rescue: int = Field( + default=3, + ge=0, + description=( + "Maximum rescue tiers for physics failures (overlapping atoms, " + "exploding box, LINCS constraint errors) in EM/NVT/NPT stages. " + "Each tier halves the step size and doubles nsteps. " + "Set to 0 to disable rescue retry." + ), + ) + + @field_validator("run_dir", mode="before") + @classmethod + def _expand_run_dir(cls, v: Any) -> Any: + """Expand ``~`` in user-supplied run_dir values.""" + return Path(v).expanduser() if v is not None else v + + @field_serializer("run_dir", "working_directory") + def _serialize_paths(self, v: Path | None) -> str | None: + """Serialize Path fields as plain strings for YAML compatibility.""" + return str(v) if v is not None else None + + def to_parsl_config(self) -> "parsl.Config": + """Build a Parsl Config with HighThroughputExecutor + LocalProvider. + + Returns + ------- + parsl.Config + Configured Parsl Config object. + + """ + parsl = _import_parsl() + from parsl.executors import HighThroughputExecutor + from parsl.providers import LocalProvider + + provider = LocalProvider( + worker_init=self.environment.compose_worker_init(), + min_blocks=0, + init_blocks=1, + max_blocks=self.max_blocks, + parallelism=1, + ) + executor = HighThroughputExecutor( + label="local", + provider=provider, + max_workers_per_node=self.max_workers_per_node, + available_accelerators=self.available_accelerators, + working_dir=str(self.working_directory) if self.working_directory else None, + ) + return parsl.Config( + executors=[executor], + run_dir=str(self.run_dir), + retries=self.retries, + ) + + @classmethod + def from_yaml(cls, path: Path) -> "ExecutorConfig | SlurmExecutorConfig": + """Load executor configuration from a YAML file. + + If the YAML does not contain an ``environment:`` section, the + global environment config (from ``mdfactory config environment``) + is loaded automatically. This allows environment setup to be + configured once per machine and reused across SLURM configs. + + Parameters + ---------- + path : Path + Path to the YAML configuration file. + + Returns + ------- + ExecutorConfig or SlurmExecutorConfig + The appropriate config model based on the ``provider`` field. + + """ + with open(path) as f: + data = yaml.safe_load(f) + if not isinstance(data, dict): + raise ValueError( + f"Executor config YAML is empty or invalid (expected a mapping): {path}" + ) + + # Auto-load global environment when YAML has no environment section + if "environment" not in data: + global_env = EnvironmentConfig.load_global() + if global_env is not None: + data["environment"] = global_env.model_dump(exclude_none=True) + + provider = data.get("provider", "local") + if provider == "slurm": + return SlurmExecutorConfig(**data) + return cls(**data) + + +#: Fields in :class:`SlurmExecutorConfig` that ``stage_overrides`` can +#: legitimately change. These are the mdrun-command-line knobs extracted by +#: :func:`~mdfactory.orchestration.stages._extract_resource_hints`. All other +#: fields (``walltime``, ``mem``, ``nodes``, ``partition``, …) are baked into +#: the Parsl allocation at session start and cannot vary per-stage without +#: spawning separate executor instances. +_HONORED_OVERRIDE_KEYS: frozenset[str] = frozenset({"cpus_per_node", "gres", "gmx_binary"}) + + +class SlurmExecutorConfig(ExecutorConfig, BaseSlurmConfig): + """SLURM executor configuration for Parsl workflows. + + Inherits the four cross-cutting SLURM fields (``account``, ``partition``, + ``qos``, ``constraint``) and the authoritative ``from_cluster()`` factory + from :class:`~mdfactory.performance.slurm_config.BaseSlurmConfig`, and the + Parsl executor fields (``run_dir``, ``available_accelerators``, …) from + :class:`ExecutorConfig`. Only Parsl/SLURM-job-specific fields are declared + here. + + Field names mirror sbatch flags where possible. The only Parsl-specific + field is ``max_workers_per_node`` (number of parallel tasks per node). + + Notes + ----- + ``BaseSlurmConfig`` is frozen (immutable), but executor configs are mutated + in places (e.g. the TUI wizard), so this subclass explicitly sets + ``frozen=False`` to match :class:`ExecutorConfig`'s mutable behaviour. + + Parameters + ---------- + account : str + SLURM account (``--account``). + partition : str + SLURM partition (``--partition``). + walltime : str + Wall-clock time limit (``--time``). + nodes : int + Number of nodes per SLURM job (``--nodes``). + cpus_per_node : int + CPUs requested per node. Wired to Parsl ``SlurmProvider(cores_per_node=...)``, + which Parsl uses to size the worker pool (roughly ``--ntasks`` / + cores-per-node) — it is **not** the sbatch ``--cpus-per-task`` flag. For + MPI+OpenMP workloads (e.g. GROMACS), OpenMP threads-per-rank must be set + separately via ``scheduler_options`` / ``environment.extra_init``. + gres : str or None + Generic resource specification (``--gres``), e.g. ``"gpu:l40s:1"``. + mem : str or None + Memory per node (``--mem``), e.g. ``"32G"``. + qos : str or None + Quality of service (``--qos``). + constraint : str or None + Node feature constraint (``--constraint``). + max_blocks : int + Maximum number of simultaneous SLURM jobs. Each block is one + SLURM job; set this to control how many run in parallel. + scheduler_options : str + Additional raw ``#SBATCH`` lines for anything not covered above + (allocation-level flags injected into the job script). + launch_options : str + Extra ``srun`` flags forwarded to Parsl's ``SrunLauncher(overrides=...)`` + for task-placement / binding control (e.g. + ``"--cpu-bind=cores --distribution=block:block"`` for NUMA-local CPU + binding in MPI+OpenMP workers). Empty string leaves Parsl's default + launcher untouched. + + Examples + -------- + .. code-block:: yaml + + provider: slurm + account: hpc_chem + partition: gpu + walltime: "4:00:00" + nodes: 1 + cpus_per_node: 12 + gres: "gpu:l40s:1" + max_blocks: 5 + max_workers_per_node: 1 + environment: + modules: + - gromacs/2024.3-gpu + pixi_manifest: /path/to/project + + """ + + model_config = ConfigDict(frozen=False) + + provider: Literal["slurm"] = "slurm" + # account, partition, qos, constraint inherited from BaseSlurmConfig. + # from_cluster() inherited from BaseSlurmConfig — extra fields below are + # forwarded to the constructor via resolve_slurm_fields(). + walltime: str = Field(default="2h", validate_default=True) + nodes: int = 1 + cpus_per_node: int = 12 + gres: str | None = None + mem: str | None = None + scheduler_options: str = "" + launch_options: str = "" + stage_overrides: dict[str, dict] = Field( + default_factory=dict, + description=( + "Per-stage resource overrides. Keys are stage names " + "(``'EM'``, ``'NVT'``, ``'NPT'``, ``'Production'``); values are " + "dicts of :class:`SlurmExecutorConfig` field overrides, e.g. " + "``{'EM': {'cpus_per_node': 8, 'gres': None}}``." + ), + ) + + @field_validator("walltime", mode="before") + @classmethod + def _normalize_walltime(cls, v: str) -> str: + """Normalize walltime string on construction.""" + from mdfactory.performance.slurm_config import normalize_slurm_time + + return normalize_slurm_time(v) + + def get_stage_config(self, stage: str) -> "SlurmExecutorConfig": + """Return a copy of this config with per-stage overrides applied. + + Looks up ``stage`` in :attr:`stage_overrides` and returns a validated + copy of *self* with the matching fields merged in. If no override + exists for *stage*, *self* is returned unchanged (no copy). + + Only the fields listed in :data:`_HONORED_OVERRIDE_KEYS` — + ``cpus_per_node``, ``gres``, and ``gmx_binary`` — are honoured at the + mdrun-command-line level. All other fields (``walltime``, ``mem``, + ``nodes``, ``partition``, …) are baked into the Parsl allocation at + session-start time and **cannot** vary per-stage without separate + executor instances. Passing an unhonorable key raises :exc:`ValueError` + immediately so the misconfiguration is caught at CLI invocation rather + than silently at sbatch time. + + Parameters + ---------- + stage : str + Stage name, e.g. ``'EM'``, ``'NVT'``, ``'NPT'``, ``'Production'``. + + Returns + ------- + SlurmExecutorConfig + Config with stage-specific field values applied and validated. + + Raises + ------ + ValueError + If any override key is not in :data:`_HONORED_OVERRIDE_KEYS`. + + Examples + -------- + >>> cfg = SlurmExecutorConfig(cpus_per_node=12, gres="gpu:1", + ... stage_overrides={"EM": {"cpus_per_node": 4, "gres": None}}) + >>> em_cfg = cfg.get_stage_config("EM") + >>> em_cfg.cpus_per_node + 4 + >>> em_cfg.gres is None + True + >>> prod_cfg = cfg.get_stage_config("Production") + >>> prod_cfg.cpus_per_node # unchanged + 12 + + """ + overrides = self.stage_overrides.get(stage, {}) + if not overrides: + return self + + ignored = set(overrides) - _HONORED_OVERRIDE_KEYS + if ignored: + raise ValueError( + f"stage_overrides[{stage!r}] contains keys that are not honored " + f"at the mdrun-command level: {sorted(ignored)}.\n" + f"Only {sorted(_HONORED_OVERRIDE_KEYS)} affect per-stage mdrun execution.\n" + f"To change walltime / mem / nodes / partition, update the top-level config " + f"fields (they apply to every stage via the Parsl allocation)." + ) + + # Use model_validate instead of model_copy(update=...) so that field-level + # Pydantic validators run on the merged dict (typos fail fast). + return type(self).model_validate({**self.model_dump(), **overrides}) + + def to_parsl_config(self) -> "parsl.Config": + """Build a Parsl Config with HighThroughputExecutor + SlurmProvider. + + Returns + ------- + parsl.Config + Configured Parsl Config object. + + """ + parsl = _import_parsl() + from parsl.executors import HighThroughputExecutor + from parsl.providers import SlurmProvider + + # Build scheduler_options from structured fields + raw extras + opts = [] + if self.gres: + opts.append(f"#SBATCH --gres={self.gres}") + if self.mem: + opts.append(f"#SBATCH --mem={self.mem}") + if self.qos: + opts.append(f"#SBATCH --qos={self.qos}") + if self.constraint: + opts.append(f"#SBATCH --constraint={self.constraint}") + if self.scheduler_options: + opts.append(self.scheduler_options) + scheduler_options = "\n".join(opts) + + # Only override Parsl's default launcher when srun-level flags are given, + # so NUMA/task-placement binding is opt-in (relevant for MPI+OpenMP work). + provider_kwargs: dict[str, Any] = {} + if self.launch_options: + from parsl.launchers import SrunLauncher + + provider_kwargs["launcher"] = SrunLauncher(overrides=self.launch_options) + + provider = SlurmProvider( + account=self.account, + partition=self.partition, + walltime=self.walltime, + nodes_per_block=self.nodes, + cores_per_node=self.cpus_per_node, + worker_init=self.environment.compose_worker_init(), + scheduler_options=scheduler_options, + min_blocks=0, + init_blocks=1, + max_blocks=self.max_blocks, + parallelism=1, + **provider_kwargs, + ) + executor = HighThroughputExecutor( + label="slurm", + provider=provider, + max_workers_per_node=self.max_workers_per_node, + available_accelerators=self.available_accelerators, + working_dir=str(self.working_directory) if self.working_directory else None, + ) + return parsl.Config( + executors=[executor], + run_dir=str(self.run_dir), + retries=self.retries, + ) + + +def get_stage_config_or_none(config: "ExecutorConfig | None", stage: str): + """Return per-stage config if the config supports it, else None.""" + if config is not None and hasattr(config, "get_stage_config"): + return config.get_stage_config(stage) + return None diff --git a/mdfactory/orchestration/environment.py b/mdfactory/orchestration/environment.py new file mode 100644 index 0000000..32d30f5 --- /dev/null +++ b/mdfactory/orchestration/environment.py @@ -0,0 +1,269 @@ +# ABOUTME: Structured execution-environment configuration for compute workers +# ABOUTME: Replaces the opaque worker_init string with typed fields and auto-detection +"""Execution-environment configuration for Parsl compute workers. + +Defines :class:`EnvironmentConfig`, a structured replacement for the legacy +``worker_init`` flat string on :class:`~mdfactory.orchestration.config.ExecutorConfig`. +Environment concerns (module loads, pixi/conda activation, MPS lifecycle) +are separated from SLURM allocation and per-stage tuning. +""" + +from __future__ import annotations + +import os +from pathlib import Path +from typing import Any + +import yaml +from loguru import logger +from pydantic import BaseModel, field_serializer + + +class EnvironmentConfig(BaseModel): + """Compute-node environment setup — what makes workers capable of running tasks. + + This model captures the structured fields that compose the ``worker_init`` + shell snippet passed to Parsl providers. Instead of hand-editing a single + opaque string, users specify modules, activation commands, and custom + init separately. + + Parameters + ---------- + modules : list[str] + Environment modules to load (e.g. ``["gromacs/2024.3-gpu"]``). + Each entry becomes a ``module load `` command. + pixi_manifest : Path or None + Path to a pixi project root (containing ``pixi.toml``). + When set, a ``pixi shell-hook`` activation command is emitted. + Auto-detected by :meth:`detect` from the filesystem or + ``PIXI_PROJECT_MANIFEST`` environment variable. + conda_env : str or None + Conda environment name to activate. Ignored when + ``pixi_manifest`` is set (pixi takes precedence). + venv_path : Path or None + Path to a Python virtualenv. Ignored when ``pixi_manifest`` + or ``conda_env`` is set. + extra_init : str + Additional shell commands appended after all structured fields. + This is the escape hatch for anything not covered by the typed + fields (e.g. custom ``export`` statements, ``ulimit`` settings). + + Examples + -------- + >>> env = EnvironmentConfig( + ... modules=["gromacs/2024.3-gpu"], + ... pixi_manifest=Path("/project"), + ... ) + >>> env.compose_worker_init() + 'module load gromacs/2024.3-gpu; eval "$(pixi shell-hook --manifest-path /project -e default)"' + + """ + + modules: list[str] = [] + pixi_manifest: Path | None = None + conda_env: str | None = None + venv_path: Path | None = None + extra_init: str = "" + + @field_serializer("pixi_manifest", "venv_path") + def _serialize_paths(self, v: Path | None) -> str | None: + """Serialize Path fields as plain strings for YAML compatibility.""" + return str(v) if v is not None else None + + def compose_worker_init(self) -> str: + """Build the shell snippet from structured fields. + + Produces a semicolon-separated string of shell commands suitable + for Parsl's ``worker_init`` parameter. The order is: + + 1. ``module load`` commands (one per entry in :attr:`modules`) + 2. Environment activation (pixi > conda > venv, first match wins) + 3. :attr:`extra_init` (appended verbatim) + + Returns + ------- + str + Shell snippet, or ``""`` if all fields are empty/default. + """ + parts: list[str] = [] + + for mod in self.modules: + parts.append(f"module load {mod}") + + if self.pixi_manifest: + parts.append( + f'eval "$(pixi shell-hook --manifest-path {self.pixi_manifest} -e default)"' + ) + elif self.conda_env: + parts.append(f"conda activate {self.conda_env}") + elif self.venv_path: + parts.append(f"source {self.venv_path}/bin/activate") + + if self.extra_init: + parts.append(self.extra_init) + + return "; ".join(parts) + + @classmethod + def detect(cls, **overrides: Any) -> EnvironmentConfig: + """Auto-detect environment from the current shell context. + + Detection priority for Python environment activation: + + 1. **pixi** — checks ``PIXI_PROJECT_MANIFEST`` env var, then + falls back to the ``mdfactory`` package's own project root + (looks for ``.pixi/envs/default``). + 2. **conda** — checks ``CONDA_PREFIX`` env var. + 3. **venv** — checks ``VIRTUAL_ENV`` env var. + + Only the first match is used; the rest are ignored. + + GROMACS modules are **not** auto-detected here — that requires + subprocess calls to ``module avail`` which belong in the TUI + layer (see :func:`~mdfactory.orchestration.tui._detect_gromacs_modules`). + + Parameters + ---------- + **overrides + Keyword arguments forwarded to the constructor, overriding + any auto-detected values. + + Returns + ------- + EnvironmentConfig + Environment config with detected fields populated. + """ + kwargs: dict[str, Any] = {} + + # --- pixi detection --- + pixi_manifest_env = os.environ.get("PIXI_PROJECT_MANIFEST") + if pixi_manifest_env: + # PIXI_PROJECT_MANIFEST points to the manifest file itself + manifest_path = Path(pixi_manifest_env) + kwargs["pixi_manifest"] = manifest_path.parent + else: + # Fall back to mdfactory's own project root + try: + import mdfactory as _mdf + + project_root = Path(_mdf.__file__).parent.parent + if (project_root / ".pixi" / "envs" / "default").exists(): + kwargs["pixi_manifest"] = project_root + except Exception: + pass + + # --- conda detection (only if pixi not found) --- + if "pixi_manifest" not in kwargs: + conda_prefix = os.environ.get("CONDA_PREFIX") + if conda_prefix: + # Extract env name from the prefix path + conda_path = Path(conda_prefix) + kwargs["conda_env"] = conda_path.name + + # --- venv detection (only if pixi and conda not found) --- + if "pixi_manifest" not in kwargs and "conda_env" not in kwargs: + venv_path = os.environ.get("VIRTUAL_ENV") + if venv_path: + kwargs["venv_path"] = Path(venv_path) + + # Apply overrides last + kwargs.update(overrides) + return cls(**kwargs) + + # ------------------------------------------------------------------ + # Persistence + # ------------------------------------------------------------------ + + def save_yaml(self, path: Path) -> None: + """Write this environment config to a YAML file. + + Empty/default fields are omitted for cleaner output. + + Parameters + ---------- + path : Path + Destination file path. Parent directories are created if + needed. + """ + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + + data = self.model_dump(exclude_none=True) + if not data.get("modules"): + data.pop("modules", None) + if not data.get("extra_init"): + data.pop("extra_init", None) + + with open(path, "w") as fh: + yaml.dump(data, fh, default_flow_style=False, sort_keys=False) + + @classmethod + def from_yaml(cls, path: Path) -> EnvironmentConfig: + """Load environment config from a YAML file. + + Parameters + ---------- + path : Path + Path to a YAML file with environment fields. + + Returns + ------- + EnvironmentConfig + Parsed environment configuration. + + Raises + ------ + FileNotFoundError + If *path* does not exist. + ValueError + If the file is empty or not a YAML mapping. + """ + path = Path(path) + with open(path) as fh: + data = yaml.safe_load(fh) + if not isinstance(data, dict): + raise ValueError( + f"Environment config YAML is empty or invalid (expected a mapping): {path}" + ) + return cls(**data) + + @classmethod + def load_global(cls) -> EnvironmentConfig | None: + """Load the global environment config, if it exists. + + The global config lives at + ``/environment.yaml`` (see + :func:`get_global_environment_path`). + + Returns + ------- + EnvironmentConfig or None + The loaded config, or ``None`` if the file does not exist. + + Raises + ------ + ValueError + If the file exists but cannot be parsed. This fails fast + so a corrupt config is caught immediately rather than + silently submitting jobs with no environment. + """ + path = get_global_environment_path() + if not path.is_file(): + return None + env = cls.from_yaml(path) + logger.debug("Loaded global environment config from %s", path) + return env + + +def get_global_environment_path() -> Path: + """Return the path to the global environment config file. + + Returns + ------- + Path + ``/environment.yaml``, where the config dir + is determined by :func:`~mdfactory.settings.get_config_dir`. + """ + from mdfactory.settings import get_config_dir + + return get_config_dir() / "environment.yaml" diff --git a/mdfactory/orchestration/errors.py b/mdfactory/orchestration/errors.py new file mode 100644 index 0000000..078df0a --- /dev/null +++ b/mdfactory/orchestration/errors.py @@ -0,0 +1,233 @@ +# ABOUTME: GROMACS error classification for rescue retry decisions +# ABOUTME: Distinguishes physics failures (rescuable) from infrastructure failures +"""GROMACS error classification for adaptive rescue retry. + +Classifies simulation failures as physics-driven (rescuable via parameter +adjustment) or infrastructure-driven (handled by Parsl's built-in retry). +""" + +from __future__ import annotations + +import re +from enum import Enum +from pathlib import Path + +from loguru import logger + +#: Regex patterns matching GROMACS physics-failure messages in stderr/log. +#: Each pattern is compiled once at import time for performance. +_PHYSICS_PATTERNS: list[re.Pattern] = [ + re.compile(p, re.IGNORECASE) + for p in [ + r"LINCS WARNING", + r"the coordinate constraints could not be satisfied", + r"Particle .+ moved more than", + r"The domain decomposition cell size", + r"Fatal error.*blowing up", + r"step \d+.*: Water molecule starting at", + r"Too many LINCS warnings", + r"can not continue", + r"can not be settled", + r"force .+ is not finite", + r"Abnorm(al termination|al end| of program)", + r"There are \d+ perturbed non-bonded pair interactions", + r"atoms .+ are involved in more than \d+ constraints", + ] +] + + +def _unwrap_parsl_exception(exc: BaseException) -> BaseException: + """Unwrap Parsl exception wrappers to find the root cause. + + Parsl surfaces worker errors differently depending on the failure mode: + + 1. **DependencyError** (downstream task failed because an upstream + dependency failed): Parsl sets ``exc.__cause__`` to the deepest + root-cause exception via ``_find_any_root_cause()``. + 2. **Legacy RemoteExceptionWrapper**: stores the original exception + on ``exc.e_value``. + 3. **Modern Parsl** (direct re-raise): the exception is already the + original — return as-is. + + Uses duck-typing (``__cause__``, ``e_value``) so no Parsl import is + needed, and works with any future exception type that follows these + conventions. + + Parameters + ---------- + exc : BaseException + The exception raised by ``future.result()``. + + Returns + ------- + BaseException + The unwrapped root-cause exception. + + """ + # Case 1: DependencyError / PropagatedException sets __cause__ + if exc.__cause__ is not None: + return exc.__cause__ + # Case 2: Legacy .e_value wrapper + underlying = getattr(exc, "e_value", None) + if underlying is not None: + return underlying + # Case 3: Already the real exception + return exc + + +class FailureType(Enum): + """Classification of a simulation stage failure. + + Attributes + ---------- + PHYSICS + Failure due to simulation physics (overlapping atoms, box explosion, + constraint failures). Rescuable via MDP parameter adjustment. + INFRASTRUCTURE + Failure due to infrastructure (OOM, timeout, SLURM preemption, + filesystem errors). Handled by Parsl's built-in retry. + UNKNOWN + Failure whose cause could not be determined. Treated as + non-rescuable (same as INFRASTRUCTURE). + + """ + + PHYSICS = "physics" + INFRASTRUCTURE = "infrastructure" + UNKNOWN = "unknown" + + +def classify_failure( + exc: BaseException, + sim_dir: Path | None = None, + stage: str | None = None, +) -> FailureType: + """Classify a GROMACS simulation failure. + + Inspects the exception message and, optionally, the GROMACS log file + in the simulation directory to determine whether the failure is due + to physics (rescuable) or infrastructure. + + Parameters + ---------- + exc : BaseException + The exception from a failed Parsl future. + sim_dir : Path, optional + Simulation directory to check for GROMACS log files. + stage : str, optional + Stage name (used to locate the correct log file). + + Returns + ------- + FailureType + The classified failure type. + + """ + # Unwrap Parsl's exception wrapping (DependencyError, legacy, modern) + underlying = _unwrap_parsl_exception(exc) + error_text = str(underlying) + + # Check the exception message first + if _matches_physics_patterns(error_text): + return FailureType.PHYSICS + + # Check GROMACS log file if available + if sim_dir is not None and stage is not None: + log_text = _read_stage_log(sim_dir, stage) + if log_text and _matches_physics_patterns(log_text): + return FailureType.PHYSICS + + # Check for known infrastructure patterns + infra_keywords = [ + "oom", + "out of memory", + "killed", + "timeout", + "time limit", + "cancelled", + "preempted", + "no space left", + "disk quota", + "connection reset", + ] + error_lower = error_text.lower() + for keyword in infra_keywords: + if keyword in error_lower: + return FailureType.INFRASTRUCTURE + + logger.debug(f"Could not classify failure: {error_text[:200]}") + return FailureType.UNKNOWN + + +def _matches_physics_patterns(text: str) -> bool: + """Return True if any physics-failure pattern matches the text. + + Parameters + ---------- + text : str + Error message or log content to search. + + Returns + ------- + bool + + """ + for pattern in _PHYSICS_PATTERNS: + if pattern.search(text): + return True + return False + + +def _read_stage_log(sim_dir: Path, stage: str) -> str: + """Read the GROMACS log file for a stage, if it exists. + + Parameters + ---------- + sim_dir : Path + Simulation directory. + stage : str + Stage name. + + Returns + ------- + str + Log file content, or empty string if not found. + + """ + from .stages import STAGE_BY_NAME + + spec = STAGE_BY_NAME.get(stage) + if spec is None: + return "" + + log_file = sim_dir / f"{spec.deffnm}.log" + if not log_file.exists(): + return "" + + try: + # Read only the last 10KB to avoid huge logs + size = log_file.stat().st_size + with open(log_file) as f: + if size > 10240: + f.seek(size - 10240) + f.readline() # Skip partial line + return f.read() + except Exception as e: + logger.debug(f"Could not read log file {log_file}: {e}") + return "" + + +def _describe_failure(exc: BaseException) -> tuple[str, str]: + """Extract ``(failure_type, error_detail)`` from a future's exception. + + Unwraps Parsl exception wrappers to surface the underlying error type. + + Returns + ------- + tuple[str, str] + ``(failure_type, error_detail)`` — the underlying exception's class + name and its string representation. + + """ + underlying = _unwrap_parsl_exception(exc) + return type(underlying).__name__, str(underlying) diff --git a/mdfactory/orchestration/mdp.py b/mdfactory/orchestration/mdp.py new file mode 100644 index 0000000..8f721ed --- /dev/null +++ b/mdfactory/orchestration/mdp.py @@ -0,0 +1,230 @@ +# ABOUTME: MDP file parser, modifier, and writer for rescue tier parameter adjustment +# ABOUTME: Supports binary-division rescue strategy (halving step sizes, doubling nsteps) +"""MDP file utilities for adaptive rescue retry. + +Provides functions to parse GROMACS MDP parameter files, modify critical +parameters for rescue tiers (binary division strategy), and write modified +files back to disk preserving structure and comments. +""" + +from __future__ import annotations + +from pathlib import Path + +from loguru import logger + +#: Parameters modified per stage during rescue. Each maps parameter name +#: to the operation applied at each tier: ``"halve"`` divides by 2, +#: ``"double"`` multiplies by 2. +RESCUE_PARAMS: dict[str, dict[str, str]] = { + "EM": {"emstep": "halve", "nsteps": "double"}, + "NVT": {"dt": "halve", "nsteps": "double"}, + "NPT": {"dt": "halve", "nsteps": "double"}, +} + +#: Stages eligible for rescue retry (Production is never rescued). +RESCUE_ELIGIBLE_STAGES: frozenset[str] = frozenset(RESCUE_PARAMS.keys()) + + +def parse_mdp(path: Path) -> list[tuple[str, str | None, str | None]]: + """Parse an MDP file preserving structure for round-trip modification. + + Each line is returned as a tuple ``(raw_line, key, value)`` where + ``key`` and ``value`` are ``None`` for comment/blank lines. + + Parameters + ---------- + path : Path + Path to the MDP file. + + Returns + ------- + list[tuple[str, str | None, str | None]] + List of ``(raw_line, normalized_key, value_string)`` tuples. + Keys are normalized to lowercase with dashes replaced by underscores. + + """ + lines: list[tuple[str, str | None, str | None]] = [] + content = path.read_text() + + for raw_line in content.splitlines(): + # Strip comment portion (everything after ';') + stripped = raw_line.split(";")[0].strip() + if "=" not in stripped: + lines.append((raw_line, None, None)) + continue + + raw_key, val = stripped.split("=", 1) + norm_key = raw_key.strip().lower().replace("-", "_") + lines.append((raw_line, norm_key, val.strip())) + + return lines + + +def get_mdp_value(parsed: list[tuple[str, str | None, str | None]], key: str) -> str | None: + """Extract the value of a specific key from parsed MDP data. + + Parameters + ---------- + parsed : list + Output of :func:`parse_mdp`. + key : str + Normalized key name (lowercase, underscores). + + Returns + ------- + str or None + The value string, or ``None`` if the key is not found. + + """ + for _, k, v in parsed: + if k == key: + return v + return None + + +def modify_mdp_value( + parsed: list[tuple[str, str | None, str | None]], + key: str, + new_value: str, +) -> list[tuple[str, str | None, str | None]]: + """Return a copy of parsed MDP with one key's value replaced. + + The raw line is reconstructed to preserve the key's original casing + and surrounding whitespace as much as possible. + + Parameters + ---------- + parsed : list + Output of :func:`parse_mdp`. + key : str + Normalized key to modify. + new_value : str + New value string. + + Returns + ------- + list + Modified parsed list (new copy). + + """ + result = [] + for raw_line, k, v in parsed: + if k == key: + # Reconstruct: keep everything before '=' and any trailing comment + parts = raw_line.split(";", 1) + assignment = parts[0] + comment = f";{parts[1]}" if len(parts) > 1 else "" + + eq_idx = assignment.index("=") + prefix = assignment[: eq_idx + 1] # "key =" + # Preserve spacing after '=' if possible + after_eq = assignment[eq_idx + 1 :] + leading_spaces = len(after_eq) - len(after_eq.lstrip()) + spacing = " " * max(leading_spaces, 1) + + new_raw = f"{prefix}{spacing}{new_value}" + if comment: + # Pad to roughly original width for alignment + new_raw = f"{new_raw:<{len(assignment)}}{comment}" + result.append((new_raw, k, new_value)) + else: + result.append((raw_line, k, v)) + return result + + +def write_mdp(parsed: list[tuple[str, str | None, str | None]], path: Path) -> None: + """Write parsed (possibly modified) MDP data back to a file. + + Parameters + ---------- + parsed : list + Parsed MDP data (from :func:`parse_mdp` or :func:`modify_mdp_value`). + path : Path + Output file path. + + """ + lines = [raw_line for raw_line, _, _ in parsed] + path.write_text("\n".join(lines) + "\n") + + +def apply_rescue_tier(sim_dir: Path, stage: str, tier: int) -> Path: + """Apply rescue-tier parameter modifications to a stage's MDP file. + + Reads the stage's standard MDP file, applies binary-division + modifications for the given tier, and writes the result to a + rescue-specific filename (e.g. ``em_rescue_t1.mdp``). + + Parameters + ---------- + sim_dir : Path + Simulation directory containing the MDP file. + stage : str + Stage name (``"EM"``, ``"NVT"``, ``"NPT"``). + tier : int + Rescue tier (1-based). Each tier halves/doubles parameters + relative to the original (not the previous tier). + + Returns + ------- + Path + Path to the written rescue MDP file. + + Raises + ------ + ValueError + If the stage is not rescue-eligible or the tier is < 1. + FileNotFoundError + If the source MDP file does not exist. + + """ + if stage not in RESCUE_PARAMS: + raise ValueError(f"Stage {stage!r} is not rescue-eligible. Valid: {sorted(RESCUE_PARAMS)}") + if tier < 1: + raise ValueError(f"Rescue tier must be >= 1, got {tier}") + + from .stages import STAGE_BY_NAME + + spec = STAGE_BY_NAME[stage] + source_mdp = sim_dir / spec.mdp_file + + if not source_mdp.exists(): + raise FileNotFoundError(f"Source MDP not found: {source_mdp}") + + parsed = parse_mdp(source_mdp) + params = RESCUE_PARAMS[stage] + + for param, operation in params.items(): + current_val = get_mdp_value(parsed, param) + if current_val is None: + logger.warning(f"MDP parameter {param!r} not found in {source_mdp}, skipping") + continue + + try: + numeric = float(current_val) + except ValueError: + logger.warning(f"Cannot parse {param}={current_val!r} as number, skipping") + continue + + if operation == "halve": + new_val = numeric / (2**tier) + elif operation == "double": + new_val = numeric * (2**tier) + else: + raise ValueError(f"Unknown operation: {operation!r}") + + # Format: use int if the result is a whole number, else float + if new_val == int(new_val) and "." not in current_val: + formatted = str(int(new_val)) + else: + formatted = f"{new_val:g}" + + logger.warning(f"RESCUE tier {tier} for {stage}: {param} = {current_val} → {formatted}") + parsed = modify_mdp_value(parsed, param, formatted) + + # Write rescue MDP with tier-specific name + rescue_name = f"{spec.mdp_file.rsplit('.', 1)[0]}_rescue_t{tier}.mdp" + rescue_path = sim_dir / rescue_name + write_mdp(parsed, rescue_path) + + return rescue_path diff --git a/mdfactory/orchestration/progress.py b/mdfactory/orchestration/progress.py new file mode 100644 index 0000000..aeb2fda --- /dev/null +++ b/mdfactory/orchestration/progress.py @@ -0,0 +1,225 @@ +# ABOUTME: Thread-safe progress tracking for per-stage simulation monitoring +# ABOUTME: Provides StageProgressTracker and Rich progress display for EM/NVT/NPT/Production stages +"""Per-stage progress tracking for simulation orchestration. + +Provides :class:`StageProgressTracker`, a thread-safe tracker that worker +threads report into, and :func:`display_stage_progress`, a Rich-based +display that polls the tracker and renders one progress bar per simulation +stage (EM, NVT, NPT, Production). +""" + +from __future__ import annotations + +import threading +import time +from dataclasses import dataclass, field +from enum import Enum + + +class SimState(Enum): + """State of a single simulation in a single stage.""" + + PENDING = "pending" + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + SKIPPED = "skipped" + + +_TERMINAL = frozenset({SimState.SUCCEEDED, SimState.FAILED, SimState.SKIPPED}) + + +@dataclass +class StageProgressTracker: + """Thread-safe tracker for per-stage, per-simulation progress. + + Worker threads call :meth:`mark_running`, :meth:`mark_succeeded`, + :meth:`mark_failed`, :meth:`mark_skipped`. The main thread calls + :meth:`snapshot` for counts and :meth:`all_done` for termination. + + Parameters + ---------- + stages : list[str] + Ordered stage names (e.g. ``["EM", "NVT", "NPT", "Production"]``). + sim_hashes : list[str] + Simulation directory hashes, one per simulation. + + """ + + stages: list[str] + sim_hashes: list[str] + + _state: dict[str, dict[str, SimState]] = field(init=False) + _lock: threading.Lock = field(init=False, default_factory=threading.Lock) + _results: dict[str, dict] = field(init=False, default_factory=dict) + + def __post_init__(self) -> None: + self._state = { + stage: {h: SimState.PENDING for h in self.sim_hashes} for stage in self.stages + } + self._lock = threading.Lock() + self._results = {} + + def mark_running(self, stage: str, sim_hash: str) -> None: + with self._lock: + self._state[stage][sim_hash] = SimState.RUNNING + + def mark_succeeded(self, stage: str, sim_hash: str) -> None: + with self._lock: + self._state[stage][sim_hash] = SimState.SUCCEEDED + + def mark_failed(self, stage: str, sim_hash: str) -> None: + with self._lock: + self._state[stage][sim_hash] = SimState.FAILED + + def mark_skipped(self, stage: str, sim_hash: str) -> None: + with self._lock: + self._state[stage][sim_hash] = SimState.SKIPPED + + def store_result(self, sim_hash: str, result: dict) -> None: + """Store the final result dict for a simulation.""" + with self._lock: + self._results[sim_hash] = result + + def snapshot(self) -> dict[str, dict[str, int]]: + """Return per-stage counts. + + Returns + ------- + dict[str, dict[str, int]] + ``{stage: {succeeded: N, failed: N, running: N, pending: N, skipped: N}}``. + """ + with self._lock: + out: dict[str, dict[str, int]] = {} + for stage in self.stages: + counts = {s.value: 0 for s in SimState} + for h in self.sim_hashes: + counts[self._state[stage][h].value] += 1 + out[stage] = counts + return out + + def all_done(self) -> bool: + """Return ``True`` when every simulation's last stage is terminal.""" + with self._lock: + last_stage = self.stages[-1] + return all(self._state[last_stage][h] in _TERMINAL for h in self.sim_hashes) + + def collect_results(self) -> list[dict]: + """Return stored result dicts in ``sim_hashes`` order.""" + with self._lock: + return [self._results.get(h, {"hash": h, "status": "unknown"}) for h in self.sim_hashes] + + +def _get_block_status() -> str: + """Query the active Parsl DFK for SLURM executor block statuses. + + Returns a Rich-markup string summarising block counts, or ``""`` + when no executor exposes status. + """ + try: + import parsl # type: ignore[import-not-found] + + dfk = parsl.dfk() + counts: dict[str, int] = {} + for executor in dfk.executors.values(): + if not hasattr(executor, "status"): + continue + for _block_id, job_status in executor.status().items(): + state = str(job_status.state.name).lower() + counts[state] = counts.get(state, 0) + 1 + if not counts: + return "" + parts = [] + if counts.get("running", 0): + parts.append(f"[green]{counts['running']} running[/]") + if counts.get("pending", 0): + parts.append(f"[yellow]{counts['pending']} pending[/]") + if counts.get("completed", 0): + parts.append(f"[dim]{counts['completed']} completed[/]") + if counts.get("failed", 0): + parts.append(f"[red]{counts['failed']} failed[/]") + for state, count in counts.items(): + if state not in ("running", "pending", "completed", "failed"): + parts.append(f"[dim]{count} {state}[/]") + return " · ".join(parts) + except Exception: + return "" + + +def display_stage_progress( + tracker: StageProgressTracker, + *, + poll_interval: float = 2.0, +) -> None: + """Poll the tracker and render Rich progress bars until all simulations finish. + + Runs on the main thread. Blocks until :meth:`StageProgressTracker.all_done` + returns ``True``. + + Parameters + ---------- + tracker : StageProgressTracker + Shared tracker updated by worker threads. + poll_interval : float + Seconds between display refreshes. + + """ + from rich.console import Console, Group + from rich.live import Live + from rich.progress import BarColumn, MofNCompleteColumn, Progress, TextColumn + from rich.text import Text + + console = Console() + total = len(tracker.sim_hashes) + + progress = Progress( + TextColumn("{task.description}"), + BarColumn(bar_width=40), + MofNCompleteColumn(), + TextColumn("·"), + TextColumn("[green]{task.fields[succeeded]} ✓[/]"), + TextColumn("[red]{task.fields[failed]} ✗[/]"), + TextColumn("[yellow]{task.fields[running]} ●[/]"), + console=console, + transient=False, + ) + + task_ids = {} + max_len = max(len(s) for s in tracker.stages) + for stage in tracker.stages: + tid = progress.add_task( + f"⚒ {stage:<{max_len}}", + total=total, + succeeded=0, + failed=0, + running=0, + ) + task_ids[stage] = tid + + def _render(): + snap = tracker.snapshot() + for stage in tracker.stages: + counts = snap[stage] + done = counts["succeeded"] + counts["failed"] + counts["skipped"] + progress.update( + task_ids[stage], + completed=done, + succeeded=counts["succeeded"], + failed=counts["failed"], + running=counts["running"], + ) + parts: list = [progress] + block_info = _get_block_status() + if block_info: + parts.append(Text.from_markup(f" ▸ SLURM: {block_info}")) + return Group(*parts) + + try: + with Live(_render(), console=console, refresh_per_second=2) as live: + while not tracker.all_done(): + time.sleep(poll_interval) + live.update(_render()) + live.update(_render()) + except KeyboardInterrupt: + console.print("\n[bold yellow]Interrupted[/]") + raise diff --git a/mdfactory/orchestration/rescue.py b/mdfactory/orchestration/rescue.py new file mode 100644 index 0000000..0f09bbe --- /dev/null +++ b/mdfactory/orchestration/rescue.py @@ -0,0 +1,158 @@ +# ABOUTME: Rescue retry loop for GROMACS physics failures +# ABOUTME: Retries failed stages with halved MDP parameters (binary division) +"""Adaptive rescue retry for GROMACS simulation stages. + +When a stage fails due to a physics error (overlapping atoms, exploding box, +constraint failures), the rescue loop modifies MDP parameters using binary +division (halving step sizes, doubling nsteps) and re-submits the stage. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from loguru import logger + +from .config import get_stage_config_or_none +from .errors import FailureType, classify_failure +from .mdp import apply_rescue_tier +from .stages import STAGE_BY_NAME, run_stage + +if TYPE_CHECKING: + from parsl import AppFuture + + from .config import ExecutorConfig + + +def _clean_partial_outputs(sim_dir: Path, stage: str) -> None: + """Remove partial outputs from a failed stage so it can be re-run cleanly. + + Removes the ``.tpr``, ``.cpt``, ``.gro`` output, ``.log``, ``.edr``, + and trajectory files for the given stage. + + Parameters + ---------- + sim_dir : Path + Simulation directory. + stage : str + Stage name. + + """ + spec = STAGE_BY_NAME[stage] + candidates = [ + spec.tpr_file, + spec.cpt_file, + spec.gro_out, + f"{spec.deffnm}.log", + f"{spec.deffnm}.edr", + ] + candidates.extend(spec.traj_files) + + for fname in candidates: + if not fname: + continue + f = sim_dir / fname + if f.exists(): + f.unlink() + logger.debug(f"Removed partial output: {f}") + + +def execute_stage_with_rescue( + sim_dir: Path, + stage: str, + prev_future: "AppFuture | None", + grompp_app, + mdrun_app, + *, + max_rescue: int = 3, + config: "ExecutorConfig | None" = None, +) -> "AppFuture": + """Execute a single stage with adaptive rescue on physics failures. + + Submits the stage and waits for its result. If the stage fails with + a physics error and rescue tiers remain, modifies the MDP parameters + (binary division), cleans partial outputs, and re-submits. + + Parameters + ---------- + sim_dir : Path + Simulation directory. + stage : str + Stage name (must be in :data:`RESCUE_ELIGIBLE_STAGES`). + prev_future : AppFuture or None + Dependency future from the preceding stage. + grompp_app : callable + Result of :func:`~mdfactory.orchestration.apps.get_grompp_app`. + mdrun_app : callable + Result of :func:`~mdfactory.orchestration.apps.get_mdrun_app`. + max_rescue : int + Maximum number of rescue tiers to attempt. + config : ExecutorConfig or None, optional + Executor configuration for per-stage resource hints. + + Returns + ------- + AppFuture + A completed future (already resolved — the result has been waited on). + + Raises + ------ + Exception + Re-raises the stage failure if rescue is exhausted or the failure + is not physics-related. + + """ + spec = STAGE_BY_NAME[stage] + stage_cfg = get_stage_config_or_none(config, stage) + cfg_kwarg: dict[str, Any] = {"stage_config": stage_cfg} if stage_cfg is not None else {} + + for tier in range(max_rescue + 1): + mdp_override = None + if tier > 0: + # Apply rescue tier modifications + rescue_mdp = apply_rescue_tier(sim_dir, stage, tier) + mdp_override = rescue_mdp.name + logger.warning( + f"RESCUE tier {tier}/{max_rescue} for {sim_dir.name}/{stage}: using {mdp_override}" + ) + + future = run_stage( + spec, + sim_dir, + prev_future, + grompp_app, + mdrun_app, + mdp_override=mdp_override, + **cfg_kwarg, + ) + + try: + future.result() + if tier > 0: + logger.warning(f"RESCUE succeeded for {sim_dir.name}/{stage} at tier {tier}") + return future + except Exception as exc: + failure_type = classify_failure(exc, sim_dir, stage) + + if failure_type != FailureType.PHYSICS: + logger.debug( + f"{sim_dir.name}/{stage}: non-physics failure " + f"({failure_type.value}), not rescuable" + ) + raise + + if tier >= max_rescue: + logger.error( + f"RESCUE exhausted for {sim_dir.name}/{stage} after {max_rescue} tier(s)" + ) + raise + + logger.warning( + f"{sim_dir.name}/{stage}: physics failure detected, " + f"will attempt rescue tier {tier + 1}" + ) + _clean_partial_outputs(sim_dir, stage) + + # Unreachable — the loop always returns or raises + raise RuntimeError("Rescue loop exited unexpectedly") # pragma: no cover diff --git a/mdfactory/orchestration/session.py b/mdfactory/orchestration/session.py new file mode 100644 index 0000000..86aecdc --- /dev/null +++ b/mdfactory/orchestration/session.py @@ -0,0 +1,223 @@ +# ABOUTME: Generic Parsl session lifecycle management (guard, load, shutdown) +# ABOUTME: Reusable context manager shared by build/simulation/benchmark orchestration +"""Parsl session management. + +Provides :func:`parsl_session`, a context manager that owns the full Parsl +``DataFlowKernel`` lifecycle — guarding against an already-active DFK, loading +the executor config, and guaranteeing shutdown (including ``scancel`` of any +lingering SLURM jobs) on exit. Build, simulation, and benchmark orchestration +all share this single implementation rather than duplicating the guard / load / +cleanup boilerplate. +""" + +from __future__ import annotations + +import subprocess +from collections.abc import Iterator +from contextlib import contextmanager +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from loguru import logger + +from .config import _import_parsl + +if TYPE_CHECKING: + from .config import ExecutorConfig + + +@dataclass +class ParslSession: + """Handle yielded by :func:`parsl_session`. + + Parameters + ---------- + parsl : module + The imported ``parsl`` module, for submitting apps or inspecting the DFK. + detached : bool + When ``True``, the context manager will **not** shut down the DFK on + exit; the caller takes ownership of cleanup. Set via :meth:`detach`. + + """ + + parsl: object + detached: bool = False + + def detach(self) -> None: + """Transfer DFK ownership to the caller (skip shutdown on exit). + + Used when returning raw futures (e.g. ``wait=False``): the caller is + then responsible for calling ``parsl.clear()`` once all futures + complete. + """ + self.detached = True + + +@contextmanager +def parsl_session(config: "ExecutorConfig") -> Iterator[ParslSession]: + """Manage a Parsl ``DataFlowKernel`` lifecycle for an orchestration run. + + Guards against an already-active DFK, loads ``config``'s Parsl config, + yields a :class:`ParslSession`, and guarantees shutdown on exit unless the + session was detached via :meth:`ParslSession.detach`. + + Sessions must be **sequential, never nested**. :func:`_guard_no_active_dfk` + raises :class:`RuntimeError` if a DFK is already loaded when this context + manager is entered. On exit, :func:`_shutdown_parsl` calls ``parsl.clear()`` + so the DFK is fully released before the next session can open. Do not open + a second :func:`parsl_session` from inside an active one. + + Examples + -------- + >>> with parsl_session(config) as session: # doctest: +SKIP + ... futures = [app(x) for x in inputs] + ... results = wait(futures) + + Parameters + ---------- + config : ExecutorConfig + Executor configuration whose ``to_parsl_config()`` drives the DFK. + + Yields + ------ + ParslSession + Session handle exposing the ``parsl`` module and a ``detach()`` escape + hatch. + + Raises + ------ + RuntimeError + If a Parsl ``DataFlowKernel`` is already active. + + """ + import signal + + parsl = _import_parsl() + _guard_no_active_dfk(parsl) + # Ignore SIGINT during load so child processes (interchange, workers) + # inherit SIG_IGN and don't print tracebacks on Ctrl+C. + prev_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) + parsl.load(config.to_parsl_config()) + signal.signal(signal.SIGINT, prev_handler) + session = ParslSession(parsl=parsl) + try: + yield session + finally: + if not session.detached: + _shutdown_parsl() + + +def _guard_no_active_dfk(parsl) -> None: + """Raise if a Parsl ``DataFlowKernel`` is already loaded. + + Parameters + ---------- + parsl : module + The imported ``parsl`` module. + + Raises + ------ + RuntimeError + If a DFK is already active. + + """ + try: + parsl.dfk() + except Exception: + return # No active DFK, good + raise RuntimeError( + "A Parsl DataFlowKernel is already active. " + "Call parsl.clear() before starting a new session." + ) + + +def _shutdown_parsl(): + """Shut down Parsl and cancel any remaining SLURM jobs.""" + try: + import parsl # type: ignore[import-not-found] + except ImportError: + return + + job_ids = [] + try: + dfk = parsl.dfk() + job_ids = _get_slurm_job_ids(dfk) + except Exception as exc: + logger.debug(f"Could not query Parsl DFK for SLURM jobs: {exc}") + + try: + parsl.clear() + except Exception as exc: + logger.warning(f"Parsl shutdown failed — DFK may still be loaded: {exc}") + + # Cancel jobs found via DFK tracking + if job_ids: + _scancel_jobs(job_ids) + + # Safety net: query squeue for any Parsl jobs the DFK missed + missed = _find_parsl_jobs_via_squeue(exclude=set(job_ids)) + if missed: + _scancel_jobs(missed) + + +def _find_parsl_jobs_via_squeue(*, exclude: set[str] | None = None) -> list[str]: + """Query squeue for running/pending Parsl jobs owned by the current user.""" + exclude = exclude or set() + try: + result = subprocess.run( + ["squeue", "--me", "--noheader", "--states=R,PD", "--format=%i %j"], + capture_output=True, + text=True, + timeout=10, + check=False, + ) + if result.returncode != 0: + return [] + job_ids = [] + for line in result.stdout.strip().splitlines(): + parts = line.split(None, 1) + if len(parts) >= 2 and "parsl" in parts[1].lower(): + if parts[0] not in exclude: + job_ids.append(parts[0]) + return job_ids + except (FileNotFoundError, subprocess.TimeoutExpired): + return [] + except Exception: + return [] + + +def _get_slurm_job_ids(dfk) -> list[str]: + """Extract active SLURM job IDs from the Parsl DataFlowKernel.""" + job_ids = [] + try: + for executor in dfk.executors.values(): + if hasattr(executor, "provider") and hasattr(executor.provider, "resources"): + for _block_id, resource in executor.provider.resources.items(): + job_id = resource.get("remote_job_id") or resource.get("job_id") + if job_id: + job_ids.append(str(job_id)) + except Exception as exc: + logger.debug(f"Could not enumerate SLURM jobs for explicit scancel: {exc}") + return job_ids + + +def _scancel_jobs(job_ids: list[str]): + """Run scancel on the given SLURM job IDs.""" + if not job_ids: + return + try: + cmd = ["scancel"] + job_ids + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10, check=False) + if result.returncode == 0: + logger.info(f"Cancelled SLURM jobs: {', '.join(job_ids)}") + else: + logger.debug(f"scancel stderr: {result.stderr.strip()}") + except FileNotFoundError: + logger.debug("scancel not found (not on SLURM cluster)") + except subprocess.TimeoutExpired: + logger.warning( + f"scancel timed out after 10 s — SLURM jobs {', '.join(job_ids)} may still " + f"be running. Run `scancel {' '.join(job_ids)}` manually." + ) + except Exception as exc: + logger.debug(f"scancel failed: {exc}") diff --git a/mdfactory/orchestration/simulate.py b/mdfactory/orchestration/simulate.py new file mode 100644 index 0000000..7f25874 --- /dev/null +++ b/mdfactory/orchestration/simulate.py @@ -0,0 +1,871 @@ +# ABOUTME: Main dispatcher for Parsl-based GROMACS simulation orchestration +# ABOUTME: Handles checkpoint detection, dry-run, and progress monitoring +"""GROMACS simulation orchestration via Parsl. + +Provides :func:`run_simulations`, the main entry point for orchestrating +GROMACS MD simulations via Parsl. Handles checkpoint detection, dry-run mode, +and progress monitoring. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING, TypedDict + +from loguru import logger + +from .apps import get_grompp_app, get_mdrun_app +from .config import get_stage_config_or_none +from .session import parsl_session +from .stages import ( + STAGE_BY_NAME, + STAGE_REGISTRY, + StageSpec, + run_stage, +) + +# Import MDAnalysis at module level for testability +try: + import MDAnalysis as mda +except ImportError: + mda = None + +if TYPE_CHECKING: + from parsl import AppFuture + + from .config import ExecutorConfig + from .progress import StageProgressTracker + + +class _StageState(TypedDict): + """Checkpoint detection result for a single stage.""" + + status: str # "complete" | "partial" | "not_started" + cpt_file: "Path | None" + restart: bool + + +def _has_restart_pair(cpt_file: Path, tpr_file: Path) -> bool: + """Return True if both checkpoint and TPR files exist (restartable state).""" + return cpt_file.exists() and tpr_file.exists() + + +def _bash_result_to_dict(raw: object, sim_hash: str) -> dict: + """Normalise a bash_app result to a ``{"hash": ..., "status": ...}`` dict.""" + if isinstance(raw, dict): + return raw + return {"hash": sim_hash, "status": "success"} + + +def run_simulations( + sim_paths: list[Path], + config: "ExecutorConfig", + *, + stages: list[str] | None = None, + wait: bool = True, + dry_run: bool = False, + clean: bool = False, + checkpoint_mode: str = "auto", + max_rescue: int = 3, +) -> list[dict]: + """Orchestrate GROMACS simulations via Parsl. + + Parameters + ---------- + sim_paths : list[Path] + Simulation directories (must contain system.pdb, topology.top, *.mdp). + config : ExecutorConfig + Parsl executor configuration (local or SLURM). + stages : list[str], optional + Stages to run. Default: ["EM", "NVT", "NPT", "Production"]. + wait : bool + Wait for completion (default: True). + dry_run : bool + Preview plan without executing (default: False). + clean : bool + Remove simulation outputs before running (default: False). + Respects ``stages`` filter — only files belonging to requested + stages are deleted. Combined with ``dry_run``, previews what + would be deleted without acting. + checkpoint_mode : str + - "auto": Skip stages with valid outputs (default) + - "skip": Never re-run completed stages + - "force": Overwrite all stages + max_rescue : int + Maximum number of rescue tiers for physics failures in EM/NVT/NPT + stages. Each tier halves the step size and doubles nsteps. Set to 0 + to disable rescue. Default: 3. + + Returns + ------- + list[dict] + Results with status, errors, timing (or futures if wait=False). + + """ + valid_stages = [s.name for s in STAGE_REGISTRY] + valid_modes = ("auto", "skip", "force") + + if stages is None: + stages = list(valid_stages) + else: + invalid = [s for s in stages if s not in valid_stages] + if invalid: + raise ValueError(f"Invalid stage(s): {invalid}. Valid stages: {valid_stages}") + + if checkpoint_mode not in valid_modes: + raise ValueError( + f"Invalid checkpoint_mode: {checkpoint_mode!r}. Valid: {list(valid_modes)}" + ) + + # 1. Filter: skip directories whose build did not complete. + # Skipped dirs are collected as result entries so callers can account for + # all requested simulations (succeeded + failed + skipped == requested). + skipped_results: list[dict] = [] + ready_paths: list[Path] = [] + for sim_dir in sim_paths: + missing = _missing_build_files(sim_dir, stages) + if missing: + logger.warning(f"Skipping {sim_dir.name}: build incomplete, missing: {missing}") + skipped_results.append( + { + "hash": sim_dir.name, + "status": "skipped", + "directory": str(sim_dir), + "reason": f"build incomplete, missing: {missing}", + } + ) + else: + ready_paths.append(sim_dir) + + n_skipped = len(skipped_results) + if n_skipped: + logger.info( + f"Skipped {n_skipped} simulation(s) with incomplete builds; " + f"{len(ready_paths)} ready to simulate." + ) + if not ready_paths: + logger.warning("No simulation directories have complete builds. Nothing to run.") + return skipped_results + sim_paths = ready_paths + + # 1b. Clean outputs (if requested) before checkpoint detection. + if clean: + for sim_dir in sim_paths: + deleted = clean_simulation_outputs(sim_dir, stages, dry_run=dry_run) + if dry_run and deleted: + logger.info( + f"Would delete {len(deleted)} file(s) from {sim_dir.name}: " + + ", ".join(p.name for p in deleted) + ) + if dry_run: + # After previewing deletions, still show the dry-run execution plan + # (which will report all stages as needed since nothing was deleted). + pass + + # 2. Checkpoint detection (includes restart info for -cpi -append support) + work_plan = [] + for sim_dir in sim_paths: + stage_items = _detect_needed_stages_with_restart_info(sim_dir, stages, checkpoint_mode) + needed_stages = [item["stage"] for item in stage_items] + stage_restarts = { + item["stage"]: str(item["cpt_file"]) + for item in stage_items + if item["restart"] and item["cpt_file"] is not None + } + work_plan.append( + { + "sim_dir": sim_dir, + "hash": sim_dir.name, + "stages": needed_stages, + "stage_restarts": stage_restarts, + } + ) + + logger.info(f"Prepared work plan for {len(work_plan)} simulation(s)") + + # 3. Dry-run mode + if dry_run: + return skipped_results + _log_dry_run_plan(work_plan, config) + + # 4. Validate prerequisites before Parsl session + for item in work_plan: + if item["stages"]: + # Validate first stage has required inputs + _validate_stage_prerequisites(item["sim_dir"], item["stages"][0]) + + # 5. Filter active items (stages still needed) + active_items = [] + for item in work_plan: + if not item["stages"]: + logger.info(f"Skipping {item['hash']} (all stages complete)") + continue + active_items.append(item) + + if not active_items: + logger.info("All simulations already complete.") + return skipped_results + + # 6. Parsl session + with parsl_session(config) as session: + grompp_app = get_grompp_app() + mdrun_app = get_mdrun_app() + + # wait=False: legacy path returning raw futures (no progress display) + if not wait: + from concurrent.futures import Future as StdFuture + from concurrent.futures import ThreadPoolExecutor + + futures: list[tuple[str, object]] = [] + + def _run_pipeline_nowait(item): + return _execute_stage_list( + item["sim_dir"], + item["stages"], + grompp_app, + mdrun_app, + stage_restarts=item.get("stage_restarts"), + config=config, + max_rescue=max_rescue, + ) + + with ThreadPoolExecutor() as pool: + thread_futs = [ + (item["hash"], pool.submit(_run_pipeline_nowait, item)) for item in active_items + ] + for h, tf in thread_futs: + try: + futures.append((h, tf.result())) + except Exception as exc: + logger.error(f"Pipeline failed for {h}: {exc}") + failed_fut = StdFuture() + failed_fut.set_exception(exc) + futures.append((h, failed_fut)) + + logger.info(f"Submitted {len(futures)} simulation(s)") + logger.warning( + "Returning raw AppFuture objects — futures resolve to None (bash_app exit), " + "not status dicts. Caller must call parsl.clear() when done." + ) + session.detach() + return [fut for _, fut in futures] + + # 7. wait=True: tracked execution with per-stage progress display + import threading + + from .errors import _describe_failure + from .progress import StageProgressTracker, display_stage_progress + + all_hashes = [item["hash"] for item in active_items] + tracker = StageProgressTracker(stages=stages, sim_hashes=all_hashes) + + # Pre-mark stages already completed by checkpoint detection + for item in active_items: + for stage in stages: + if stage not in item["stages"]: + tracker.mark_succeeded(stage, item["hash"]) + + def _run_pipeline(item): + sim_hash = item["hash"] + try: + final_future = _execute_stage_list( + item["sim_dir"], + item["stages"], + grompp_app, + mdrun_app, + stage_restarts=item.get("stage_restarts"), + config=config, + max_rescue=max_rescue, + tracker=tracker, + ) + # Wait for the terminal future (Production) to collect its result + if final_future is not None: + try: + raw = final_future.result() + result = _bash_result_to_dict(raw, sim_hash) + except Exception as exc: + failure_type, error_detail = _describe_failure(exc) + result = { + "hash": sim_hash, + "status": "failed", + "error": error_detail, + "failure_type": failure_type, + "error_detail": error_detail, + } + else: + result = {"hash": sim_hash, "status": "success"} + tracker.store_result(sim_hash, result) + except Exception as exc: + failure_type, error_detail = _describe_failure(exc) + tracker.store_result( + sim_hash, + { + "hash": sim_hash, + "status": "failed", + "error": error_detail, + "failure_type": failure_type, + "error_detail": error_detail, + }, + ) + + logger.info(f"Submitted {len(active_items)} simulation(s)") + + # Launch daemon threads — they won't block process exit on Ctrl+C + workers = [] + for item in active_items: + t = threading.Thread(target=_run_pipeline, args=(item,), daemon=True) + t.start() + workers.append(t) + + # Main thread: display progress until all stages complete + display_stage_progress(tracker) + + # Ensure all worker threads have stored results before collecting + for t in workers: + t.join() + + # Collect results + run_results = tracker.collect_results() + return skipped_results + run_results + + +def _missing_build_files(sim_dir: Path, stages: list[str]) -> list[str]: + """Return names of required build output files that are absent from *sim_dir*.""" + required = ["system.pdb", "topology.top"] + [STAGE_BY_NAME[s].mdp_file for s in stages] + return [f for f in required if not (sim_dir / f).exists()] + + +def clean_simulation_outputs( + sim_dir: Path, + stages: list[str], + *, + dry_run: bool = False, +) -> list[Path]: + """Remove simulation outputs for the given stages, preserving build inputs. + + Derives deletable files from :data:`STAGE_BY_NAME` (tpr, cpt, log, edr, + gro, trr, xtc) plus ``mdout.mdp``, rescue-tier MDPs, and GROMACS backup + files (``#*#``). + + Parameters + ---------- + sim_dir : Path + Simulation directory. + stages : list[str] + Stage names whose outputs should be removed. + dry_run : bool + If ``True``, collect and return the list of files that *would* be + deleted without actually removing them. + + Returns + ------- + list[Path] + Paths that were deleted (or would be deleted in dry-run mode). + + """ + to_delete: list[Path] = [] + + for stage_name in stages: + spec = STAGE_BY_NAME[stage_name] + deffnm = spec.deffnm + + # Named output files: tpr, cpt, log, edr + for ext in ("tpr", "cpt", "log", "edr"): + to_delete.append(sim_dir / f"{deffnm}.{ext}") + + # Structure output (EM/NVT/NPT) + if spec.gro_out: + to_delete.append(sim_dir / spec.gro_out) + + # Trajectory outputs (Production) + for traj in spec.traj_files: + to_delete.append(sim_dir / traj) + + # Rescue-tier MDPs (e.g. em_rescue_t1.mdp, em_rescue_t2.mdp) + mdp_stem = spec.mdp_file.rsplit(".", 1)[0] + to_delete.extend(sim_dir.glob(f"{mdp_stem}_rescue_t*.mdp")) + + # GROMACS backup files (#deffnm.*#) + to_delete.extend(sim_dir.glob(f"#{deffnm}.*#")) + + # mdout.mdp — grompp output, always regenerated + to_delete.append(sim_dir / "mdout.mdp") + + # Deduplicate (glob results may overlap with named files) and filter to + # files that actually exist. + seen: set[Path] = set() + existing: list[Path] = [] + for p in to_delete: + if p not in seen and p.exists(): + seen.add(p) + existing.append(p) + + if not dry_run: + for p in existing: + p.unlink() + logger.debug(f"Deleted {p}") + if existing: + logger.info(f"Cleaned {len(existing)} file(s) from {sim_dir.name}") + + return existing + + +def _detect_skip_mode_state(output_file: Path, cpt_file: Path, tpr_file: Path) -> _StageState: + """Return stage state for 'skip' mode: complete if output exists, partial if cpt+tpr exist.""" + if output_file.exists(): + return {"status": "complete", "cpt_file": None, "restart": False} + if _has_restart_pair(cpt_file, tpr_file): + return {"status": "partial", "cpt_file": cpt_file, "restart": True} + return {"status": "not_started", "cpt_file": None, "restart": False} + + +def _detect_production_output_state( + sim_dir: Path, + cpt_file: Path, + tpr_file: Path, + traj_files: "tuple[str, ...]", +) -> _StageState: + """Check Production trajectory completeness; restart from cpt if incomplete.""" + expected_frames = _extract_expected_frames_from_mdp(sim_dir, "Production") + for traj_file in traj_files: + if _validate_trajectory_complete(sim_dir, traj_file, expected_frames): + return {"status": "complete", "cpt_file": None, "restart": False} + if _has_restart_pair(cpt_file, tpr_file): + return {"status": "partial", "cpt_file": cpt_file, "restart": True} + # All trajectory files incomplete and no checkpoint to restart from + return {"status": "partial", "cpt_file": None, "restart": False} + + +def _detect_skip_stage_state( + sim_dir: Path, + spec: "StageSpec", + cpt_file: Path, + tpr_file: Path, +) -> _StageState: + """Return stage state for 'skip' mode: file-existence only, no integrity checks.""" + if spec.traj_files: + # Mirror auto mode: complete if ANY trajectory file exists (XTC or TRR). + if any((sim_dir / tf).exists() for tf in spec.traj_files): + return {"status": "complete", "cpt_file": None, "restart": False} + if _has_restart_pair(cpt_file, tpr_file): + return {"status": "partial", "cpt_file": cpt_file, "restart": True} + return {"status": "not_started", "cpt_file": None, "restart": False} + return _detect_skip_mode_state(sim_dir / spec.gro_out, cpt_file, tpr_file) + + +def _detect_auto_output_state( + sim_dir: Path, + stage: str, + spec: "StageSpec", + cpt_file: Path, + tpr_file: Path, + prereq_cpt: "Path | None", +) -> _StageState: + """Return stage state for 'auto' mode; validates prerequisite integrity before accepting.""" + # Workflow integrity: prerequisite checkpoint must exist for the output to + # be trustworthy (e.g. npt.gro is only valid if nvt.cpt is present). + if prereq_cpt and not prereq_cpt.exists(): + return {"status": "not_started", "cpt_file": None, "restart": False} + if stage == "Production": + return _detect_production_output_state(sim_dir, cpt_file, tpr_file, spec.traj_files) + return {"status": "complete", "cpt_file": None, "restart": False} + + +def _detect_stage_state(sim_dir: Path, stage: str, mode: str = "auto") -> _StageState: + """Detect completion or partial progress of a stage given checkpoint *mode*.""" + spec = STAGE_BY_NAME[stage] + cpt_file = sim_dir / spec.cpt_file + tpr_file = sim_dir / spec.tpr_file + + if mode == "skip": + return _detect_skip_stage_state(sim_dir, spec, cpt_file, tpr_file) + + # For trajectory stages (Production) any accepted file counts as output. + # For structure stages (EM/NVT/NPT) the single gro_out is the output. + if spec.traj_files: + output_exists = any( + (sim_dir / tf).exists() and (sim_dir / tf).stat().st_size > 0 for tf in spec.traj_files + ) + else: + gro_out_file = sim_dir / spec.gro_out + output_exists = gro_out_file.exists() and gro_out_file.stat().st_size > 0 + + # Prerequisite checkpoint (for validating workflow integrity in auto mode) + prereq_cpt = sim_dir / spec.prereq_cpt if spec.prereq_cpt else None + + if output_exists: + return _detect_auto_output_state(sim_dir, stage, spec, cpt_file, tpr_file, prereq_cpt) + + # Check partial progress (checkpoint exists, output doesn't). + if _has_restart_pair(cpt_file, tpr_file): + if spec.traj_files: + # Trajectory stage (Production): stale checkpoint without a + # trajectory file cannot use -append — GROMACS would crash. + # Treat as not_started so grompp+mdrun run from scratch. + return {"status": "not_started", "cpt_file": None, "restart": False} + return {"status": "partial", "cpt_file": cpt_file, "restart": True} + + return {"status": "not_started", "cpt_file": None, "restart": False} + + +def _detect_needed_stages(sim_dir: Path, stages: list[str], mode: str) -> list[str]: + """Return stage names that still need to run (discards restart metadata).""" + return [ + item["stage"] for item in _detect_needed_stages_with_restart_info(sim_dir, stages, mode) + ] + + +def _detect_needed_stages_with_restart_info( + sim_dir: Path, stages: list[str], mode: str +) -> list[dict]: + """Return stage work items ``[{"stage", "restart", "cpt_file"}, ...]`` for incomplete stages.""" + if mode == "force": + return [{"stage": s, "restart": False, "cpt_file": None} for s in stages] + + needed = [] + for stage in stages: + state = _detect_stage_state(sim_dir, stage, mode) + + if state["status"] == "complete": + continue # Skip completed stages + elif state["status"] == "partial" and state["restart"]: + # Can resume from checkpoint + needed.append( + { + "stage": stage, + "restart": True, + "cpt_file": state["cpt_file"], + } + ) + else: + # Not started or can't restart - run from beginning + needed.append( + { + "stage": stage, + "restart": False, + "cpt_file": None, + } + ) + + return needed + + +def _execute_stage_list( + sim_dir: Path, + stages: list[str], + grompp_app, + mdrun_app, + stage_restarts: "dict[str, str] | None" = None, + config: "ExecutorConfig | None" = None, + max_rescue: int = 3, + tracker: "StageProgressTracker | None" = None, +) -> "AppFuture | None": + """Chain and submit stages in dependency order, with rescue retry and progress tracking.""" + if not stages: + return None + + restarts = stage_restarts or {} + sim_hash = sim_dir.name + + # Validate that stages are in dependency order (derived from STAGE_REGISTRY — single source) + stage_order = [s.name for s in STAGE_REGISTRY] + stage_indices = {name: idx for idx, name in enumerate(stage_order)} + + prev_idx = -1 + for stage in stages: + if stage not in stage_indices: + raise ValueError(f"Unknown stage: {stage}. Valid: {stage_order}") + + curr_idx = stage_indices[stage] + if curr_idx <= prev_idx: + raise ValueError(f"Stages must be in dependency order: {stage_order}. Got: {stages}") + prev_idx = curr_idx + + from .mdp import RESCUE_ELIGIBLE_STAGES + from .rescue import execute_stage_with_rescue + + prev_future = None + for i, stage in enumerate(stages): + cpt_file = restarts.get(stage, "") + + if tracker is not None: + tracker.mark_running(stage, sim_hash) + + try: + if max_rescue > 0 and stage in RESCUE_ELIGIBLE_STAGES and not cpt_file: + prev_future = execute_stage_with_rescue( + sim_dir, + stage, + prev_future, + grompp_app, + mdrun_app, + max_rescue=max_rescue, + config=config, + ) + # Rescue stages block — if we get here, it succeeded + if tracker is not None: + tracker.mark_succeeded(stage, sim_hash) + else: + stage_cfg = get_stage_config_or_none(config, stage) + cfg_kwarg = {"stage_config": stage_cfg} if stage_cfg is not None else {} + prev_future = run_stage( + STAGE_BY_NAME[stage], + sim_dir, + prev_future, + grompp_app, + mdrun_app, + restart_from_cpt=cpt_file, + **cfg_kwarg, + ) + # Non-rescue stages return immediately — track via callback + if tracker is not None: + _s, _h, _t = stage, sim_hash, tracker + + def _on_done(fut, s=_s, h=_h, t=_t): + if fut.exception() is not None: + t.mark_failed(s, h) + else: + t.mark_succeeded(s, h) + + prev_future.add_done_callback(_on_done) + + except Exception: + if tracker is not None: + tracker.mark_failed(stage, sim_hash) + for skip_stage in stages[i + 1 :]: + tracker.mark_skipped(skip_stage, sim_hash) + raise + + return prev_future + + +def _validate_stage_prerequisites(sim_dir: Path, first_stage: str) -> None: + """Fail fast if prerequisite files for *first_stage* are missing.""" + # Prerequisites: files needed BEFORE this stage can start. + # Derived from StageSpec: the input .gro file and, when present, the + # prerequisite checkpoint (which is also the grompp -t velocity input). + spec = STAGE_BY_NAME[first_stage] + required: list[Path] = [] + if spec.gro_in and spec.gro_in != "system.pdb": + required.append(sim_dir / spec.gro_in) + if spec.prereq_cpt: + required.append(sim_dir / spec.prereq_cpt) + missing = [f.name for f in required if not f.exists()] + + if missing: + raise FileNotFoundError( + f"Cannot start {first_stage} in {sim_dir.name}: " + f"missing prerequisite files: {missing}\n\n" + f"Resolution:\n" + f" 1. Run earlier stages first:\n" + f" mdfactory simulate {sim_dir} --stages EM NVT\n" + f" 2. Or force overwrite all:\n" + f" mdfactory simulate {sim_dir} --checkpoint force" + ) + + +def _validate_trajectory_complete( + sim_dir: Path, traj_file: str, expected_frames: int | None = None +) -> bool: + """Return True if *traj_file* exists, is readable via MDAnalysis, and has enough frames.""" + traj_path = sim_dir / traj_file + + if not traj_path.exists(): + return False + + # Quick check: empty file + if traj_path.stat().st_size == 0: + return False + + # Try to read with MDAnalysis + try: + if mda is None: + raise ImportError("MDAnalysis not available") + + # Find structure file for topology + structure_file = find_structure_file(sim_dir) + if not structure_file: + logger.warning(f"No structure file found in {sim_dir}, skipping frame check") + # Cannot validate frames without a topology — treat as incomplete so + # the caller can decide (partial restart will regenerate if needed). + return False + + # Load trajectory and count frames + u = mda.Universe(str(structure_file), str(traj_path)) + num_frames = len(u.trajectory) + + logger.debug(f"{traj_file}: {num_frames} frames") + + if expected_frames is not None: + return num_frames >= expected_frames + else: + # If no expectation, just check it's readable and non-trivial + return num_frames > 0 + + except Exception as e: + logger.warning(f"Trajectory validation failed for {traj_file}: {e}") + # A parse failure means the file is corrupt or truncated — not complete. + # Return False so the caller triggers a partial restart rather than + # silently skipping the stage on a bad trajectory. + return False + + +#: Candidate structure files checked by :func:`find_structure_file` in +#: priority order (most-equilibrated first), followed by the raw input. +#: Derived from :data:`~mdfactory.orchestration.stages.STAGE_REGISTRY` at +#: import time — add a new stage to the registry and this list stays in sync +#: automatically without any further edits to this module. +_STRUCTURE_CANDIDATES: list[str] = [ + spec.gro_out for spec in reversed(STAGE_REGISTRY) if spec.gro_out +] + ["system.pdb"] + + +def find_structure_file(sim_dir: Path) -> Path | None: + """Find the best available structure file in a simulation directory. + + Checks candidates in GROMACS output priority order so that the most + equilibrated coordinates are used when available. This is the canonical + priority list shared by trajectory validation (this module) and benchmark + pre-processing (:mod:`mdfactory.performance.benchmark`). + + The candidate list is derived from + :data:`~mdfactory.orchestration.stages.STAGE_REGISTRY` at import time + (see :data:`_STRUCTURE_CANDIDATES`), so adding a new stage with a + ``gro_out`` field automatically extends the search without modifying this + function. + + Parameters + ---------- + sim_dir : Path + Simulation directory. + + Returns + ------- + Path or None + Path to the first existing structure file, or ``None`` if none of the + candidates are found. + + Notes + ----- + Priority order (highest to lowest): + + 1. Most-recently-added stage's ``.gro`` (most equilibrated) + 2. …earlier stages in reverse registry order… + 3. ``system.pdb`` — raw starting structure + + """ + for candidate in _STRUCTURE_CANDIDATES: + path = sim_dir / candidate + if path.exists(): + return path + return None + + +def _extract_expected_frames_from_mdp(sim_dir: Path, stage: str) -> int | None: + """Extract expected frame count from MDP file (nsteps / output_frequency).""" + from .mdp import get_mdp_value, parse_mdp + + mdp_path = sim_dir / STAGE_BY_NAME.get(stage, STAGE_BY_NAME["Production"]).mdp_file + if not mdp_path.exists(): + return None + try: + parsed = parse_mdp(mdp_path) + nsteps_val = get_mdp_value(parsed, "nsteps") + # XTC frequency preferred; fall back to TRR + nstxout_val = get_mdp_value(parsed, "nstxout_compressed") or get_mdp_value( + parsed, "nstxout" + ) + if nsteps_val and nstxout_val: + nsteps = int(nsteps_val) + nstxout = int(nstxout_val) + if nstxout > 0: + return nsteps // nstxout + except Exception as e: + logger.debug(f"Could not parse MDP file: {e}") + return None + + +def _log_dry_run_plan(work_plan: list[dict], config: "ExecutorConfig") -> list[dict]: + """Log the dry-run work plan with resolved gmx commands per stage.""" + from .apps import _build_grompp_script, _build_mdrun_script + from .stages import _extract_resource_hints + + logger.info("=" * 60) + logger.info("DRY RUN - No jobs will be submitted") + logger.info("=" * 60) + + for item in work_plan: + logger.info(f"Simulation: {item['hash']}") + logger.info(f" Directory: {item['sim_dir']}") + if item["stages"]: + restarts = item.get("stage_restarts", {}) + stage_parts = [] + for s in item["stages"]: + label = f"{s} (resume from {restarts[s]})" if s in restarts else s + stage_parts.append(label) + logger.info(f" Stages: {', '.join(stage_parts)}") + + # Resolve and display actual commands per stage + work_dir = str(Path(item["sim_dir"]).resolve()) + stage_config = get_stage_config_or_none(config, item["stages"][0]) + hints = _extract_resource_hints(stage_config) + + for stage_name in item["stages"]: + spec = STAGE_BY_NAME[stage_name] + restart_cpt = restarts.get(stage_name, "") + + logger.info(f" [{spec.name}]") + + if restart_cpt: + # Restart: skip grompp, resume mdrun from checkpoint + logger.info(f" grompp: skipped (restart from {restart_cpt})") + else: + grompp_script = _build_grompp_script( + mdp_file=spec.mdp_file, + gro_file=spec.gro_in, + top_file="topology.top", + tpr_file=spec.tpr_file, + work_dir=work_dir, + ref_file=spec.ref_file or "", + cpt_file=spec.prereq_cpt or "", + maxwarn=spec.maxwarn, + ) + _log_resolved_script(grompp_script, label="grompp") + + mdrun_script = _build_mdrun_script( + deffnm=spec.deffnm, + work_dir=work_dir, + restart_from_cpt=restart_cpt, + ntasks=hints.ntasks, + disable_gpu=hints.disable_gpu, + pme_gpu=spec.supports_pme_gpu, + gro_out=spec.gro_out, + traj_files=spec.traj_files, + gmx_binary=hints.gmx_binary, + ) + _log_resolved_script(mdrun_script, label="mdrun") + else: + logger.info(" Stages: None (all complete)") + + logger.info("=" * 60) + logger.info(f"Executor: {config.provider}") + if hasattr(config, "account"): + logger.info(f"SLURM Account: {config.account}") + logger.info(f"SLURM Partition: {config.partition}") + + return work_plan + + +def _log_resolved_script(script: str, *, label: str) -> None: + """Extract and log the ``$GMX_BIN`` command line from a generated bash script.""" + # Extract the actual gmx command line(s) — lines starting with $GMX_BIN + # or containing "gmx" invocations, skipping boilerplate (set, cd, echo, if) + for line in script.splitlines(): + stripped = line.strip() + if stripped.startswith("$GMX_BIN"): + # Remove trailing shell continuation characters for display + cmd = stripped.rstrip("\\").strip() + logger.info(f" {label}: {cmd}") + return + + # Fallback: show the label with an indicator that resolution failed + logger.info(f" {label}: ") diff --git a/mdfactory/orchestration/stages.py b/mdfactory/orchestration/stages.py new file mode 100644 index 0000000..10f40d1 --- /dev/null +++ b/mdfactory/orchestration/stages.py @@ -0,0 +1,288 @@ +# ABOUTME: Stage composition functions for GROMACS MD pipeline +# ABOUTME: Chains grompp + mdrun for EM → NVT → NPT → Production +"""GROMACS MD simulation stage composition. + +Provides :class:`StageSpec`, :data:`STAGE_REGISTRY`, :data:`STAGE_BY_NAME`, +and the generic :func:`run_stage` that drives all four pipeline stages. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Any, NamedTuple + +if TYPE_CHECKING: + from parsl import AppFuture + + +@dataclass(frozen=True) +class StageSpec: + """Descriptor for a single GROMACS MD pipeline stage. + + Parameters + ---------- + name : str + Human-readable stage name used in CLI and logs (e.g. ``"EM"``). + deffnm : str + Prefix passed to ``gmx mdrun -deffnm`` (e.g. ``"min"``). + mdp_file : str + MDP parameter file name (e.g. ``"em.mdp"``). + gro_in : str + Input structure file for ``gmx grompp -c`` (e.g. ``"system.pdb"``). + gro_out : str + Expected output ``.gro`` file; empty string when the stage writes a + trajectory instead (Production stage). + tpr_file : str + Preprocessed run-input file (e.g. ``"min.tpr"``). + cpt_file : str + Checkpoint file written by mdrun (e.g. ``"min.cpt"``). + prereq_cpt : str or None + Checkpoint from the *prior* stage that is required both as the + ``gmx grompp -t`` input and for workflow-integrity validation. + ``None`` for stages with no velocity continuation (EM, NVT). + traj_files : tuple of str + Accepted trajectory output files checked after mdrun — non-empty only + for the Production stage: ``("prod.xtc", "prod.trr")``. + ref_file : str or None + Reference structure for ``gmx grompp -r`` (position restraints). + ``None`` when no position restraints are used. + maxwarn : int + Maximum grompp warnings to allow (``-maxwarn``). + supports_pme_gpu : bool + ``False`` for EM (``steep`` integrator) — GROMACS rejects ``-pme gpu`` + for non-dynamical integrators. + + """ + + name: str + deffnm: str + mdp_file: str + gro_in: str + gro_out: str + tpr_file: str + cpt_file: str + prereq_cpt: "str | None" + traj_files: "tuple[str, ...]" + ref_file: "str | None" + maxwarn: int + supports_pme_gpu: bool + + +#: Canonical ordered list of all pipeline stages. +STAGE_REGISTRY: list[StageSpec] = [ + StageSpec( + name="EM", + deffnm="min", + mdp_file="em.mdp", + gro_in="system.pdb", + gro_out="min.gro", + tpr_file="min.tpr", + cpt_file="min.cpt", + prereq_cpt=None, + traj_files=(), + ref_file=None, + maxwarn=1, + supports_pme_gpu=False, + ), + StageSpec( + name="NVT", + deffnm="nvt", + mdp_file="nvt.mdp", + gro_in="min.gro", + gro_out="nvt.gro", + tpr_file="nvt.tpr", + cpt_file="nvt.cpt", + prereq_cpt=None, + traj_files=(), + ref_file="min.gro", + maxwarn=1, + supports_pme_gpu=True, + ), + StageSpec( + name="NPT", + deffnm="npt", + mdp_file="npt.mdp", + gro_in="nvt.gro", + gro_out="npt.gro", + tpr_file="npt.tpr", + cpt_file="npt.cpt", + prereq_cpt="nvt.cpt", + traj_files=(), + ref_file="nvt.gro", + maxwarn=2, + supports_pme_gpu=True, + ), + StageSpec( + name="Production", + deffnm="prod", + mdp_file="md.mdp", + gro_in="npt.gro", + gro_out="", + tpr_file="prod.tpr", + cpt_file="prod.cpt", + prereq_cpt="npt.cpt", + traj_files=("prod.xtc", "prod.trr"), + ref_file=None, + maxwarn=2, + supports_pme_gpu=True, + ), +] + +#: Lookup from stage name to :class:`StageSpec` — the single source of truth +#: for all stage-specific file names, dependencies, and flags. +STAGE_BY_NAME: dict[str, StageSpec] = {s.name: s for s in STAGE_REGISTRY} + + +class ResourceHints(NamedTuple): + """Resource hints extracted from a stage executor config for mdrun. + + Parameters + ---------- + ntasks : int + Explicit thread count (0 = auto-detect from SLURM env var). + disable_gpu : bool + ``True`` when the stage config has no GPU resource (``gres`` is + ``None`` or does not contain ``"gpu"``). + gmx_binary : str + GROMACS binary selection: ``"auto"``, ``"gmx"``, or ``"gmx_mpi"``. + + """ + + ntasks: int + disable_gpu: bool + gmx_binary: str # "auto" | "gmx" | "gmx_mpi" + + +def _extract_resource_hints(stage_config: Any) -> ResourceHints: + """Extract mdrun resource hints from a stage-specific executor config. + + Parameters + ---------- + stage_config : SlurmExecutorConfig or None + Per-stage config returned by :meth:`SlurmExecutorConfig.get_stage_config`. + May be ``None`` for local / unconfigured runs. + + Returns + ------- + ResourceHints + Named tuple with ``ntasks``, ``disable_gpu``, and ``gmx_binary``. + + Notes + ----- + Uses :func:`getattr` with safe fallbacks so the function remains + callable with any config object that does not yet declare all fields. + + """ + if stage_config is None: + return ResourceHints(ntasks=0, disable_gpu=True, gmx_binary="auto") + ntasks = getattr(stage_config, "cpus_per_node", 0) or 0 + # Avoid oversubscription when multiple workers share a node: each mdrun + # worker should claim only its fair share of the node's cores. + max_workers = getattr(stage_config, "max_workers_per_node", 1) or 1 + if ntasks > 0 and max_workers > 1: + ntasks = ntasks // max_workers + gres = getattr(stage_config, "gres", None) + disable_gpu = gres is None or "gpu" not in str(gres).lower() + gmx_binary = getattr(stage_config, "gmx_binary", "auto") + return ResourceHints(ntasks=ntasks, disable_gpu=disable_gpu, gmx_binary=gmx_binary) + + +def run_stage( + spec: StageSpec, + sim_dir: Path, + prev_future: "AppFuture | None", + grompp_app, + mdrun_app, + restart_from_cpt: str = "", + stage_config: Any = None, + mdp_override: str | None = None, +) -> "AppFuture": + """Execute a single GROMACS pipeline stage. + + All four pipeline stages share an identical structural skeleton — resolve + working directory, extract resource hints, optionally skip grompp on + restart, submit grompp → mdrun with explicit dependency chaining. Only + the file names and flags differ, so this generic function covers all cases + driven by the :class:`StageSpec` descriptor. + + Parameters + ---------- + spec : StageSpec + Stage descriptor from :data:`STAGE_REGISTRY`. Supplies all + stage-specific file names, flags, and resource hints. + sim_dir : Path + Simulation directory (must be absolute-resolvable). + prev_future : AppFuture or None + Future from the preceding stage. ``None`` when this is the first + stage in the pipeline or when resuming from checkpoint and the + preceding output already exists on disk. + grompp_app : callable + Result of :func:`~mdfactory.orchestration.apps.get_grompp_app`. + mdrun_app : callable + Result of :func:`~mdfactory.orchestration.apps.get_mdrun_app`. + restart_from_cpt : str, optional + Path to an existing checkpoint file. When non-empty the grompp step + is skipped and mdrun is invoked with ``-cpi -append``. + stage_config : SlurmExecutorConfig or None, optional + Per-stage resource config. Resource hints (thread count, GPU disable) + are extracted and forwarded to the mdrun bash app. + mdp_override : str or None, optional + Alternative MDP filename to use instead of ``spec.mdp_file``. + Used by the rescue retry system to pass modified MDP files + (e.g. ``"em_rescue_t1.mdp"``). + + Returns + ------- + AppFuture + Future that resolves when mdrun completes. + + """ + work_dir = str(sim_dir.resolve()) + hints = _extract_resource_hints(stage_config) + + if restart_from_cpt: + # TPR exists from the interrupted run — skip grompp, resume mdrun. + mdrun_inputs = [prev_future] if prev_future is not None else [] + return mdrun_app( + deffnm=spec.deffnm, + work_dir=work_dir, + restart_from_cpt=restart_from_cpt, + ntasks=hints.ntasks, + disable_gpu=hints.disable_gpu, + pme_gpu=spec.supports_pme_gpu, + gmx_binary=hints.gmx_binary, + gro_out=spec.gro_out, + traj_files=spec.traj_files, + inputs=mdrun_inputs, + ) + + grompp_inputs = [prev_future] if prev_future is not None else [] + + grompp_kwargs: dict[str, Any] = { + "mdp_file": mdp_override if mdp_override else spec.mdp_file, + "gro_file": spec.gro_in, + "top_file": "topology.top", + "tpr_file": spec.tpr_file, + "work_dir": work_dir, + "maxwarn": spec.maxwarn, + "inputs": grompp_inputs, + } + if spec.ref_file: + grompp_kwargs["ref_file"] = spec.ref_file + if spec.prereq_cpt: + grompp_kwargs["cpt_file"] = spec.prereq_cpt + + grompp_fut = grompp_app(**grompp_kwargs) + + return mdrun_app( + deffnm=spec.deffnm, + work_dir=work_dir, + ntasks=hints.ntasks, + disable_gpu=hints.disable_gpu, + pme_gpu=spec.supports_pme_gpu, + gmx_binary=hints.gmx_binary, + gro_out=spec.gro_out, + traj_files=spec.traj_files, + inputs=[grompp_fut], + ) diff --git a/mdfactory/orchestration/tui.py b/mdfactory/orchestration/tui.py new file mode 100644 index 0000000..2a32896 --- /dev/null +++ b/mdfactory/orchestration/tui.py @@ -0,0 +1,1010 @@ +# ABOUTME: Interactive SLURM configuration wizard using questionary. +# ABOUTME: Leverages cluster autodiscovery to guide users through executor setup. +"""Interactive SLURM configuration wizard. + +Provides a terminal-based wizard that queries the local SLURM cluster +(via :func:`~mdfactory.performance.cluster.discover_cluster`) and guides +the user through selecting accounts, partitions, and resource limits. +The result is a :class:`~mdfactory.orchestration.config.SlurmExecutorConfig` +that can be saved as YAML and used with Parsl-based workflows. + +The wizard is structured into three distinct sections: + +1. **SLURM Allocation** — cluster infrastructure (account, partition, resources) +2. **Execution Environment** — project/tool setup (modules, pixi, extra init) +3. **Per-Stage Tuning** — simulation-specific overrides (cpus, gres, gmx_binary) +""" + +from __future__ import annotations + +from pathlib import Path + +import yaml +from rich.console import Console +from rich.panel import Panel +from rich.rule import Rule + +from mdfactory.orchestration.config import SlurmExecutorConfig +from mdfactory.orchestration.environment import EnvironmentConfig +from mdfactory.performance.cluster import ClusterInfo, Partition, discover_cluster + +console = Console() + + +def _import_questionary(): + """Import questionary with a clear error message if not installed. + + ``questionary`` ships in the ``[parsl]`` optional extra. Importing it + lazily (rather than at module level) means ``import mdfactory.orchestration`` + succeeds for users who never touch the TUI, matching the lazy-import + pattern used for ``parsl`` and ``rich``. + + Returns + ------- + module + The questionary module. + + Raises + ------ + ImportError + If questionary is not installed. + + """ + try: + import questionary + except ImportError as exc: + raise ImportError( + "questionary is required for the SLURM TUI. " + "Install with: pip install 'mdfactory[parsl]'" + ) from exc + return questionary + + +def _detect_gromacs_modules() -> list[str]: + """Discover available GROMACS modules via ``module avail``. + + Returns + ------- + list[str] + Module names (e.g. ``["gromacs/2024.4", "gromacs/2023.5"]``), + or an empty list if ``module`` is not available or no GROMACS + modules are found. + """ + import subprocess + + try: + # 'module avail' writes to stderr (by design) + result = subprocess.run( + ["bash", "-lc", "module avail gromacs 2>&1"], + capture_output=True, + text=True, + timeout=10, + check=False, + ) + output = result.stdout + result.stderr + # Parse module names — typical format: "gromacs/2024.4" + modules = [] + for token in output.split(): + # Strip trailing markers like "(default)", "(D)", or "*" + clean = token.strip().rstrip("*") + if "(" in clean: + clean = clean[: clean.index("(")] + # Match patterns like "gromacs/2024" or "gromacs/2024.4-gpu" + if clean.lower().startswith("gromacs/") and "/" in clean: + if clean not in modules: + modules.append(clean) + return modules + except Exception: + return [] + + +def _prompt_gromacs_modules() -> list[str]: + """Prompt the user to select GROMACS modules for compute workers. + + Only prompts if ``gmx``/``gmx_mpi`` is not on the current PATH. + If GROMACS is already available, returns an empty list. + + Returns + ------- + list[str] + Module names to load (e.g. ``["gromacs/2024.4"]``), or ``[]``. + """ + import shutil + + # If gmx is already on PATH, no action needed + if shutil.which("gmx") or shutil.which("gmx_mpi"): + return [] + + questionary = _import_questionary() + + # Try to discover modules + modules = _detect_gromacs_modules() + + console.print("\n [yellow]⚠ GROMACS (gmx/gmx_mpi) not found in current PATH.[/yellow]") + console.print(" Compute workers need GROMACS to run simulations.\n") + + _CUSTOM = "Custom module name…" + _SKIP = "Skip (I'll handle it in extra_init or PATH)" + + if modules: + choices = [*modules, _CUSTOM, _SKIP] + selected = _require( + questionary.select( + "GROMACS module to load on workers:", + choices=choices, + default=modules[0], + ).ask(), + "gromacs module", + ) + else: + # No modules found — offer manual entry or skip + selected = _require( + questionary.select( + "GROMACS module to load on workers:", + choices=[_CUSTOM, _SKIP], + ).ask(), + "gromacs module", + ) + + if selected == _SKIP: + return [] + elif selected == _CUSTOM: + mod_name = _require( + questionary.text( + "Module name (e.g. 'gromacs/2024.4-gpu'):", + ).ask(), + "gromacs module name", + ) + return [mod_name.strip()] if mod_name.strip() else [] + else: + # User selected a discovered module + return [selected] + + +class UserCancelledError(Exception): + """Raised when the user cancels an interactive prompt.""" + + +def _require(value: str | None, label: str) -> str: + """Return *value* or raise if the user cancelled the prompt. + + Parameters + ---------- + value : str or None + Return value from a ``questionary`` ``.ask()`` call. + label : str + Human-readable label used in the error message. + + Returns + ------- + str + The validated, non-None value. + + Raises + ------ + UserCancelledError + If *value* is None (user pressed Ctrl-C / Ctrl-D). + """ + if value is None: + raise UserCancelledError(f"Prompt cancelled at: {label}") + return value + + +# --------------------------------------------------------------------------- +# Partition / node helpers +# --------------------------------------------------------------------------- + + +def _format_node_type_summary(partition: Partition) -> str: + """Build a one-line summary of node hardware for a partition label. + + Parameters + ---------- + partition : Partition + Partition whose node types to summarise. + + Returns + ------- + str + Human-readable summary, e.g. ``"96 cpu, 512 GB, 4×a100"`` + """ + parts: list[str] = [] + for nt in partition.node_types: + items = [f"{nt.cpus} cpu", f"{nt.memory_mb // 1024} GB"] + for count, gpu_type in nt.gpu_specs: + gpu_label = f"{count}×{gpu_type}" if gpu_type else f"{count}×gpu" + items.append(gpu_label) + parts.append(", ".join(items)) + return " | ".join(parts) + + +def _partition_has_gpus(partition: Partition) -> bool: + """Check whether any node type in *partition* has GPUs.""" + return any(len(nt.gpu_specs) > 0 for nt in partition.node_types) + + +def _suggest_gres(partition: Partition) -> str: + """Suggest a ``--gres`` string from the first GPU-equipped node type. + + Parameters + ---------- + partition : Partition + Selected partition. + + Returns + ------- + str + Suggested GRES string, e.g. ``"gpu:a100:1"``, or empty string. + """ + for nt in partition.node_types: + for count, gpu_type in nt.gpu_specs: + if gpu_type: + return f"gpu:{gpu_type}:1" + return "gpu:1" + return "" + + +def _suggest_mem(partition: Partition) -> str: + """Suggest a ``--mem`` value from the first node type. + + Parameters + ---------- + partition : Partition + Selected partition. + + Returns + ------- + str + Suggested memory string in GB, e.g. ``"64G"``. + """ + if partition.node_types: + mem_gb = partition.node_types[0].memory_mb // 1024 + return f"{mem_gb}G" + return "16G" + + +# --------------------------------------------------------------------------- +# Selection helpers +# --------------------------------------------------------------------------- + + +def _select_with_custom( + message: str, + choices: list[str], + default: str = "", +) -> str: + """Present a selection list with a "Custom…" escape hatch. + + Parameters + ---------- + message : str + Prompt message shown to the user. + choices : list[str] + Pre-defined choices (e.g. ``["1h", "2h", "4h"]``). + default : str + Pre-selected value in the list. + + Returns + ------- + str + The selected or custom-entered value. + + Raises + ------ + UserCancelledError + If the user cancels. + """ + questionary = _import_questionary() + _CUSTOM = "Custom…" + all_choices = [*choices, _CUSTOM] + selected = _require( + questionary.select(message, choices=all_choices, default=default).ask(), + message, + ) + if selected == _CUSTOM: + return _require( + questionary.text(f"{message} (enter value):").ask(), + message, + ) + return selected + + +# --------------------------------------------------------------------------- +# Stage override prompts +# --------------------------------------------------------------------------- + + +def _resolve_stages(stages: tuple[str, ...] | None) -> tuple[str, ...]: + """Resolve *stages* to a concrete tuple of stage names. + + Parameters + ---------- + stages : tuple[str, ...] or None + Explicit stage names, or ``None`` to use the full pipeline from + :data:`~mdfactory.orchestration.stages.STAGE_REGISTRY`. + + Returns + ------- + tuple[str, ...] + Concrete stage names. + """ + if stages is not None: + return stages + from mdfactory.orchestration.stages import STAGE_REGISTRY + + return tuple(s.name for s in STAGE_REGISTRY) + + +def _prompt_stage_overrides( + common_cpus: int, + common_gres: str | None, + common_gmx: str, + stages: tuple[str, ...], +) -> dict[str, dict]: + """Prompt the user for per-stage resource overrides. + + Only prompts when there are multiple stages *and* per-stage + differentiation is meaningful (e.g. GPU selected but EM cannot use + it). Skipped entirely when fewer than 2 stages are configured. + + Parameters + ---------- + common_cpus : int + The ``cpus_per_node`` value from the common configuration. + common_gres : str or None + The ``gres`` value from the common configuration. + common_gmx : str + The ``gmx_binary`` value from the common configuration. + stages : tuple[str, ...] + The stages that will actually run. Empty or single-element + tuples cause an early return (no overrides needed). + + Returns + ------- + dict[str, dict] + Stage overrides dict ready for ``SlurmExecutorConfig``. + Empty dict if the user declines, no deviations are entered, + or overrides are not applicable. + + Raises + ------ + UserCancelledError + If the user cancels any prompt. + """ + # No point configuring per-stage overrides for a single-stage workflow + if len(stages) < 2: + return {} + + questionary = _import_questionary() + has_gpu = common_gres is not None + has_em = "EM" in stages + + # --- EM/GPU information panel --- + if has_gpu and has_em: + console.print( + Panel( + "[bold]GROMACS energy minimisation (EM) does not support GPU acceleration.[/bold]\n" + "EM uses steepest-descent/conjugate-gradient integrators which have no GPU\n" + "code path. Consider configuring a CPU-only override for the EM stage with\n" + "more CPU cores to compensate.", + title="ℹ Stage hint", + border_style="blue", + ) + ) + + # Only proactively suggest overrides when there's a reason (GPU + EM mismatch) + default_configure = has_gpu and has_em + configure = questionary.confirm( + "Configure per-stage resource overrides?", + default=default_configure, + ).ask() + if configure is None: + raise UserCancelledError("Prompt cancelled at: stage overrides confirm") + if not configure: + return {} + + # --- Select stages to override --- + stage_choices = [ + questionary.Choice(title=stage, value=stage, checked=(stage == "EM" and has_gpu)) + for stage in stages + ] + selected_stages = questionary.checkbox( + "Select stages to customise:", + choices=stage_choices, + ).ask() + if selected_stages is None: + raise UserCancelledError("Prompt cancelled at: stage selection") + if not selected_stages: + return {} + + # --- Prompt for each selected stage --- + stage_overrides: dict[str, dict] = {} + for stage in selected_stages: + console.print(f"\n [bold]Configure {stage} stage:[/bold]") + + # Suggest higher CPU count for EM when GPU is the common config + em_cpu_default = str(common_cpus * 2) if (stage == "EM" and has_gpu) else str(common_cpus) + # Suggest empty gres for EM when GPU is common + em_gres_default = "" if (stage == "EM" and has_gpu) else (common_gres or "") + + cpus_input = _require( + questionary.text( + f" {stage} — CPUs per node:", + default=em_cpu_default, + ).ask(), + f"{stage} cpus_per_node", + ) + cpus_val = int(cpus_input) + + gres_input = _require( + questionary.text( + f" {stage} — GRES (leave empty for CPU-only):", + default=em_gres_default, + ).ask(), + f"{stage} gres", + ) + gres_val: str | None = gres_input.strip() or None + + gmx_input = _require( + questionary.text( + f" {stage} — gmx_binary:", + default=common_gmx, + ).ask(), + f"{stage} gmx_binary", + ) + gmx_val = gmx_input.strip() + + # Only store fields that differ from common values + overrides: dict = {} + if cpus_val != common_cpus: + overrides["cpus_per_node"] = cpus_val + if gres_val != common_gres: + overrides["gres"] = gres_val + if gmx_val != common_gmx: + overrides["gmx_binary"] = gmx_val + + if overrides: + stage_overrides[stage] = overrides + + return stage_overrides + + +# --------------------------------------------------------------------------- +# Environment section prompts +# --------------------------------------------------------------------------- + + +def _prompt_environment( + *, stages: tuple[str, ...] | None = None, ignore_global: bool = False +) -> EnvironmentConfig: + """Prompt the user for execution-environment configuration. + + If a global environment config exists (written by + ``mdfactory config environment``), it is used directly without + prompting. Otherwise, auto-detects pixi/conda/venv, prompts for + GROMACS modules (if needed for simulate workflows), and offers an + extra_init escape hatch. + + Parameters + ---------- + stages : tuple[str, ...] or None, optional + The simulation stages. Pass ``()`` to skip GROMACS prompts + (e.g. for ``build``). ``None`` uses the full pipeline. + ignore_global : bool, optional + If True, skip loading the global config and always run the + interactive wizard. Used by ``configure_and_save_environment`` + so users can update an existing global config. + + Returns + ------- + EnvironmentConfig + Structured environment configuration. + """ + from mdfactory.orchestration.environment import get_global_environment_path + + if not ignore_global: + global_env = EnvironmentConfig.load_global() + else: + global_env = None + if global_env is not None: + path = get_global_environment_path() + console.print(f" ✓ Using saved environment config from {path}") + if global_env.modules: + console.print(f" modules: {', '.join(global_env.modules)}") + if global_env.pixi_manifest: + console.print(f" pixi: {global_env.pixi_manifest}") + elif global_env.conda_env: + console.print(f" conda: {global_env.conda_env}") + return global_env + + questionary = _import_questionary() + + # Start with auto-detection + env = EnvironmentConfig.detect() + + # Show what was detected + if env.pixi_manifest: + console.print(f" ✓ Detected pixi environment at {env.pixi_manifest}") + elif env.conda_env: + console.print(f" ✓ Detected conda environment: {env.conda_env}") + elif env.venv_path: + console.print(f" ✓ Detected virtualenv at {env.venv_path}") + else: + console.print(" ℹ No Python environment auto-detected.") + + # GROMACS module prompts (skip for build workflows) + modules: list[str] = [] + if stages != (): + modules = _prompt_gromacs_modules() + + # Extra init commands + extra_init = _require( + questionary.text( + "Additional init commands (leave empty to skip):", + default="", + ).ask(), + "extra_init", + ) + + # Build the final config, merging auto-detected + user input + return EnvironmentConfig( + modules=modules, + pixi_manifest=env.pixi_manifest, + conda_env=env.conda_env if not env.pixi_manifest else None, + venv_path=env.venv_path if not env.pixi_manifest and not env.conda_env else None, + extra_init=extra_init.strip(), + ) + + +# --------------------------------------------------------------------------- +# Interactive prompts — cluster-assisted +# --------------------------------------------------------------------------- + + +def _configure_with_cluster( + cluster: ClusterInfo, + *, + stages: tuple[str, ...] | None = None, +) -> SlurmExecutorConfig: + """Run the interactive wizard using autodiscovered cluster info. + + Parameters + ---------- + cluster : ClusterInfo + Cluster information from ``discover_cluster()``. + stages : tuple[str, ...] or None, optional + The simulation stages that will run. Determines whether per-stage + override prompts are shown and which stages are offered. Pass an + empty tuple to skip stage-override prompts entirely (e.g. for + ``build``). ``None`` (default) uses the full simulation pipeline + from :data:`~mdfactory.orchestration.stages.STAGE_REGISTRY`. + + Returns + ------- + SlurmExecutorConfig + Fully populated SLURM executor config. + + Raises + ------ + UserCancelledError + If the user cancels any prompt. + """ + questionary = _import_questionary() + + # ━━━ Section 1: SLURM Allocation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + console.print(Rule("SLURM Allocation", style="bold cyan")) + + # --- Account --- + if cluster.accounts: + account = _require( + questionary.select( + "SLURM account:", + choices=cluster.accounts, + default=cluster.default_account or cluster.accounts[0], + ).ask(), + "account", + ) + else: + account = _require( + questionary.text("SLURM account (no accounts discovered):").ask(), + "account", + ) + + # --- Partition --- + up_partitions = [p for p in cluster.partitions if p.state == "up"] + if not up_partitions: + console.print("⚠ No partitions in 'up' state — showing all partitions.") + up_partitions = list(cluster.partitions) + + if not up_partitions: + raise UserCancelledError("No partitions available on the cluster.") + + partition_choices = [ + questionary.Choice( + title=f"{p.name} ({p.total_nodes} nodes — {_format_node_type_summary(p)})", + value=p.name, + ) + for p in up_partitions + ] + default_partition = next((p.name for p in up_partitions if p.is_default), up_partitions[0].name) + + partition_name = _require( + questionary.select( + "SLURM partition:", + choices=partition_choices, + default=default_partition, + ).ask(), + "partition", + ) + + partition = next(p for p in up_partitions if p.name == partition_name) + + # --- Display node types --- + console.print(f"\n Partition '{partition_name}' node types:") + for nt in partition.node_types: + gpu_info = "" + if nt.gpu_specs: + gpu_parts = [f"{c}×{t}" if t else f"{c}×gpu" for c, t in nt.gpu_specs] + gpu_info = f", GPUs: {', '.join(gpu_parts)}" + console.print( + f" {nt.count} nodes — {nt.cpus} CPUs, {nt.memory_mb // 1024} GB RAM{gpu_info}" + ) + console.print() + + # --- Walltime --- + walltime = _select_with_custom( + "Walltime (--time):", + choices=["30m", "1h", "2h", "4h", "8h", "12h", "1d"], + default="2h", + ) + + # --- CPUs per node --- + max_cpus = partition.node_types[0].cpus if partition.node_types else 128 + cpu_choices = [str(c) for c in [1, 2, 4, 8, 16, 32, 64, max_cpus] if c <= max_cpus] + # Deduplicate (max_cpus might equal one of the fixed values) + cpu_choices = list(dict.fromkeys(cpu_choices)) + cpus_per_node = int( + _select_with_custom( + "CPUs per node (--cpus-per-task):", + choices=cpu_choices, + default="4" if "4" in cpu_choices else cpu_choices[0], + ) + ) + + # --- GPU --- + gres: str | None = None + if _partition_has_gpus(partition): + gres_default = _suggest_gres(partition) + gres_input = _require( + questionary.text( + "GRES (--gres), leave empty to skip:", + default=gres_default, + ).ask(), + "gres", + ) + gres = gres_input.strip() or None + + # --- Memory --- + mem_gb = partition.node_types[0].memory_mb // 1024 if partition.node_types else 128 + mem_choices = [f"{m}G" for m in [10, 20, 50, 100, 200, 500, mem_gb] if m <= mem_gb] + mem_choices = list(dict.fromkeys(mem_choices)) + mem_selected = _select_with_custom( + "Memory per node (--mem):", + choices=mem_choices, + default="20G" if "20G" in mem_choices else mem_choices[0], + ) + mem: str | None = mem_selected.strip() or None + + # --- Max blocks --- + max_blocks = int( + _select_with_custom( + "Max simultaneous SLURM jobs (max_blocks):", + choices=["1", "2", "4", "8", "16", "32"], + default="4", + ) + ) + + # --- QOS --- + qos: str | None = None + if cluster.qos_policies: + use_qos = questionary.confirm("Configure QOS?", default=False).ask() + if use_qos is None: + raise UserCancelledError("Prompt cancelled at: qos confirm") + if use_qos: + qos = _require( + questionary.select("QOS policy:", choices=cluster.qos_policies).ask(), + "qos", + ) + + # --- Constraint --- + constraint_input = _require( + questionary.text( + "Node feature constraint (--constraint), leave empty to skip:", + default="", + ).ask(), + "constraint", + ) + constraint: str | None = constraint_input.strip() or None + + # ━━━ Section 2: Execution Environment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + console.print(Rule("Execution Environment", style="bold green")) + + environment = _prompt_environment(stages=stages) + + # ━━━ Section 3: Per-Stage Tuning ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + console.print(Rule("Per-Stage Tuning", style="bold yellow")) + + resolved_stages = _resolve_stages(stages) + stage_overrides = _prompt_stage_overrides( + common_cpus=cpus_per_node, + common_gres=gres, + common_gmx="auto", + stages=resolved_stages, + ) + + return SlurmExecutorConfig( + account=account, + partition=partition_name, + walltime=walltime, + cpus_per_node=cpus_per_node, + gres=gres, + mem=mem, + qos=qos, + constraint=constraint, + max_blocks=max_blocks, + environment=environment, + stage_overrides=stage_overrides, + ) + + +# --------------------------------------------------------------------------- +# Interactive prompts — manual fallback +# --------------------------------------------------------------------------- + + +def _configure_manual(*, stages: tuple[str, ...] | None = None) -> SlurmExecutorConfig: + """Run the interactive wizard with manual text entry (no cluster info). + + Parameters + ---------- + stages : tuple[str, ...] or None, optional + The simulation stages that will run. Pass an empty tuple to skip + stage-override prompts entirely. ``None`` (default) uses the full + simulation pipeline. + + Returns + ------- + SlurmExecutorConfig + Fully populated SLURM executor config. + + Raises + ------ + UserCancelledError + If the user cancels any prompt. + """ + questionary = _import_questionary() + + # ━━━ Section 1: SLURM Allocation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + console.print(Rule("SLURM Allocation", style="bold cyan")) + + account = _require(questionary.text("SLURM account:").ask(), "account") + partition = _require(questionary.text("SLURM partition:", default="gpu").ask(), "partition") + walltime = _require(questionary.text("Walltime (--time):", default="2:00:00").ask(), "walltime") + cpus_per_node = int( + _require(questionary.text("CPUs per node:", default="12").ask(), "cpus_per_node") + ) + + gres_input = _require( + questionary.text("GRES (--gres), leave empty to skip:", default="").ask(), + "gres", + ) + gres: str | None = gres_input.strip() or None + + mem_input = _require( + questionary.text("Memory per node (--mem), leave empty to skip:", default="").ask(), + "mem", + ) + mem: str | None = mem_input.strip() or None + + qos_input = _require( + questionary.text("QOS (--qos), leave empty to skip:", default="").ask(), + "qos", + ) + qos: str | None = qos_input.strip() or None + + max_blocks = int( + _require( + questionary.text("Max simultaneous SLURM jobs (max_blocks):", default="4").ask(), + "max_blocks", + ) + ) + + # ━━━ Section 2: Execution Environment ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + console.print(Rule("Execution Environment", style="bold green")) + + environment = _prompt_environment(stages=stages) + + # ━━━ Section 3: Per-Stage Tuning ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + console.print(Rule("Per-Stage Tuning", style="bold yellow")) + + resolved_stages = _resolve_stages(stages) + stage_overrides = _prompt_stage_overrides( + common_cpus=cpus_per_node, + common_gres=gres, + common_gmx="auto", + stages=resolved_stages, + ) + + return SlurmExecutorConfig( + account=account, + partition=partition, + walltime=walltime, + cpus_per_node=cpus_per_node, + gres=gres, + mem=mem, + qos=qos, + max_blocks=max_blocks, + environment=environment, + stage_overrides=stage_overrides, + ) + + +# --------------------------------------------------------------------------- +# Public API +# --------------------------------------------------------------------------- + + +def configure_slurm_interactive( + *, + stages: tuple[str, ...] | None = None, +) -> SlurmExecutorConfig: + """Interactive SLURM configuration wizard. + + Attempts to autodiscover the SLURM cluster. If discovery succeeds, + the wizard presents select menus populated with real partitions, + accounts, and hardware specs. Otherwise it falls back to free-text + prompts. + + Parameters + ---------- + stages : tuple[str, ...] or None, optional + The simulation stages that will run. Controls whether per-stage + resource override prompts are shown and which stages are offered. + Pass an empty tuple to skip stage-override prompts entirely + (e.g. for ``build``). ``None`` (default) uses the full + simulation pipeline from + :data:`~mdfactory.orchestration.stages.STAGE_REGISTRY`. + + Returns + ------- + SlurmExecutorConfig + A validated SLURM executor config ready for use with Parsl. + + Raises + ------ + UserCancelledError + If the user cancels any prompt (Ctrl-C / Ctrl-D). + """ + questionary = _import_questionary() + console.print("Querying SLURM cluster...") + cluster = discover_cluster() + + if cluster is not None: + console.print( + f"✓ Cluster discovered: {len(cluster.partitions)} partitions, " + f"{len(cluster.accounts)} accounts\n" + ) + return _configure_with_cluster(cluster, stages=stages) + + console.print("⚠ SLURM not detected (sinfo unavailable). Falling back to manual entry.\n") + proceed = questionary.confirm("Enter SLURM configuration manually?", default=True).ask() + if not proceed: + raise UserCancelledError("User declined manual SLURM configuration.") + return _configure_manual(stages=stages) + + +def save_slurm_config_yaml(config: SlurmExecutorConfig, path: Path) -> None: + """Write a SLURM executor config to a YAML file. + + Parameters + ---------- + config : SlurmExecutorConfig + The config to serialise. + path : Path + Destination file path. Parent directories are created if needed. + """ + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + + data = config.model_dump(exclude_none=True) + + # Clean up empty fields in environment section + if "environment" in data: + env_data = data["environment"] + if not env_data.get("modules"): + env_data.pop("modules", None) + if not env_data.get("extra_init"): + env_data.pop("extra_init", None) + # Remove environment section entirely if empty after cleanup + if not env_data: + data.pop("environment") + + # Omit empty stage_overrides for cleaner YAML output + if not data.get("stage_overrides"): + data.pop("stage_overrides", None) + + with open(path, "w") as fh: + yaml.dump(data, fh, default_flow_style=False, sort_keys=False) + + console.print(f"✓ SLURM config written to {path}") + + +def configure_and_save_slurm() -> SlurmExecutorConfig: + """Run the interactive wizard and save the result to YAML. + + Combines :func:`configure_slurm_interactive` with a file-save prompt. + + Returns + ------- + SlurmExecutorConfig + The config that was saved. + + Raises + ------ + UserCancelledError + If the user cancels any prompt. + """ + questionary = _import_questionary() + try: + config = configure_slurm_interactive() + except UserCancelledError: + console.print("Configuration cancelled.") + raise + + save_path = _require( + questionary.text("Save config to:", default="slurm_executor.yaml").ask(), + "save path", + ) + + save_slurm_config_yaml(config, Path(save_path)) + return config + + +def configure_and_save_environment() -> EnvironmentConfig: + """Run the environment wizard and save to the global config location. + + Prompts for execution environment (modules, pixi/conda/venv, extra init) + and saves the result to ``~/.config/mdfactory/environment.yaml`` (or the + platform-appropriate config directory). + + This global environment config is automatically loaded by + :meth:`~mdfactory.orchestration.config.ExecutorConfig.from_yaml` when a + SLURM executor YAML does not contain an ``environment:`` section. + + Returns + ------- + EnvironmentConfig + The environment config that was saved. + + Raises + ------ + UserCancelledError + If the user cancels any prompt. + """ + from mdfactory.orchestration.environment import get_global_environment_path + + console.print(Rule("Execution Environment", style="bold green")) + + try: + env = _prompt_environment(stages=None, ignore_global=True) + except UserCancelledError: + console.print("Configuration cancelled.") + raise + + save_path = get_global_environment_path() + env.save_yaml(save_path) + console.print(f"✓ Environment config written to {save_path}") + console.print( + " This will be used automatically when SLURM configs don't include an environment section." + ) + return env diff --git a/mdfactory/parametrize.py b/mdfactory/parametrize.py index e18937c..7ce61de 100644 --- a/mdfactory/parametrize.py +++ b/mdfactory/parametrize.py @@ -269,6 +269,11 @@ def parametrize_smirnoff_gromacs( if not itp_path.is_file(): workdir.mkdir(parents=True, exist_ok=True) molecule = species.openff_molecule + # Force molecule name to "SOL" for consistent Interchange output, + # regardless of the user-provided resname (e.g. "WAT"). + # This ensures atom types are always SOL_0, SOL_1, ... and the + # generated ITP is always SOL_SOL.itp. + molecule.name = "SOL" topology = Topology.from_molecules([molecule]) forcefield = ForceField(water_model) interchange = Interchange.from_smirnoff(force_field=forcefield, topology=topology) @@ -280,6 +285,9 @@ def parametrize_smirnoff_gromacs( generated_itp = workdir / "SOL_SOL.itp" if generated_itp.is_file(): generated_itp.rename(itp_path) + # Remove stale params if atomtypes were regenerated + if params_itp_path.is_file(): + params_itp_path.unlink() if top_path.is_file() and atomtypes_path.is_file() and not params_itp_path.is_file(): _build_smirnoff_parameter_itp(top_path, atomtypes_path, params_itp_path) diff --git a/mdfactory/performance/__init__.py b/mdfactory/performance/__init__.py new file mode 100644 index 0000000..f8986a1 --- /dev/null +++ b/mdfactory/performance/__init__.py @@ -0,0 +1,18 @@ +# ABOUTME: HPC performance optimization package for mdfactory. +# ABOUTME: Cluster autodiscovery, CPU affinity, benchmarking, and GPU MPS management. +"""HPC performance optimization utilities. + +Modules +------- +cluster + SLURM cluster autodiscovery — query partitions, node types, accounts, and QOS. +slurm_config + SLURM configuration models shared across all submission backends. + ``BaseSlurmConfig`` provides 3-tier autodiscovery for account and partition. + ``SlurmConfig`` is the submitit backend configuration. +""" + +from mdfactory.performance import cluster +from mdfactory.performance.slurm_config import BaseSlurmConfig, SlurmConfig + +__all__ = ["cluster", "BaseSlurmConfig", "SlurmConfig"] diff --git a/mdfactory/performance/cluster.py b/mdfactory/performance/cluster.py new file mode 100644 index 0000000..cf7e81f --- /dev/null +++ b/mdfactory/performance/cluster.py @@ -0,0 +1,674 @@ +# ABOUTME: SLURM cluster autodiscovery — query partitions, node types, accounts, QOS. +# ABOUTME: Parses sinfo/sacctmgr output into structured dataclasses for resource-aware scheduling. +"""SLURM cluster autodiscovery. + +Query the local SLURM scheduler and return a structured representation of +available resources (partitions, node types, accounts, QOS policies, GPU types). + +Functions +--------- +discover_cluster + Main entry point — returns ``ClusterInfo`` or ``None`` if SLURM is unavailable. +select_partition + Heuristic partition selection given resource requirements. + +Examples +-------- +>>> from mdfactory.performance.cluster import discover_cluster, select_partition +>>> cluster = discover_cluster() +>>> if cluster is not None: +... gpu_part = select_partition(cluster, needs_gpu=True) +""" + +from __future__ import annotations + +import os +import shutil + +from pydantic import BaseModel, ConfigDict, Field + +from mdfactory.utils.utilities import run_command + +# Single-element list used as a mutable cache cell (avoids 'global', ruff PLW0603). +# _CLUSTER_CACHE_SENTINEL = not yet queried; None = sinfo absent; ClusterInfo = success. +# Transient sinfo failures are not cached so the next call retries. +_CLUSTER_CACHE_SENTINEL = object() +_cluster_cache: list = [_CLUSTER_CACHE_SENTINEL] + + +class NodeType(BaseModel): + """Hardware specification of a node type within a partition. + + Parameters + ---------- + cpus : int + Number of CPU cores per node. + memory_mb : int + Memory in megabytes per node. + gpu_specs : tuple of (int, str | None) + GPU specifications as (count, type) tuples. Empty if CPU-only. + Multiple entries represent different GPU types on the same node. + Type can be None if SLURM reports GPU count without type info. + features : tuple of str + SLURM feature/constraint tags on this node type (immutable). + count : int + Number of nodes with this exact configuration. + """ + + model_config = ConfigDict(frozen=True) + + cpus: int + memory_mb: int + gpu_specs: tuple[tuple[int, str | None], ...] = Field(default_factory=tuple) + features: tuple[str, ...] = Field(default_factory=tuple) + count: int = 1 + + +class Partition(BaseModel): + """A SLURM partition with its node types and limits. + + Parameters + ---------- + name : str + Partition name (e.g., ``"gpu"``, ``"cpu"``). + state : str + Partition-level state: ``"up"`` if any node is schedulable, otherwise + the last observed unhealthy state (e.g., ``"down"``, ``"drained"``). + max_time : str + Maximum walltime (SLURM format, e.g., ``"3-00:00:00"``). + default_time : str + Default walltime assigned when user does not specify one. + Populated from sinfo ``%L``; equals ``max_time`` on legacy output. + node_types : list of NodeType + Distinct hardware configurations available in this partition. + total_nodes : int + Total number of nodes in the partition. + is_default : bool + Whether this is the cluster's default partition. + """ + + model_config = ConfigDict(frozen=True) + + name: str + state: str + max_time: str + default_time: str + node_types: list[NodeType] = Field(default_factory=list) + total_nodes: int = 0 + is_default: bool = False + + +class ClusterInfo(BaseModel): + """Structured representation of a SLURM cluster's resources. + + Parameters + ---------- + partitions : list of Partition + All discovered partitions. + accounts : list of str + SLURM accounts available to the current user. + qos_policies : list of str + Available QOS policy names. + default_account : str or None + The user's default account, if determinable. + """ + + model_config = ConfigDict(frozen=True) + + partitions: list[Partition] = Field(default_factory=list) + accounts: list[str] = Field(default_factory=list) + qos_policies: list[str] = Field(default_factory=list) + default_account: str | None = None + + +def _parse_gres(gres_str: str) -> list[tuple[int, str | None]]: + """Parse SLURM GRES string to extract GPU count and type for all GPU entries. + + Parameters + ---------- + gres_str : str + GRES field from sinfo (e.g., ``"gpu:a100:4"``, ``"gpu:2"``, ``"(null)"``). + May contain multiple GPU entries separated by commas. + + Returns + ------- + list of tuple (int, str or None) + List of (gpu_count, gpu_type) for each GPU entry. Empty list when no GPUs. + + Examples + -------- + >>> _parse_gres("gpu:a100:4") + [(4, 'a100')] + >>> _parse_gres("gpu:b200:8(S:0-1),gpu:1g.23gb:7(S:1)") + [(8, 'b200'), (7, '1g.23gb')] + >>> _parse_gres("(null)") + [] + """ + if not gres_str or gres_str == "(null)": + return [] + + gpu_entries = [] + + # Handle multiple GRES entries separated by commas + for raw_entry in gres_str.split(","): + entry = raw_entry.strip() + if not entry.startswith("gpu"): + continue + + # Strip socket binding suffix like "(S:0-1)" or "(IDX:0-3)" + if "(" in entry: + entry = entry.split("(")[0] + + parts = entry.split(":") + if len(parts) == 3: + # gpu:type:count + _, gpu_type, count_str = parts + try: + gpu_entries.append((int(count_str), gpu_type)) + except ValueError: + continue # skip malformed GRES entry + elif len(parts) == 2: + # gpu:count (no type specified) + _, count_str = parts + # Check if second part is a number or a type + try: + count = int(count_str) + gpu_entries.append((count, None)) + except ValueError: + # gpu:type with implicit count of 1 + gpu_entries.append((1, count_str)) + + return gpu_entries + + +def _parse_time_limit(time_str: str) -> str: + """Normalize SLURM time limit strings. + + Parameters + ---------- + time_str : str + Time limit from sinfo (e.g., ``"3-00:00:00"``, ``"infinite"``, ``"2:00:00"``). + + Returns + ------- + str + Cleaned time string. + """ + if not time_str or time_str == "n/a": + return "unknown" + return time_str.strip() + + +def _parse_memory_mb(mem_str: str) -> int: + """Parse memory string from sinfo to megabytes. + + Parameters + ---------- + mem_str : str + Memory field from sinfo (numeric, in MB by default). + + Returns + ------- + int + Memory in MB. Returns 0 on parse failure. + """ + try: + # sinfo %m gives memory in MB as an integer + cleaned = mem_str.strip().rstrip("+") + return int(cleaned) + except (ValueError, AttributeError): + return 0 + + +def _parse_features(features_str: str) -> tuple[str, ...]: + """Parse SLURM features/constraints string. + + Parameters + ---------- + features_str : str + Features field from sinfo (comma-separated or ``"(null)"``). + + Returns + ------- + tuple of str + Feature strings (immutable). + """ + if not features_str or features_str == "(null)": + return () + return tuple(f.strip() for f in features_str.split(",") if f.strip()) + + +def _parse_sinfo(output: str) -> list[Partition]: + """Parse sinfo output into Partition objects. + + Expects output from: + sinfo -N --noheader -o "%P %n %c %m %G %f %l %L %T" + + Fields: Partition, NodeName, CPUs, Memory(MB), GRES, Features, + MaxTimeLimit, DefaultTimeLimit, State + + Also supports the legacy 8-field format (without %L) for backward + compatibility — in that case ``default_time`` equals ``max_time``. + + Parameters + ---------- + output : str + Raw sinfo output. + + Returns + ------- + list of Partition + Parsed partition list with deduplicated node types. + """ + # Collect data per partition + partition_data: dict[str, dict] = {} + + for raw_line in output.splitlines(): + line = raw_line.strip() + if not line: + continue + + parts = line.split() + if len(parts) >= 9: + # 9-field format: %P %n %c %m %G %f %l %L %T + partition_name = parts[0] + cpus_str = parts[2] + mem_str = parts[3] + gres_str = parts[4] + features_str = parts[5] + max_time = parts[6] + default_time = parts[7] + state = parts[8] + elif len(parts) == 8: + # Legacy 8-field format (no %L): default_time = max_time + partition_name = parts[0] + cpus_str = parts[2] + mem_str = parts[3] + gres_str = parts[4] + features_str = parts[5] + max_time = parts[6] + default_time = parts[6] + state = parts[7] + else: + continue + + # Handle default partition marker (trailing asterisk) + is_default = partition_name.endswith("*") + if is_default: + partition_name = partition_name.rstrip("*") + + # Parse node specs + try: + cpus = int(cpus_str) + except ValueError: + continue + + memory_mb = _parse_memory_mb(mem_str) + gpu_entries = _parse_gres(gres_str) + features = _parse_features(features_str) + + if partition_name not in partition_data: + partition_data[partition_name] = { + "max_time": _parse_time_limit(max_time), + "default_time": _parse_time_limit(default_time), + "node_types": {}, # Maps node_key -> count + "node_count": 0, + "is_default": is_default, + "has_healthy_node": False, + "last_unhealthy_state": "down", + } + + # Track if any line marks this as default + if is_default: + partition_data[partition_name]["is_default"] = True + + # Determine if node is operational (can schedule jobs or report accurate config) + # Exclude broken/invalid nodes: down, drained, inval, fail, maint, unknown, error + state_lower = state.lower() + is_broken = any( + broken in state_lower + for broken in ("down", "drained", "inval", "fail", "maint", "unknown", "error") + ) + is_operational = not is_broken + + # Partition is "up" if ANY operational node exists + if is_operational: + partition_data[partition_name]["has_healthy_node"] = True + else: + partition_data[partition_name]["last_unhealthy_state"] = state + + # Count all nodes (including allocated, reserved, etc.) for stable totals + partition_data[partition_name]["node_count"] += 1 + + # Only collect node specs from operational nodes + # (broken nodes may report incorrect/stale hardware configs) + if is_operational: + # Convert GPU entries to a sorted tuple for consistent hashing + # Sort by type name for deterministic ordering + gpu_specs = tuple(sorted(gpu_entries, key=lambda x: x[1] or "")) + + # Use a hashable representation for deduplication + node_key = (cpus, memory_mb, gpu_specs, features) + + # Track count of nodes with this exact configuration + if node_key not in partition_data[partition_name]["node_types"]: + partition_data[partition_name]["node_types"][node_key] = 0 + partition_data[partition_name]["node_types"][node_key] += 1 + + # Build Partition objects + partitions = [] + for name, data in partition_data.items(): + # Partition state: "up" if any node is healthy, otherwise report + # the last observed unhealthy state + if data["has_healthy_node"]: + partition_state = "up" + else: + partition_state = data["last_unhealthy_state"] + + node_types = [ + NodeType( + cpus=cpus, + memory_mb=mem, + gpu_specs=gpu_specs, + features=feats, + count=count, + ) + for (cpus, mem, gpu_specs, feats), count in data["node_types"].items() + ] + # Sort node types by CPU count, memory, then GPU specs for deterministic ordering + node_types.sort(key=lambda n: (n.cpus, n.memory_mb, n.gpu_specs)) + + partitions.append( + Partition( + name=name, + state=partition_state, + max_time=data["max_time"], + default_time=data["default_time"], + node_types=node_types, + total_nodes=data["node_count"], + is_default=data["is_default"], + ) + ) + + # Sort partitions: default first, then alphabetical + partitions.sort(key=lambda p: (not p.is_default, p.name)) + return partitions + + +def _parse_accounts(output: str) -> list[str]: + """Parse sacctmgr account output. + + Parameters + ---------- + output : str + Raw sacctmgr output (one account per line, parsable2 format). + + Returns + ------- + list of str + Unique account names, sorted. + """ + accounts = set() + for line in output.splitlines(): + account = line.strip() + if account: + accounts.add(account) + return sorted(accounts) + + +def _parse_qos(output: str) -> list[str]: + """Parse sacctmgr QOS output. + + Parameters + ---------- + output : str + Raw sacctmgr output (pipe-separated: Name|MaxWall|MaxTRES). + + Returns + ------- + list of str + QOS policy names, sorted. + """ + qos_names = set() + for raw_line in output.splitlines(): + line = raw_line.strip() + if not line: + continue + # Format: Name|MaxWall|MaxTRES + parts = line.split("|") + if parts and parts[0].strip(): + qos_names.add(parts[0].strip()) + return sorted(qos_names) + + +def _discover_partitions() -> list[Partition] | None: + """Query sinfo for partition and node information. + + Returns + ------- + list of Partition or None + Parsed partitions, or None if sinfo is unavailable. + """ + output = run_command( + ["sinfo", "-N", "--noheader", "-o", "%P %n %c %m %G %f %l %L %T"], + timeout=30, + graceful=True, + ) + if output is None: + return None + return _parse_sinfo(output) + + +def _discover_accounts() -> list[str] | None: + """Query sacctmgr for the current user's accounts. + + Returns + ------- + list of str or None + Account names, or None if sacctmgr is unavailable. + """ + user = os.environ.get("USER", os.environ.get("LOGNAME", "")) + if not user: + return None + output = run_command( + [ + "sacctmgr", + "show", + "assoc", + f"user={user}", + "format=Account", + "--noheader", + "--parsable2", + ], + timeout=30, + graceful=True, + ) + if output is None: + return None + return _parse_accounts(output) + + +def _discover_qos() -> list[str] | None: + """Query sacctmgr for available QOS policies. + + Returns + ------- + list of str or None + QOS names, or None if sacctmgr is unavailable. + """ + output = run_command( + [ + "sacctmgr", + "show", + "qos", + "format=Name,MaxWall,MaxTRES", + "--noheader", + "--parsable2", + ], + timeout=30, + graceful=True, + ) + if output is None: + return None + return _parse_qos(output) + + +def _discover_default_account() -> str | None: + """Query sacctmgr for the current user's default SLURM account. + + Returns + ------- + str or None + The user's default account, or None if unavailable. + """ + user = os.environ.get("USER", os.environ.get("LOGNAME", "")) + if not user: + return None + output = run_command( + [ + "sacctmgr", + "show", + "user", + user, + "format=DefaultAccount", + "--noheader", + "--parsable2", + ], + timeout=30, + graceful=True, + ) + if output is None: + return None + account = output.strip().splitlines()[0].strip() if output.strip() else None + return account if account else None + + +def discover_cluster() -> ClusterInfo | None: + """Query SLURM and return structured cluster information. + + Calls ``sinfo`` and ``sacctmgr`` to discover partitions, node types, + accounts, and QOS policies. Returns None gracefully when SLURM commands + are not available (e.g., running on a laptop). + + Successful results and confirmed sinfo-absent are cached for the session + (cluster topology doesn't change mid-run). Transient failures (sinfo + present but exiting non-zero) are not cached so the next call retries. + Call ``discover_cluster.cache_clear()`` to force a full re-query. + + Returns + ------- + ClusterInfo or None + Structured cluster information, or None if SLURM is unavailable. + + Examples + -------- + >>> cluster = discover_cluster() + >>> if cluster is not None: + ... for p in cluster.partitions: + ... print(f"{p.name}: {p.total_nodes} nodes") + """ + if _cluster_cache[0] is not _CLUSTER_CACHE_SENTINEL: + return _cluster_cache[0] # type: ignore[return-value] + + # sinfo is the minimum requirement — if it's not available, we're not + # on a SLURM cluster. This is a stable fact; cache it. + if shutil.which("sinfo") is None: + _cluster_cache[0] = None + return None + + partitions = _discover_partitions() + if partitions is None: + # Transient failure (sinfo exited non-zero) — don't cache, allow retry. + return None + + # Accounts and QOS are best-effort (sacctmgr may be restricted) + accounts = _discover_accounts() or [] + qos_policies = _discover_qos() or [] + + # Query the real SLURM default account; fall back to first available + default_account = _discover_default_account() + if default_account is None and accounts: + default_account = accounts[0] + + result = ClusterInfo( + partitions=partitions, + accounts=accounts, + qos_policies=qos_policies, + default_account=default_account, + ) + _cluster_cache[0] = result + return result + + +def _clear_cluster_cache() -> None: + _cluster_cache[0] = _CLUSTER_CACHE_SENTINEL + + +discover_cluster.cache_clear = _clear_cluster_cache # type: ignore[attr-defined] + + +def select_partition( + cluster: ClusterInfo, + *, + needs_gpu: bool = False, + min_cpus: int = 1, + min_mem_gb: int = 1, +) -> Partition | None: + """Heuristic partition selection given resource requirements. + + Selects the best-matching partition from the cluster based on hardware + needs. Prefers partitions that are ``"up"`` and have nodes meeting the + specified requirements. + + Parameters + ---------- + cluster : ClusterInfo + Cluster information from ``discover_cluster()``. + needs_gpu : bool + If True, only consider partitions with GPU-equipped nodes. + min_cpus : int + Minimum CPUs per node required. + min_mem_gb : int + Minimum memory per node in GB. + + Returns + ------- + Partition or None + Best matching partition, or None if no partition meets requirements. + + Examples + -------- + >>> cluster = discover_cluster() + >>> gpu_partition = select_partition(cluster, needs_gpu=True, min_cpus=8) + """ + min_mem_mb = min_mem_gb * 1024 + candidates: list[Partition] = [] + + for partition in cluster.partitions: + # Skip partitions with no schedulable nodes + if partition.state.lower() != "up": + continue + + # Check if any node type meets requirements + has_qualifying_node = False + for node in partition.node_types: + if node.cpus < min_cpus: + continue + if node.memory_mb < min_mem_mb: + continue + # Check if node has any GPUs + has_gpu = len(node.gpu_specs) > 0 + if needs_gpu and not has_gpu: + continue + has_qualifying_node = True + break + + if has_qualifying_node: + candidates.append(partition) + + if not candidates: + return None + + # Prefer: default partition > most nodes > alphabetical + candidates.sort(key=lambda p: (not p.is_default, -p.total_nodes, p.name)) + return candidates[0] diff --git a/mdfactory/performance/slurm_config.py b/mdfactory/performance/slurm_config.py new file mode 100644 index 0000000..ec36d86 --- /dev/null +++ b/mdfactory/performance/slurm_config.py @@ -0,0 +1,397 @@ +# ABOUTME: SLURM configuration models shared across all submission backends. +# ABOUTME: BaseSlurmConfig with 3-tier autodiscovery; SlurmConfig for submitit analysis jobs. +"""SLURM configuration models for all submission backends. + +Provides a shared ``BaseSlurmConfig`` Pydantic model with four cross-cutting +SLURM fields (``account``, ``partition``, ``qos``, ``constraint``) and a single +authoritative ``from_cluster()`` factory that implements three-tier precedence: + +1. Explicit keyword arguments passed by the caller +2. ``[slurm]`` section in ``config.ini`` +3. Live ``sinfo`` / ``sacctmgr`` autodiscovery via ``mdfactory.performance.cluster`` + +Subclasses add backend-specific fields and inherit ``from_cluster()`` for free. + +Classes +------- +BaseSlurmConfig + Abstract base for all SLURM-facing config objects. +SlurmConfig + Configuration for submitit-based analysis submission. + +Functions +--------- +normalize_slurm_time + Convert human-friendly time strings (``"2h"``, ``"30m"``) to + ``HH:MM:SS`` / ``D-HH:MM:SS`` format accepted by SLURM. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any, Self + +from pydantic import BaseModel, ConfigDict, Field, field_validator + + +def resolve_slurm_fields( + *, + needs_gpu: bool = False, + min_cpus: int = 1, + min_mem_gb: int = 1, + **extra_fields: Any, +) -> dict[str, Any]: + """Resolve SLURM scheduling fields via three-tier precedence. + + Three-tier precedence (highest wins): + + 1. Explicit keyword arguments passed via ``extra_fields`` + (``account``, ``partition``, ``qos``, ``constraint``). + 2. ``[slurm]`` section in ``config.ini`` — read via + ``mdfactory.settings.settings``. + 3. Live ``sinfo`` / ``sacctmgr`` autodiscovery via + ``mdfactory.performance.cluster``. + + Parameters + ---------- + needs_gpu : bool + Select a GPU-capable partition when ``True``. + min_cpus : int + Minimum CPUs per node required (passed to ``select_partition()``). + min_mem_gb : int + Minimum memory per node in GB (passed to ``select_partition()``). + **extra_fields + Any additional fields. The keys ``account``, ``partition``, + ``qos``, and ``constraint`` are consumed for SLURM resolution; + remaining keys are passed through unchanged. + + Returns + ------- + dict[str, Any] + A dict with at least ``account``, ``partition``, ``qos``, and + ``constraint`` plus any remaining ``extra_fields``. + + Raises + ------ + RuntimeError + If SLURM is unavailable *and* the required ``account`` or + ``partition`` value cannot be resolved from config. + """ + from mdfactory.performance.cluster import discover_cluster, select_partition + from mdfactory.settings import settings + + cluster = discover_cluster() + + # --- account: explicit kwarg > config.ini > autodiscovery --- + resolved_account: str | None = extra_fields.pop("account", None) + if resolved_account is None: + resolved_account = settings.slurm_account + if resolved_account is None: + if cluster is None: + raise RuntimeError( + "SLURM autodiscovery failed and no account configured. " + "Set [slurm] ACCOUNT in config.ini or pass account= explicitly." + ) + resolved_account = cluster.default_account + if resolved_account is None: + raise RuntimeError( + "No SLURM account available. " + "Set [slurm] ACCOUNT in config.ini or pass account= explicitly." + ) + + # --- partition: explicit kwarg > config.ini (cpu/gpu) > autodiscovery --- + resolved_partition: str | None = extra_fields.pop("partition", None) + if resolved_partition is None: + resolved_partition = ( + settings.slurm_partition_gpu if needs_gpu else settings.slurm_partition_cpu + ) + if resolved_partition is None: + if cluster is None: + raise RuntimeError( + "SLURM autodiscovery failed and no partition configured. " + "Set [slurm] PARTITION_CPU or PARTITION_GPU in config.ini " + "or pass partition= explicitly." + ) + selected = select_partition( + cluster, + needs_gpu=needs_gpu, + min_cpus=min_cpus, + min_mem_gb=min_mem_gb, + ) + if selected is None: + raise RuntimeError( + f"No suitable partition found for requirements: " + f"needs_gpu={needs_gpu}, cpus={min_cpus}, mem={min_mem_gb}GB" + ) + resolved_partition = selected.name + + # --- qos: explicit kwarg > config.ini --- + resolved_qos: str | None = extra_fields.pop("qos", None) + if resolved_qos is None: + resolved_qos = settings.slurm_qos + + # --- constraint: explicit kwarg only (no config.ini equivalent) --- + resolved_constraint: str | None = extra_fields.pop("constraint", None) + + return { + "account": resolved_account, + "partition": resolved_partition, + "qos": resolved_qos, + "constraint": resolved_constraint, + **extra_fields, + } + + +def normalize_slurm_time(value: str) -> str: + """Normalize SLURM time strings to ``HH:MM:SS`` or ``D-HH:MM:SS`` format. + + Accepts human-friendly shorthand as well as all canonical SLURM formats. + + Parameters + ---------- + value : str + Raw time string, e.g. ``"2h"``, ``"30m"``, ``"90"`` (minutes), + ``"1d"``, ``"01:00:00"``, ``"3-00:00:00"``. + + Returns + ------- + str + Normalized time string. Strings that already contain ``:`` are + returned unchanged (pass-through for HH:MM:SS / D-HH:MM:SS). + + Examples + -------- + >>> normalize_slurm_time("2h") + '02:00:00' + >>> normalize_slurm_time("30m") + '00:30:00' + >>> normalize_slurm_time("90") + '01:30:00' + >>> normalize_slurm_time("1d") + '1-00:00:00' + >>> normalize_slurm_time("01:00:00") + '01:00:00' + """ + raw = value.strip() + if ":" in raw: + return raw + lowered = raw.lower() + if lowered.endswith("d"): + days = int(lowered[:-1]) + return f"{days}-00:00:00" + if lowered.endswith("h"): + hours = int(lowered[:-1]) + return f"{hours:02d}:00:00" + if lowered.endswith("m"): + minutes = int(lowered[:-1]) + hours, minutes = divmod(minutes, 60) + return f"{hours:02d}:{minutes:02d}:00" + if lowered.isdigit(): + minutes = int(lowered) + hours, minutes = divmod(minutes, 60) + return f"{hours:02d}:{minutes:02d}:00" + return raw + + +class BaseSlurmConfig(BaseModel): + """SLURM resource specification shared by all submission backends. + + Base class owning the four cross-cutting SLURM fields and the single + authoritative ``from_cluster()`` factory method. Subclasses add + backend-specific fields (e.g. ``cpus_per_task`` for submitit, + ``cpus_per_node`` for Parsl) and inherit ``from_cluster()`` without + writing any additional code. + + Parameters + ---------- + account : str + SLURM account string (``--account``). + partition : str + SLURM partition name (``--partition``). Defaults to ``"cpu"``. + qos : str or None + Quality-of-service policy name (``--qos``). ``None`` lets SLURM + apply its own default. + constraint : str or None + Node feature constraint string (``--constraint``). + + Notes + ----- + This model is *frozen*: instances are immutable after construction. + Use Pydantic's ``model_copy(update={...})`` to derive a modified copy. + """ + + model_config = ConfigDict(frozen=True) + + # TODO(follow-up): decide whether account="" should be validated non-empty + # for SLURM configs. The current default allows autodiscovery scenarios + # (some clusters inherit the account from the user's environment), but it + # means a missing account fails only at sbatch time rather than at config + # load. If explicit validation is desired, add a @field_validator that + # rejects empty strings when provider=="slurm". + account: str = "" + partition: str = "cpu" + qos: str | None = None + constraint: str | None = None + + @classmethod + def from_cluster( + cls, + *, + needs_gpu: bool = False, + min_cpus: int = 1, + min_mem_gb: int = 1, + **extra_fields: Any, + ) -> Self: + """Create an instance with SLURM fields auto-populated from the cluster. + + Three-tier precedence (highest wins): + + 1. Explicit keyword arguments passed by the caller (e.g. + ``account="mygroup"`` or ``partition="gpu"``). + 2. ``[slurm]`` section in ``config.ini`` — read via + ``mdfactory.settings.settings``. + 3. Live ``sinfo`` / ``sacctmgr`` autodiscovery via + ``mdfactory.performance.cluster``. + + The lazy import of ``mdfactory.performance.cluster`` keeps this + module importable on non-SLURM machines. + + Parameters + ---------- + needs_gpu : bool + Select a GPU-capable partition when ``True``. Used both for + partition autodiscovery and for choosing the correct + ``PARTITION_GPU`` config key. + min_cpus : int + Minimum CPUs per node required (passed to ``select_partition()``). + min_mem_gb : int + Minimum memory per node in GB (passed to ``select_partition()``). + **extra_fields + Backend-specific fields forwarded to the subclass constructor. + For ``SlurmConfig``: ``time``, ``cpus_per_task``, ``mem_gb``, + ``job_name_prefix``. + Base-class fields (``account``, ``partition``, ``qos``, + ``constraint``) may also be passed here to override autodiscovery. + + Returns + ------- + BaseSlurmConfig + A fully initialised subclass instance (the concrete type is + determined by ``cls``). + + Raises + ------ + RuntimeError + If SLURM is unavailable *and* the required ``account`` or + ``partition`` value cannot be resolved from config. + """ + fields = resolve_slurm_fields( + needs_gpu=needs_gpu, + min_cpus=min_cpus, + min_mem_gb=min_mem_gb, + **extra_fields, + ) + return cls(**fields) + + +class SlurmConfig(BaseSlurmConfig): + """SLURM configuration for submitit-based analysis submission. + + Backend: submitit — one SLURM job per analysis task. + + The ``time`` field is normalised on construction via + ``normalize_slurm_time()``, so ``"2h"``, ``"120"`` (minutes), and + ``"02:00:00"`` are all accepted and stored as ``"02:00:00"``. + + Parameters + ---------- + account : str + SLURM account (inherited from ``BaseSlurmConfig``). + partition : str + SLURM partition (inherited). Defaults to ``"cpu"``. + qos : str or None + QOS policy (inherited). + constraint : str or None + Node constraint (inherited). + time : str + Job time limit. Maps to SLURM ``--time``. + Accepts human-friendly strings (``"2h"``, ``"30m"``, ``"1d"``) as + well as ``MM``, ``HH:MM:SS``, and ``D-HH:MM:SS`` formats. + cpus_per_task : int + CPUs allocated per task. Maps to SLURM ``--cpus-per-task``. + **Not** the same as ``--cpus-per-node`` used by Parsl executor configs. + mem_gb : int + Memory per task in gigabytes. Maps to SLURM ``--mem``. + job_name_prefix : str + Prefix for SLURM job names submitted via submitit. + + Examples + -------- + Minimal construction (explicit account): + + >>> cfg = SlurmConfig(account="mygroup") + >>> cfg.time + '02:00:00' + + Autodiscovery on a SLURM cluster: + + >>> cfg = SlurmConfig.from_cluster(time="4h", cpus_per_task=8, mem_gb=16) + """ + + time: str = Field(default="2h", validate_default=True) + cpus_per_task: int = 4 + """Maps to SLURM ``--cpus-per-task`` (NOT ``--cpus-per-node``).""" + mem_gb: int = 8 + job_name_prefix: str = "mdfactory-analysis" + + @field_validator("time", mode="before") + @classmethod + def _normalize_time(cls, v: str) -> str: + """Normalize time string on construction.""" + return normalize_slurm_time(v) + + @classmethod + def from_yaml(cls, path: Path | str) -> "SlurmConfig": + """Load a ``SlurmConfig`` from a YAML file. + + Parameters + ---------- + path : Path + Path to a YAML file whose top-level keys match ``SlurmConfig`` + field names. + + Returns + ------- + SlurmConfig + Loaded and validated instance. + + Raises + ------ + FileNotFoundError + If the YAML file does not exist. + pydantic.ValidationError + If the YAML content fails validation. + + Examples + -------- + .. code-block:: yaml + + # slurm.yaml + account: mygroup + partition: cpu + time: 4h + cpus_per_task: 8 + mem_gb: 16 + + >>> cfg = SlurmConfig.from_yaml(Path("slurm.yaml")) + >>> cfg.time + '04:00:00' + """ + import yaml + + path = Path(path) + if not path.exists(): + raise FileNotFoundError(f"SLURM config YAML not found: {path}") + with path.open() as fh: + data = yaml.safe_load(fh) + return cls.model_validate(data or {}) diff --git a/mdfactory/prepare.py b/mdfactory/prepare.py index 6beaaa6..a37237d 100644 --- a/mdfactory/prepare.py +++ b/mdfactory/prepare.py @@ -9,6 +9,48 @@ from .models.input import BuildInput +# Protein fields that are lists/dicts and therefore optional: a CSV row may leave +# them blank. They are excluded from the strict all-columns NaN check and dropped +# per row when empty so heterogeneous proteins can share one CSV. +OPTIONAL_PROTEIN_FIELD_PREFIXES = ( + "system.protein.chains", + "system.protein.disulfide_bonds", + "system.protein.protonation_states", +) + + +def _parse_delimited_protein_fields(nested: dict) -> None: + """Parse a proteinbox row's delimited list cells into lists in place. + + ``chains`` is a ``;``-separated list of chain ids ("A;B"). ``disulfide_bonds`` + is a ``;``-separated list of ``RESNAME-RESNAME`` pairs + ("CYS6-CYS127;CYS30-CYS115"). ``protonation_states`` is expressed as nested + dotted columns (system.protein.protonation_states.HIS15) and needs no parsing. + """ + protein = nested.get("system", {}).get("protein") + if not isinstance(protein, dict): + return + + chains = protein.get("chains") + if isinstance(chains, str): + protein["chains"] = [c.strip() for c in chains.split(";") if c.strip()] + + bonds = protein.get("disulfide_bonds") + if isinstance(bonds, str): + parsed = [] + for raw_token in bonds.split(";"): + token = raw_token.strip() + if not token: + continue + first, sep, second = token.partition("-") + if not sep: + raise ValueError( + f"Invalid disulfide bond '{token}'. Expected " + "'RESNAME-RESNAME', e.g. 'CYS6-CYS127'." + ) + parsed.append((first.strip(), second.strip())) + protein["disulfide_bonds"] = parsed + def dict_to_nested_dict_with_species_prefix(dct: dict) -> dict: """Convert a flat dot-separated key dict into a nested dict with species grouping. @@ -59,7 +101,9 @@ def dict_to_nested_dict_with_species_prefix(dct: dict) -> dict: {"resname": resname, **props} for resname, props in species_data.items() if props ] - return dict(result) + nested = dict(result) + _parse_delimited_protein_fields(nested) + return nested def df_to_build_input_models( @@ -78,7 +122,10 @@ def df_to_build_input_models( (valid_models, {row_index: error_message} for failed rows) """ - if df.isnull().values.any(): + # Optional protein list/dict fields may legitimately be blank for some proteins, + # so exclude them from the strict NaN check and drop them per row when empty. + required_cols = [c for c in df.columns if not c.startswith(OPTIONAL_PROTEIN_FIELD_PREFIXES)] + if df[required_cols].isnull().values.any(): raise ValueError("Cannot process data frame with NaN values.") if df.duplicated(keep=False).any(): raise ValueError("Data frame contains duplicate rows.") @@ -86,7 +133,7 @@ def df_to_build_input_models( errors = {} for ii, row in df.iterrows(): try: - inp = BuildInput(**dict_to_nested_dict_with_species_prefix(row)) + inp = BuildInput(**dict_to_nested_dict_with_species_prefix(row.dropna())) ret.append(inp) except Exception as e: errors[ii] = str(e) diff --git a/mdfactory/run_schedules/gromacs/proteinbox/em.mdp b/mdfactory/run_schedules/gromacs/proteinbox/em.mdp new file mode 100644 index 0000000..6099eaf --- /dev/null +++ b/mdfactory/run_schedules/gromacs/proteinbox/em.mdp @@ -0,0 +1,20 @@ +; Energy minimization parameters for protein systems (CHARMM36m) +integrator = steep +emtol = 1000.0 +emstep = 0.01 +nsteps = 50000 + +; Neighbor searching +nstlist = 1 +cutoff-scheme = Verlet +ns_type = grid +pbc = xyz + +; Electrostatics and VdW (CHARMM36m settings) +coulombtype = PME +rcoulomb = 1.2 +rvdw = 1.2 +vdwtype = cutoff +vdw-modifier = Force-switch +rvdw-switch = 1.0 +DispCorr = no diff --git a/mdfactory/run_schedules/gromacs/proteinbox/md.mdp b/mdfactory/run_schedules/gromacs/proteinbox/md.mdp new file mode 100644 index 0000000..e2af45d --- /dev/null +++ b/mdfactory/run_schedules/gromacs/proteinbox/md.mdp @@ -0,0 +1,57 @@ +; MD production run for protein systems (CHARMM36m) +title = MD production run + +; Run parameters +integrator = md +nsteps = 50000000 +dt = 0.002 + +; Output control +nstxout = 0 +nstvout = 0 +nstfout = 0 +nstenergy = 5000 +nstlog = 5000 +nstxout-compressed = 5000 +compressed-x-grps = System + +; Bond parameters +continuation = yes +constraint_algorithm = lincs +constraints = h-bonds +lincs_iter = 1 +lincs_order = 4 + +; Neighbor searching +cutoff-scheme = Verlet +ns_type = grid +nstlist = 10 +pbc = xyz + +; Electrostatics and VdW (CHARMM36m settings) +coulombtype = PME +pme_order = 4 +fourierspacing = 0.16 +rcoulomb = 1.2 +rvdw = 1.2 +vdwtype = cutoff +vdw-modifier = Force-switch +rvdw-switch = 1.0 +DispCorr = no + +; Temperature coupling +tcoupl = V-rescale +tc-grps = Protein Non-Protein +tau_t = 0.1 0.1 +ref_t = 300 300 + +; Pressure coupling (Parrinello-Rahman for production) +pcoupl = Parrinello-Rahman +pcoupltype = isotropic +tau_p = 5.0 +ref_p = 1.0 +compressibility = 4.5e-5 +refcoord_scaling = com + +; Velocity generation +gen_vel = no diff --git a/mdfactory/run_schedules/gromacs/proteinbox/npt.mdp b/mdfactory/run_schedules/gromacs/proteinbox/npt.mdp new file mode 100644 index 0000000..95eceb8 --- /dev/null +++ b/mdfactory/run_schedules/gromacs/proteinbox/npt.mdp @@ -0,0 +1,54 @@ +; NPT equilibration for protein systems (CHARMM36m) +title = NPT equilibration +define = -DPOSRES + +; Run parameters +integrator = md +nsteps = 50000 +dt = 0.002 + +; Output control +nstxout = 500 +nstvout = 500 +nstenergy = 500 +nstlog = 500 + +; Bond parameters +continuation = yes +constraint_algorithm = lincs +constraints = h-bonds +lincs_iter = 1 +lincs_order = 4 + +; Neighbor searching +cutoff-scheme = Verlet +ns_type = grid +nstlist = 10 +pbc = xyz + +; Electrostatics and VdW (CHARMM36m settings) +coulombtype = PME +pme_order = 4 +fourierspacing = 0.16 +rcoulomb = 1.2 +rvdw = 1.2 +vdwtype = cutoff +vdw-modifier = Force-switch +rvdw-switch = 1.0 +DispCorr = no + +; Temperature coupling +tcoupl = V-rescale +tc-grps = Protein Non-Protein +tau_t = 0.1 0.1 +ref_t = 300 300 + +; Pressure coupling +pcoupl = Berendsen +pcoupltype = isotropic +tau_p = 2.0 +ref_p = 1.0 +compressibility = 4.5e-5 + +; Velocity generation +gen_vel = no diff --git a/mdfactory/run_schedules/gromacs/proteinbox/nvt.mdp b/mdfactory/run_schedules/gromacs/proteinbox/nvt.mdp new file mode 100644 index 0000000..a85ecce --- /dev/null +++ b/mdfactory/run_schedules/gromacs/proteinbox/nvt.mdp @@ -0,0 +1,52 @@ +; NVT equilibration for protein systems (CHARMM36m) +title = NVT equilibration +define = -DPOSRES + +; Run parameters +integrator = md +nsteps = 50000 +dt = 0.002 + +; Output control +nstxout = 500 +nstvout = 500 +nstenergy = 500 +nstlog = 500 + +; Bond parameters +continuation = no +constraint_algorithm = lincs +constraints = h-bonds +lincs_iter = 1 +lincs_order = 4 + +; Neighbor searching +cutoff-scheme = Verlet +ns_type = grid +nstlist = 10 +pbc = xyz + +; Electrostatics and VdW (CHARMM36m settings) +coulombtype = PME +pme_order = 4 +fourierspacing = 0.16 +rcoulomb = 1.2 +rvdw = 1.2 +vdwtype = cutoff +vdw-modifier = Force-switch +rvdw-switch = 1.0 +DispCorr = no + +; Temperature coupling +tcoupl = V-rescale +tc-grps = Protein Non-Protein +tau_t = 0.1 0.1 +ref_t = 300 300 + +; Pressure coupling off for NVT +pcoupl = no + +; Velocity generation +gen_vel = yes +gen_temp = 300 +gen_seed = -1 diff --git a/mdfactory/run_schedules/run_schedules.yaml b/mdfactory/run_schedules/run_schedules.yaml index 684567e..3a591d4 100644 --- a/mdfactory/run_schedules/run_schedules.yaml +++ b/mdfactory/run_schedules/run_schedules.yaml @@ -36,3 +36,16 @@ num_gpus: 1 num_cores: 8 walltime: 24:00:00 + +- engine: gromacs + system_type: proteinbox + version: 1.0 + run_files: + - em.mdp + - nvt.mdp + - npt.mdp + - md.mdp + settings: + num_gpus: 1 + num_cores: 8 + walltime: 24:00:00 diff --git a/mdfactory/settings.py b/mdfactory/settings.py index 7c99a42..53cad52 100644 --- a/mdfactory/settings.py +++ b/mdfactory/settings.py @@ -45,6 +45,10 @@ def _get_defaults() -> dict[str, dict[str, str]]: "cgenff": { "SILCSBIODIR": "", }, + "gromacs": { + "GMX_PATH": "", + "FORCEFIELD_DIR": str(data_dir / "forcefields"), + }, "storage": { "PARAMETERS": str(data_dir / "parameters"), }, @@ -70,11 +74,18 @@ def _get_defaults() -> dict[str, dict[str, str]]: "ANALYSIS_DB_PATH": "/Group Functions/mdfactory/analysis", "ARTIFACT_DB_PATH": "/Group Functions/mdfactory/artifacts", }, + "slurm": { + "ACCOUNT": "", + "PARTITION_CPU": "", + "PARTITION_GPU": "", + "DEFAULT_QOS": "", + }, } # Keep DEFAULT_CONFIG as class attribute for backward compat in tests DEFAULT_CONFIG = { "cgenff": {"SILCSBIODIR": ""}, + "gromacs": {"GMX_PATH": "", "FORCEFIELD_DIR": ""}, "storage": {"PARAMETERS": ""}, "database": {"TYPE": "sqlite"}, "databases": { @@ -96,6 +107,12 @@ def _get_defaults() -> dict[str, dict[str, str]]: "ANALYSIS_DB_PATH": "/Group Functions/mdfactory/analysis", "ARTIFACT_DB_PATH": "/Group Functions/mdfactory/artifacts", }, + "slurm": { + "ACCOUNT": "", + "PARTITION_CPU": "", + "PARTITION_GPU": "", + "DEFAULT_QOS": "", + }, } def __init__(self): @@ -105,6 +122,10 @@ def __init__(self): self._load_config() if "cgenff" in self.config: os.environ.setdefault("SILCSBIODIR", str(self.cgenff_dir)) + if "gromacs" in self.config: + env = self.gromacs_env(os.environ) + if "GMXLIB" in env: + os.environ["GMXLIB"] = env["GMXLIB"] def _load_config(self): """Load configuration from defaults, then user config, then env overrides.""" @@ -146,6 +167,7 @@ def ensure_dirs(self): get_config_dir().mkdir(parents=True, exist_ok=True) get_data_dir().mkdir(parents=True, exist_ok=True) self.parameter_store.mkdir(parents=True, exist_ok=True) + self.gromacs_forcefield_dir.mkdir(parents=True, exist_ok=True) # --- CGenFF properties --- @@ -159,6 +181,37 @@ def cgenff_dir(self) -> Path: """Return the CGenFF directory (SILCSBIODIR).""" return Path(self.config["cgenff"].get("SILCSBIODIR", "")).resolve() + # --- GROMACS properties --- + + @property + def gromacs_gmx_path(self) -> Path | None: + """Return the configured gmx binary path, or None to use PATH lookup.""" + raw = self.config["gromacs"].get("GMX_PATH", "") + if not raw: + return None + return Path(self._resolve_local_path(raw)) + + @property + def gromacs_forcefield_dir(self) -> Path: + """Return the directory where downloaded force fields are stored.""" + raw = self.config["gromacs"].get("FORCEFIELD_DIR", "") + if raw: + return Path(self._resolve_local_path(raw)) + return get_data_dir() / "forcefields" + + def gromacs_env(self, base_env: dict[str, str] | None = None) -> dict[str, str]: + """Return an environment with configured GROMACS force fields on GMXLIB.""" + env = dict(os.environ if base_env is None else base_env) + forcefield_dir = str(self.gromacs_forcefield_dir) + if not forcefield_dir: + return env + + paths = [p for p in env.get("GMXLIB", "").split(":") if p] + if forcefield_dir not in paths: + paths.insert(0, forcefield_dir) + env["GMXLIB"] = ":".join(paths) + return env + # --- Storage properties --- @property @@ -312,6 +365,43 @@ def get_csv_path(self, db_name: str) -> str: raw_path = self.config["csv"].get(path_key, "") return self._resolve_local_path(raw_path) + # --- SLURM properties --- + + @property + def slurm_account(self) -> str | None: + """Return configured SLURM account, or None if not set.""" + val = self.config.get("slurm", "ACCOUNT", fallback="").strip() + return val if val else None + + @property + def slurm_partition_cpu(self) -> str | None: + """Return configured CPU partition name, or None if not set. + + Maps to ``[slurm] PARTITION_CPU`` in config.ini. + Used by ``BaseSlurmConfig.from_cluster()`` when ``needs_gpu=False``. + """ + val = self.config.get("slurm", "PARTITION_CPU", fallback="").strip() + return val if val else None + + @property + def slurm_partition_gpu(self) -> str | None: + """Return configured GPU partition name, or None if not set. + + Maps to ``[slurm] PARTITION_GPU`` in config.ini. + Used by ``BaseSlurmConfig.from_cluster()`` when ``needs_gpu=True``. + """ + val = self.config.get("slurm", "PARTITION_GPU", fallback="").strip() + return val if val else None + + @property + def slurm_qos(self) -> str | None: + """Return configured SLURM default QOS, or None if not set. + + Maps to ``[slurm] DEFAULT_QOS`` in config.ini. + """ + val = self.config.get("slurm", "DEFAULT_QOS", fallback="").strip() + return val if val else None + # Module-level singleton settings = Settings() diff --git a/mdfactory/setup/protein.py b/mdfactory/setup/protein.py new file mode 100644 index 0000000..938a082 --- /dev/null +++ b/mdfactory/setup/protein.py @@ -0,0 +1,1039 @@ +# ABOUTME: Protein preparation utilities for the proteinbox simulation type +# ABOUTME: Wraps PDBFixer for cleaning and gmx pdb2gmx for topology generation +"""Protein preparation utilities for the proteinbox simulation type.""" + +import hashlib +import io +import re +import shutil +import subprocess +import tarfile +from pathlib import Path +from urllib.request import urlopen + +from loguru import logger + +from ..models.parametrization import GromacsProteinParameterSet, Pdb2gmxConfig + +_DISULFIDE_CUTOFF_ANGSTROM = 2.2 +_CYSTEINE_RESNAMES = {"CYS", "CYM", "CYX"} + +FORCEFIELD_REGISTRY: dict[str, dict[str, str]] = { + "charmm36m": { + "url": "https://mackerell.umaryland.edu/download.php?filename=CHARMM_ff_params_files/charmm36-feb2026_cgenff-5.0.ff.tgz", + "dirname": "charmm36-feb2026_cgenff-5.0.ff", + "sha256": "c2b0c70e21cced150528ee25d205e5bd3367a5e55e85c2ff0403911980528151", + "description": "CHARMM36m protein + CGenFF 5.0 (Feb 2026)", + }, + "charmm36m-ljpme": { + "url": "https://mackerell.umaryland.edu/download.php?filename=CHARMM_ff_params_files/charmm36-feb2026_ljpme_cgenff-5.0.ff.tgz", + "dirname": "charmm36-feb2026_ljpme_cgenff-5.0.ff", + "sha256": "b2221dcba73066b6b5c30a52b9894216d03ea6c3e68206d9ce43857dd8116c21", + "description": "CHARMM36m + CGenFF 5.0, LJ-PME variant (Feb 2026)", + }, +} + + +def get_forcefield_dir() -> Path: + """Return the directory where downloaded force fields are stored.""" + from ..settings import settings # noqa: PLC0415 + + return settings.gromacs_forcefield_dir + + +def get_gromacs_env(base_env: dict[str, str] | None = None) -> dict[str, str]: + """Return an environment with configured GROMACS force fields on GMXLIB.""" + from ..settings import settings # noqa: PLC0415 + + return settings.gromacs_env(base_env) + + +# AMBER-style protonation residue names mapped to their CHARMM equivalents. +# Histidine tautomers map to 3-character names pdb2gmx accepts; protonated +# GLU/ASP map to 4-character CHARMM names that cannot fit a PDB residue-name +# column and are rejected below. +_CHARMM_PROTONATION_ALIASES = { + "HID": "HSD", + "HIE": "HSE", + "HIP": "HSP", + "GLH": "GLUP", + "ASH": "ASPP", + "LYN": "LSN", +} + +# Histidine tautomers share a titratable identity but not a common name prefix, +# so a "HIS" override must still match a residue already named HID/HSE/etc. +_HISTIDINE_RESNAMES = {"HIS", "HID", "HIE", "HIP", "HSD", "HSE", "HSP"} + + +def _resolve_protonation_residue_name(residue_name: str, forcefield: str | None) -> str: + """Return the residue name to write into the PDB for a protonation override.""" + residue_name = residue_name.upper() + if forcefield and forcefield.lower().startswith("charmm"): + residue_name = _CHARMM_PROTONATION_ALIASES.get(residue_name, residue_name) + + if len(residue_name) != 3: + raise ValueError( + f"Protonation residue name '{residue_name}' cannot be written to a PDB " + "residue-name column (needs exactly 3 characters). Protonated GLU/ASP in " + "CHARMM use 4-character names (GLUP/ASPP) that cannot be set by residue " + "renaming; only 3-character states such as histidine tautomers " + "(HID/HIE/HIP -> HSD/HSE/HSP) or neutral lysine (LYN -> LSN) are supported." + ) + return residue_name + + +def _override_matches_residue(resname_prefix: str, current_resname: str) -> bool: + """Return True if a protonation override applies to the residue currently present.""" + if current_resname.startswith(resname_prefix): + return True + return resname_prefix in _HISTIDINE_RESNAMES and current_resname in _HISTIDINE_RESNAMES + + +def _distance(coords_a: tuple[float, float, float], coords_b: tuple[float, float, float]) -> float: + return sum((a - b) ** 2 for a, b in zip(coords_a, coords_b, strict=True)) ** 0.5 + + +_ResidueKey = tuple[str, int, str] + + +def _parse_residue_reference(reference: str) -> tuple[str | None, str, int, str]: + """Parse a possibly chain-qualified PDB residue reference. + + References have the form ``RESNAME`` or + ``CHAIN:RESNAME``. For example, ``HIS15``, + ``A:HIS15``, and ``H:HIS100A`` are valid. + """ + match = re.fullmatch(r"(?:([A-Za-z0-9]):)?([A-Za-z]+)(\d+)([A-Za-z]?)", reference) + if not match: + raise ValueError( + f"Invalid residue reference '{reference}'. Expected format " + "RESNAME or CHAIN:RESNAME, " + "e.g. 'HIS15', 'A:HIS15', or 'H:HIS100A'." + ) + insertion_code = match.group(4) + return match.group(1), match.group(2).upper(), int(match.group(3)), insertion_code + + +def _format_residue_key(key: _ResidueKey, resname: str) -> str: + """Format a concrete PDB residue key as a configuration reference.""" + chain, resid, insertion_code = key + residue = f"{resname}{resid}{insertion_code}" + return f"{chain}:{residue}" if chain else residue + + +def _read_cysteine_sg_atoms(pdb_path: Path) -> dict[_ResidueKey, tuple[float, float, float]]: + """Read cysteine SG atoms in PDB encounter order, keyed by residue identity.""" + atoms: dict[_ResidueKey, tuple[float, float, float]] = {} + + with open(pdb_path) as f: + for line in f: + if not line.startswith(("ATOM", "HETATM")) or len(line) < 54: + continue + + atom_name = line[12:16].strip() + resname = line[17:20].strip() + if atom_name != "SG" or resname not in _CYSTEINE_RESNAMES: + continue + + chain = line[21].strip() + resid = int(line[22:26].strip()) + insertion_code = line[26].strip() + coords = ( + float(line[30:38].strip()), + float(line[38:46].strip()), + float(line[46:54].strip()), + ) + key = (chain, resid, insertion_code) + if key in atoms: + raise ValueError( + "Multiple cysteine SG atoms share the residue identifier " + f"'{_format_residue_key(key, resname)}' in {pdb_path}." + ) + atoms[key] = coords + + return atoms + + +def _resolve_cysteine_reference( + reference: str, + sg_atoms: dict[_ResidueKey, tuple[float, float, float]], + pdb_path: Path, +) -> _ResidueKey: + """Resolve a cysteine reference to a concrete PDB residue key. + + References without a chain are accepted only when they identify exactly one + cysteine. An omitted insertion code means the residue has no insertion code. + """ + chain, resname, resid, insertion_code = _parse_residue_reference(reference) + if resname not in _CYSTEINE_RESNAMES: + raise ValueError( + f"Disulfide residue reference '{reference}' must be a cysteine " + f"(one of {sorted(_CYSTEINE_RESNAMES)})." + ) + + not_found = ( + f"Disulfide residue reference '{reference}' is not a cysteine SG atom in {pdb_path}." + ) + candidates = [ + key + for key in sg_atoms + if key[1] == resid and (chain is None or key[0] == chain) and key[2] == insertion_code + ] + if not candidates: + raise ValueError(not_found) + if len(candidates) > 1: + choices = sorted(_format_residue_key(key, resname) for key in candidates) + raise ValueError( + f"Disulfide residue reference '{reference}' is ambiguous across chains or " + f"insertion codes. Use one of these fully qualified references: {choices}." + ) + return candidates[0] + + +def _build_disulfide_prompt_input( + pdb_path: Path, + disulfide_bonds: list[tuple[str, str]], + cutoff_angstrom: float = _DISULFIDE_CUTOFF_ANGSTROM, +) -> str: + """Build deterministic yes/no input for pdb2gmx disulfide prompts.""" + sg_atoms = _read_cysteine_sg_atoms(pdb_path) + + requested: set[frozenset[_ResidueKey]] = set() + for a, b in disulfide_bonds: + key_a = _resolve_cysteine_reference(a, sg_atoms, pdb_path) + key_b = _resolve_cysteine_reference(b, sg_atoms, pdb_path) + if key_a == key_b: + raise ValueError(f"Invalid disulfide bond with identical residues: {a}-{b}") + requested.add(frozenset((key_a, key_b))) + + # pdb2gmx presents -ss prompts in PDB atom encounter order. Do not sort these + # keys: doing so can attach each y/n answer to a different chain's prompt. + candidate_pairs: list[tuple[_ResidueKey, _ResidueKey]] = [] + keys = list(sg_atoms) + for i, key_a in enumerate(keys): + for key_b in keys[i + 1 :]: + if _distance(sg_atoms[key_a], sg_atoms[key_b]) <= cutoff_angstrom: + candidate_pairs.append((key_a, key_b)) + + candidate_identities = {frozenset(pair) for pair in candidate_pairs} + missing_pairs = requested - candidate_identities + if missing_pairs: + formatted_missing = sorted(tuple(sorted(pair)) for pair in missing_pairs) + raise ValueError( + "Requested disulfide bonds were not detected as close CYS SG pairs " + f"within {cutoff_angstrom:.1f} A in {pdb_path}: {formatted_missing}" + ) + + answers = ["y" if frozenset(pair) in requested else "n" for pair in candidate_pairs] + formatted_requested = sorted(tuple(sorted(pair)) for pair in requested) + logger.info( + f"pdb2gmx disulfide candidates: {candidate_pairs}; requested: {formatted_requested}" + ) + return "\n".join(answers) + "\n" + + +def _read_pdb_residues(pdb_path: Path) -> dict[_ResidueKey, str]: + """Read physical PDB residues keyed by chain, residue id, and insertion code.""" + residues: dict[_ResidueKey, str] = {} + with open(pdb_path) as f: + for line in f: + if not line.startswith(("ATOM", "HETATM")) or len(line) < 27: + continue + try: + resid = int(line[22:26].strip()) + except ValueError: + continue + key = (line[21].strip(), resid, line[26].strip()) + resname = line[17:20].strip() + previous = residues.setdefault(key, resname) + if previous != resname: + raise ValueError( + f"Residue identifier '{_format_residue_key(key, resname)}' has " + f"inconsistent residue names '{previous}' and '{resname}' in {pdb_path}." + ) + return residues + + +def _resolve_protonation_reference( + reference: str, residues: dict[_ResidueKey, str], pdb_path: Path +) -> tuple[_ResidueKey, str]: + """Resolve a protonation reference to one physical PDB residue.""" + chain, resname_prefix, resid, insertion_code = _parse_residue_reference(reference) + candidates = [ + (key, current_resname) + for key, current_resname in residues.items() + if key[1] == resid + and (chain is None or key[0] == chain) + and key[2] == insertion_code + and _override_matches_residue(resname_prefix, current_resname) + ] + if not candidates: + raise ValueError( + f"Protonation state override '{reference}' matched no residue in {pdb_path}. " + "Check the residue number, insertion code, chain, and name against the input PDB." + ) + if len(candidates) > 1: + choices = sorted( + _format_residue_key(key, current_resname) for key, current_resname in candidates + ) + raise ValueError( + f"Protonation state override '{reference}' is ambiguous across chains or " + f"insertion codes. Use one of these fully qualified references: {choices}." + ) + return candidates[0] + + +def _apply_protonation_states( + pdb_path: Path, + protonation_states: dict[str, str], + output_dir: Path, + forcefield: str | None = None, +) -> Path: + """Apply protonation state overrides by renaming residues in the PDB. + + pdb2gmx selects protonation states based on residue names. This function renames + matching residues in the PDB so pdb2gmx applies the requested 3-character residue + names without interactive prompts. AMBER-style names are translated to CHARMM + names when a CHARMM force field is selected (e.g. HIE -> HSE). Histidine tautomers + are matched as a family, so a "HIS" override still applies when the residue is + already named HID/HSE/etc. An override that matches no residue raises rather than + being silently dropped. + + Parameters + ---------- + pdb_path : Path + Input PDB file (absolute path). + protonation_states : dict[str, str] + Mapping of "RESNAME" (or chain-qualified "CHAIN:RESNAME") + to target residue name, e.g. {"HIS15": "HIE"} or {"A:HIS15": "HIE"}. An + unqualified key that matches the residue in more than one chain is + rejected as ambiguous. For CHARMM only 3-character states are + expressible (histidine tautomers, neutral lysine). + output_dir : Path + Directory for the modified PDB. + forcefield : str | None + Selected pdb2gmx force field, used for force-field-specific aliases. + + Returns + ------- + Path + Path to the modified PDB file. + + """ + output_pdb = output_dir / "protonation_adjusted.pdb" + + residues = _read_pdb_residues(pdb_path) + resolved_overrides: dict[_ResidueKey, tuple[str, str]] = {} + for reference, new_name in protonation_states.items(): + residue_key, _ = _resolve_protonation_reference(reference, residues, pdb_path) + target_name = _resolve_protonation_residue_name(new_name, forcefield) + if residue_key in resolved_overrides: + previous_reference = resolved_overrides[residue_key][0] + raise ValueError( + f"Protonation state overrides '{previous_reference}' and '{reference}' " + "resolve to the same residue. Specify it only once." + ) + resolved_overrides[residue_key] = (reference, target_name) + + with open(pdb_path) as f_in, open(output_pdb, "w") as f_out: + for line in f_in: + output_line = line + if output_line.startswith(("ATOM", "HETATM")) and len(output_line) >= 27: + try: + current_resid = int(output_line[22:26].strip()) + except ValueError: + f_out.write(output_line) + continue + residue_key = ( + output_line[21].strip(), + current_resid, + output_line[26].strip(), + ) + override = resolved_overrides.get(residue_key) + if override is not None: + new_name = override[1] + output_line = output_line[:17] + f"{new_name:<3s}" + output_line[20:] + f_out.write(output_line) + + logger.info(f"Protonation states applied: {protonation_states}") + return output_pdb + + +def check_gmx_available() -> Path: + """Verify that the GROMACS gmx binary is available. + + Checks the configured path from settings first, then falls back to PATH. + + Returns + ------- + Path + Path to the gmx binary. + + Raises + ------ + RuntimeError + If gmx is not found. + + """ + from ..settings import settings # noqa: PLC0415 + + configured = settings.gromacs_gmx_path + if configured is not None: + if configured.is_file(): + return configured + raise RuntimeError( + f"Configured GROMACS path '{configured}' does not exist. " + "Check [gromacs] GMX_PATH in your mdfactory config." + ) + + gmx_path = shutil.which("gmx") + if gmx_path is None: + raise RuntimeError( + "GROMACS 'gmx' binary not found on PATH. " + "Either set [gromacs] GMX_PATH in mdfactory config, " + "or ensure 'gmx' is on PATH (e.g. 'module load gromacs')." + ) + return Path(gmx_path) + + +def download_forcefield(name: str) -> Path: + """Download a force field from the registry to the local data directory. + + Parameters + ---------- + name : str + Registry key (e.g. "charmm36m"). + + Returns + ------- + Path + Path to the extracted force field directory. + + Raises + ------ + ValueError + If the name is not in the registry. + + """ + if name not in FORCEFIELD_REGISTRY: + raise ValueError( + f"Force field '{name}' is not in the download registry. " + f"Available: {sorted(FORCEFIELD_REGISTRY.keys())}." + ) + + entry = FORCEFIELD_REGISTRY[name] + ff_dir = get_forcefield_dir() + ff_dir.mkdir(parents=True, exist_ok=True) + + target = ff_dir / entry["dirname"] + if target.is_dir(): + logger.info(f"Force field '{name}' already present at {target}") + return target + + logger.info(f"Downloading force field '{name}' from {entry['url']}...") + response = urlopen(entry["url"], timeout=120) # noqa: S310 + payload = response.read() + + expected_sha256 = entry.get("sha256") + if expected_sha256: + actual_sha256 = hashlib.sha256(payload).hexdigest() + if actual_sha256 != expected_sha256: + raise RuntimeError( + f"Force field '{name}' download failed integrity check.\n" + f"Expected SHA-256: {expected_sha256}\n" + f"Actual SHA-256: {actual_sha256}\n" + "The download may be corrupted or tampered with. If the upstream file was " + "legitimately updated, update the pinned 'sha256' in FORCEFIELD_REGISTRY." + ) + + data = io.BytesIO(payload) + with tarfile.open(fileobj=data, mode="r:gz") as tar: + tar.extractall(path=ff_dir, filter="data") + + if not target.is_dir(): + raise RuntimeError( + f"Download succeeded but expected directory '{entry['dirname']}' " + f"not found in {ff_dir}. Tarball may have unexpected structure." + ) + + logger.info(f"Force field '{name}' installed to {target}") + return target + + +def download_all_forcefields() -> list[Path]: + """Download all force fields in the registry. Idempotent.""" + paths = [] + for name in FORCEFIELD_REGISTRY: + paths.append(download_forcefield(name)) + return paths + + +def _get_gmx_search_paths() -> list[Path]: + """Return the directories GROMACS searches for force fields.""" + search_paths: list[Path] = [] + + gmx_bin = str(check_gmx_available()) + result = subprocess.run( + [gmx_bin, "--version"], + text=True, + capture_output=True, + timeout=10, + check=False, + ) + for line in result.stdout.splitlines(): + if "Data prefix" in line: + data_prefix = Path(line.split(":", 1)[1].strip()) + top_dir = data_prefix / "share" / "gromacs" / "top" + if top_dir.is_dir(): + search_paths.append(top_dir) + break + + gmxlib = get_gromacs_env().get("GMXLIB", "") + for p in gmxlib.split(":"): + if p and Path(p).is_dir(): + search_paths.append(Path(p)) + + search_paths.append(Path.cwd()) + return search_paths + + +def resolve_forcefield(forcefield: str) -> str: + """Resolve a friendly force field name to the actual directory stem. + + If the name is a registry key (e.g. "charmm36m"), returns the actual + directory name (e.g. "charmm36-feb2026_cgenff-5.0"). Otherwise returns + the name unchanged (for built-in FFs like "charmm27"). + + """ + if forcefield in FORCEFIELD_REGISTRY: + return FORCEFIELD_REGISTRY[forcefield]["dirname"].removesuffix(".ff") + return forcefield + + +def check_forcefield_available(forcefield: str) -> None: + """Verify that the requested force field is findable by GROMACS. + + Parameters + ---------- + forcefield : str + Force field name as passed to ``gmx pdb2gmx -ff``, or a registry + key (e.g. "charmm36m"). + + Raises + ------ + ValueError + If the force field is not found and not in the registry. + + """ + # Resolve friendly name to actual dirname + resolved = resolve_forcefield(forcefield) + ff_dirname = f"{resolved}.ff" + + search_paths = _get_gmx_search_paths() + + for search_dir in search_paths: + if (search_dir / ff_dirname).is_dir(): + return + + available = set() + for search_dir in search_paths: + if search_dir.is_dir(): + for entry in search_dir.iterdir(): + if entry.is_dir() and entry.name.endswith(".ff"): + available.add(entry.stem) + + raise ValueError( + f"Force field '{forcefield}' not found. " + f"Searched: {[str(p) for p in search_paths]}.\n" + f"Available force fields: {sorted(available)}.\n" + f"Downloadable: {sorted(FORCEFIELD_REGISTRY.keys())}.\n" + "Install the force field to a search path, set [gromacs] FORCEFIELD_DIR, " + "or download registered force fields with `mdfactory config init`." + ) + + +def clean_pdb(pdb_path: Path, output_path: Path) -> Path: + """Clean a PDB file using PDBFixer: remove heterogens, add missing heavy atoms. + + Parameters + ---------- + pdb_path : Path + Input PDB file. + output_path : Path + Where to write the cleaned PDB. + + Returns + ------- + Path + Path to the cleaned PDB file. + + """ + from pdbfixer import PDBFixer # noqa: PLC0415 + + fixer = PDBFixer(filename=str(pdb_path)) + fixer.removeHeterogens(keepWater=False) + fixer.findMissingResidues() + fixer.findMissingAtoms() + fixer.addMissingAtoms() + + from openmm.app import PDBFile # noqa: PLC0415 + + # keepIds=True preserves the input chain IDs and residue numbers so that + # user-supplied chain, disulfide, and protonation references (which name + # residues by their original numbering) still resolve after cleaning. + with open(output_path, "w") as f: + PDBFile.writeFile(fixer.topology, fixer.positions, f, keepIds=True) + + logger.info(f"PDB cleaned: {pdb_path} -> {output_path}") + return output_path + + +def _parse_protein_molecule_names(top_path: Path) -> list[str]: + """Return protein moleculetype names from a topology's [ molecules ] section. + + pdb2gmx names each protein moleculetype ``Protein_chain_`` (or ``Protein`` + when chains are merged), listing each once. Water and ion entries are ignored. + """ + names: list[str] = [] + in_molecules = False + with open(top_path) as f: + for line in f: + stripped = line.strip() + if not stripped or stripped.startswith(";"): + continue + if stripped.startswith("["): + in_molecules = stripped.strip("[] ").lower() == "molecules" + continue + if in_molecules and stripped.split()[0].startswith("Protein"): + names.append(stripped.split()[0]) + return names + + +def _collect_topology_include_files(output_dir: Path) -> list[Path]: + """Return the local .itp files a pdb2gmx topology includes alongside topol.top. + + Covers per-chain moleculetype files (``topol_Protein_chain_*.itp``) and position + restraint files (``posre*.itp``); for a single-chain build this is just + ``posre.itp``. Force-field library includes live inside the bundled .ff directory + and are handled separately. + """ + includes: list[Path] = [] + for pattern in ("topol_Protein_chain_*.itp", "posre*.itp"): + includes.extend(sorted(output_dir.glob(pattern))) + return includes + + +def run_pdb2gmx( + pdb_path: Path, + config: Pdb2gmxConfig, + disulfide_bonds: list[tuple[str, str]], + protonation_states: dict[str, str], + output_dir: Path, + chains: list[str] | None = None, +) -> GromacsProteinParameterSet: + """Run gmx pdb2gmx to generate GROMACS topology from a protein PDB. + + Parameters + ---------- + pdb_path : Path + Cleaned PDB file. + config : Pdb2gmxConfig + Force field and water model configuration. + disulfide_bonds : list[tuple[str, str]] + Cysteine residue pairs for disulfide bonds, e.g. [("CYS6", "CYS127")]. + protonation_states : dict[str, str] + Residue-specific protonation state overrides. + output_dir : Path + Directory for output files. + chains : list[str] | None + Declared chain identifiers. When given, the chains pdb2gmx produces must + match exactly. When omitted, the PDB must yield a single protein chain. + + Returns + ------- + GromacsProteinParameterSet + Paths to the generated topology, structure, and per-chain include files. + + """ + chains = chains or [] + gmx_bin = str(check_gmx_available()) + check_forcefield_available(config.forcefield) + resolved_ff = resolve_forcefield(config.forcefield) + + output_dir.mkdir(parents=True, exist_ok=True) + + # Resolve all paths to absolute to avoid cwd confusion with subprocess + pdb_path = pdb_path.resolve() + output_dir = output_dir.resolve() + + structure_file = output_dir / "processed.gro" + topology_file = output_dir / "topol.top" + posre_file = output_dir / "posre.itp" + + # pdb2gmx runs with cwd=output_dir; pass output filenames relative so the + # generated topology contains a relative `#include "posre.itp"` rather than an + # absolute path. The force-field #includes it writes are absolute and are made + # relative later by bundle_forcefield_into_topology. + cmd = [ + gmx_bin, + "pdb2gmx", + "-f", + str(pdb_path), + "-o", + structure_file.name, + "-p", + topology_file.name, + "-i", + posre_file.name, + "-ff", + resolved_ff, + "-water", + config.water_model, + ] + + if config.ignore_hydrogens: + cmd.append("-ignh") + + if config.merge_all: + cmd.extend(["-merge", "all"]) + + # Apply protonation state overrides by renaming residues in the PDB + # before pdb2gmx processes it (standard approach for CHARMM/AMBER FFs) + if protonation_states: + pdb_path = _apply_protonation_states( + pdb_path, protonation_states, output_dir, forcefield=config.forcefield + ) + cmd[cmd.index("-f") + 1] = str(pdb_path) + + pdb2gmx_input = "" + if disulfide_bonds: + cmd.append("-ss") + pdb2gmx_input = _build_disulfide_prompt_input(pdb_path, disulfide_bonds) + + logger.info(f"Running pdb2gmx: {' '.join(cmd)}") + + result = subprocess.run( + cmd, + cwd=str(output_dir), + text=True, + input=pdb2gmx_input, + capture_output=True, + timeout=120, + env=get_gromacs_env(), + check=False, + ) + + if result.returncode != 0: + raise RuntimeError( + f"gmx pdb2gmx failed (exit code {result.returncode}).\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" + ) + + logger.info("pdb2gmx completed successfully.") + + # Verify expected outputs exist. The position-restraint file is named per chain + # for multi-chain inputs, so it is checked via the include collection below + # rather than by a fixed name here. + for path in [structure_file, topology_file]: + if not path.is_file(): + raise FileNotFoundError( + f"Expected pdb2gmx output not found: {path}\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" + ) + + # Each chain becomes its own Protein_chain_ moleculetype, listed once in + # [ molecules ]. Declared chains must match exactly; an undeclared multi-chain + # PDB is an error so subunits are never silently dropped or merged. + produced_names = _parse_protein_molecule_names(topology_file) + produced_chains = [ + name[len("Protein_chain_") :] + for name in produced_names + if name.startswith("Protein_chain_") + ] + + if chains: + if set(produced_chains) != set(chains): + raise ValueError( + f"Declared protein chains {sorted(chains)} do not match the chains " + f"pdb2gmx produced {sorted(produced_chains)}. Update protein.chains to " + "list exactly the chain identifiers present in the PDB." + ) + elif len(produced_names) > 1: + raise ValueError( + f"pdb2gmx produced multiple protein chains {sorted(produced_chains)}, but " + "none were declared. List each subunit explicitly via protein.chains." + ) + + include_files = _collect_topology_include_files(output_dir) + + total_charge = extract_charge_from_topology(topology_file) + logger.info(f"Protein net charge from topology: {total_charge}") + + return GromacsProteinParameterSet( + topology_file=topology_file, + structure_file=structure_file, + topology_include_files=include_files, + chains=produced_chains, + forcefield=config.forcefield, + water_model=config.water_model, + total_charge=total_charge, + ) + + +def extract_charge_from_topology(top_path: Path) -> int: + """Parse the topology file to determine the net system charge. + + Reads the [ atoms ] section(s) and sums the charge column. + + Parameters + ---------- + top_path : Path + Path to the GROMACS .top file. + + Returns + ------- + int + Net formal charge (rounded to nearest integer). + + """ + total_charge = 0.0 + in_atoms = False + + with open(top_path) as f: + for line in f: + stripped = line.strip() + + if stripped.startswith("#include"): + # Follow includes only for local itp files (protein chains, + # position restraints), not force field library files + include_path = stripped.split('"')[1] if '"' in stripped else None + if include_path and ".ff/" not in include_path: + itp_path = top_path.parent / include_path + if itp_path.is_file(): + total_charge += _sum_charges_from_itp(itp_path) + continue + + if stripped.startswith("["): + section = stripped.strip("[] ").lower() + in_atoms = section == "atoms" + continue + + if in_atoms and stripped and not stripped.startswith(";"): + parts = stripped.split() + if len(parts) >= 7: + try: + total_charge += float(parts[6]) + except (ValueError, IndexError): + pass + + return round(total_charge) + + +def _sum_charges_from_itp(itp_path: Path) -> float: + """Sum charges from the [ atoms ] section of an .itp file.""" + total = 0.0 + in_atoms = False + + with open(itp_path) as f: + for line in f: + stripped = line.strip() + if stripped.startswith("["): + section = stripped.strip("[] ").lower() + in_atoms = section == "atoms" + continue + if in_atoms and stripped and not stripped.startswith(";"): + parts = stripped.split() + if len(parts) >= 7: + try: + total += float(parts[6]) + except (ValueError, IndexError): + pass + return total + + +def update_topology_molecules( + top_path: Path, n_water: int, num_na: int, num_cl: int, water_name: str = "SOL" +) -> None: + """Update the [ molecules ] section of a topology file after solvation/ionization. + + Appends water and ion molecule counts to the topology. + + Parameters + ---------- + top_path : Path + Path to the GROMACS .top file (modified in-place). + n_water : int + Number of water molecules. + num_na : int + Number of Na+ ions. + num_cl : int + Number of Cl- ions. + water_name : str + Residue name for water (default "SOL"). + + """ + with open(top_path, "a") as f: + if n_water > 0: + f.write(f"{water_name:<20s} {n_water}\n") + if num_na > 0: + f.write(f"{'NA':<20s} {num_na}\n") + if num_cl > 0: + f.write(f"{'CL':<20s} {num_cl}\n") + + logger.info(f"Topology updated: {n_water} water, {num_na} Na+, {num_cl} Cl-") + + +def bundle_forcefield_into_topology(top_path: Path) -> str | None: + """Copy the force field referenced by #include lines next to the topology. + + pdb2gmx writes force-field ``#include`` lines that point either at the absolute + location where it found the force field (its GMXLIB entry) or, for built-in + GROMACS force fields, at a relative path such as ``charmm27.ff/forcefield.itp``. + Copy that ``.ff`` directory next to the topology so the includes resolve from + the topology's own directory without GMXLIB and the build directory stays + portable. Absolute includes are also rewritten to the bundled relative form; + relative includes already resolve once the directory sits alongside them and + are left untouched. Mirrors the CGenFF strategy in ``generate_gromacs_topology``. + + Parameters + ---------- + top_path : Path + Path to the GROMACS .top file (modified in-place). + + Returns + ------- + str or None + Name of the bundled force-field directory (e.g. "charmm36-....ff"), or + None if the topology has no force-field includes. + + Raises + ------ + RuntimeError + If a relative ``.ff`` include cannot be resolved against any GROMACS + search path, so it cannot be bundled for a portable topology. + + """ + lines = top_path.read_text().splitlines(keepends=True) + ff_dir_name: str | None = None + copied_sources: set[Path] = set() + search_paths: list[Path] | None = None + rewrote_line = False + + for i, line in enumerate(lines): + stripped = line.strip() + if not stripped.startswith("#include") or '"' not in stripped: + continue + include_path = stripped.split('"')[1] + if ".ff/" not in include_path: + continue + + parts = Path(include_path).parts + ff_index = next(idx for idx, part in enumerate(parts) if part.endswith(".ff")) + ff_dir_name = parts[ff_index] + remainder = "/".join(parts[ff_index + 1 :]) + is_absolute = Path(include_path).is_absolute() + + if is_absolute: + ff_source = Path(*parts[: ff_index + 1]) + else: + # Built-in force fields emit relative includes; resolve the .ff directory + # against the GROMACS search paths so it can be bundled. + if search_paths is None: + search_paths = _get_gmx_search_paths() + ff_source = next( + (sp / ff_dir_name for sp in search_paths if (sp / ff_dir_name).is_dir()), + None, + ) + if ff_source is None: + raise RuntimeError( + f"Force-field directory '{ff_dir_name}' referenced by {top_path} was " + f"not found in any GROMACS search path: {[str(p) for p in search_paths]}. " + "Cannot bundle it for a portable topology." + ) + + # A topology has several includes into the same .ff directory + # (forcefield.itp, water, ions); copy the directory only once. + if ff_source not in copied_sources: + shutil.copytree(ff_source, top_path.parent / ff_dir_name, dirs_exist_ok=True) + copied_sources.add(ff_source) + + # Relative includes already resolve from the topology's directory; only + # absolute includes need rewriting to the bundled relative form. + if is_absolute: + lines[i] = line.replace(include_path, f"{ff_dir_name}/{remainder}") + rewrote_line = True + + if rewrote_line: + top_path.write_text("".join(lines)) + if ff_dir_name is not None: + logger.info(f"Bundled force field '{ff_dir_name}' into {top_path.parent}") + + return ff_dir_name + + +def validate_with_grompp(topology: Path, structure: Path, mdp: Path, cwd: Path) -> None: + """Run gmx grompp as a validation check for topology/coordinate consistency. + + Parameters + ---------- + topology : Path + Path to topology.top file. + structure : Path + Path to structure .gro file. + mdp : Path + Path to MDP file (e.g., em.mdp). + cwd : Path + Working directory for the command. + + Raises + ------ + RuntimeError + If grompp reports errors. + + """ + gmx_bin = str(check_gmx_available()) + tpr_out = cwd / "check.tpr" + cmd = [ + gmx_bin, + "grompp", + "-f", + str(mdp), + "-c", + str(structure), + "-p", + str(topology), + "-o", + str(tpr_out), + "-maxwarn", + "0", + ] + + result = subprocess.run( + cmd, + cwd=str(cwd), + text=True, + input="", + capture_output=True, + timeout=60, + env=get_gromacs_env(), + check=False, + ) + + # Clean up grompp artifacts + if tpr_out.is_file(): + tpr_out.unlink() + mdout_path = cwd / "mdout.mdp" + if mdout_path.is_file(): + mdout_path.unlink() + + if result.returncode != 0: + raise RuntimeError( + f"gmx grompp validation failed (exit code {result.returncode}).\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" + ) + + logger.info("grompp validation passed.") diff --git a/mdfactory/simulation/openmm_utils.py b/mdfactory/simulation/openmm_utils.py index 126df55..a180ecd 100644 --- a/mdfactory/simulation/openmm_utils.py +++ b/mdfactory/simulation/openmm_utils.py @@ -1,5 +1,5 @@ -# ABOUTME: OpenMM simulation utilities for box compression and equilibration -# ABOUTME: Provides volume convergence monitoring and density-targeted compression +# ABOUTME: OpenMM simulation utilities for box compression, equilibration, and relaxation +# ABOUTME: Provides convergence checks, density-targeted compression, and protein restraints """OpenMM simulation utilities for box compression and equilibration.""" import sys @@ -635,3 +635,121 @@ def compress_equilibrate_bilayer( plt.savefig("equilibration_analysis.png", dpi=300, bbox_inches="tight") return ret + + +def relax_with_protein_restraints( + u, + pdb, + top, + protein_indices, + restraint_k=1000.0, + steps=10000, +): + """Minimize and briefly equilibrate with protein atoms position-restrained. + + Runs a short NPT simulation at 1 bar with harmonic restraints on + the specified protein atom indices, allowing water and ions to relax + around the fixed protein structure. + + Parameters + ---------- + u : mda.Universe + Reference MDAnalysis Universe whose topology is preserved. + pdb : str or Path + Path to the input PDB file for OpenMM. + top : str or Path + Path to the GROMACS topology file. + protein_indices : list of int + 0-based atom indices to restrain (typically protein heavy atoms). + restraint_k : float, optional + Restraint force constant in kJ/mol/nm^2. Default is 1000.0. + steps : int, optional + Number of MD steps after minimization. Default is 10000. + + Returns + ------- + mda.Universe + Universe with relaxed solvent positions. Protein positions + remain at their restrained coordinates. + + """ + print("Loading GROMACS files for protein relaxation...") + gro = app.PDBFile(str(pdb)) + top_file = app.GromacsTopFile(str(top), periodicBoxVectors=gro.topology.getPeriodicBoxVectors()) + + print("Creating OpenMM system...") + system = top_file.createSystem( + nonbondedMethod=app.PME, + nonbondedCutoff=1.0 * unit.nanometer, + constraints=app.HBonds, + ) + + # Position restraints on protein atoms + restraint_force = mm.CustomExternalForce("k*((x-x0)^2+(y-y0)^2+(z-z0)^2)") + restraint_force.addGlobalParameter( + "k", restraint_k * unit.kilojoules_per_mole / unit.nanometer**2 + ) + restraint_force.addPerParticleParameter("x0") + restraint_force.addPerParticleParameter("y0") + restraint_force.addPerParticleParameter("z0") + + for idx in protein_indices: + pos = gro.positions[idx] + restraint_force.addParticle( + idx, + [ + pos[0].value_in_unit(unit.nanometer), + pos[1].value_in_unit(unit.nanometer), + pos[2].value_in_unit(unit.nanometer), + ], + ) + + system.addForce(restraint_force) + + # Isotropic barostat at 1 bar + barostat = mm.MonteCarloBarostat(1.0 * unit.bar, 300 * unit.kelvin) + system.addForce(barostat) + + integrator = mm.LangevinMiddleIntegrator( + 300 * unit.kelvin, 1 / unit.picosecond, 0.002 * unit.picoseconds + ) + + simulation = app.Simulation(top_file.topology, system, integrator) + simulation.context.setPositions(gro.positions) + + simulation.reporters.append( + app.StateDataReporter( + sys.stdout, + 1000, + step=True, + potentialEnergy=True, + temperature=True, + density=True, + volume=True, + ) + ) + + print("Minimizing energy (protein restrained)...") + simulation.minimizeEnergy() + + print(f"Running {steps} steps of NPT relaxation (protein restrained)...") + simulation.step(steps) + + positions = simulation.context.getState(getPositions=True).getPositions() + state = simulation.context.getState() + box_vectors = state.getPeriodicBoxVectors() + simulation.topology.setPeriodicBoxVectors(box_vectors) + + with open("relaxed_system.pdb", "w") as f: + app.PDBFile.writeFile(simulation.topology, positions, f, keepIds=True) + with open("relaxed_system.mmcif", "w") as f: + app.PDBxFile.writeFile(simulation.topology, positions, f, keepIds=True) + + u_tmp = mda.Universe(app.PDBxFile("relaxed_system.mmcif")) + ret = mda.Merge(u.atoms) + ret.atoms.positions = u_tmp.atoms.positions + ret.dimensions = u_tmp.dimensions + ret.atoms.positions = ret.atoms.wrap(compound="residues") + + print("Protein relaxation complete.") + return ret diff --git a/mdfactory/tests/fixtures/rescue_test/em.mdp b/mdfactory/tests/fixtures/rescue_test/em.mdp new file mode 100644 index 0000000..d57ef87 --- /dev/null +++ b/mdfactory/tests/fixtures/rescue_test/em.mdp @@ -0,0 +1,11 @@ +; Energy minimisation — standard parameters for pre-equilibration. +integrator = steep +emtol = 1000.0 +emstep = 0.01 +nsteps = 5000 +nstlist = 1 +cutoff-scheme = Verlet +coulombtype = PME +rcoulomb = 0.4 +rvdw = 0.4 +pbc = xyz diff --git a/mdfactory/tests/fixtures/rescue_test/nvt.mdp b/mdfactory/tests/fixtures/rescue_test/nvt.mdp new file mode 100644 index 0000000..441aba7 --- /dev/null +++ b/mdfactory/tests/fixtures/rescue_test/nvt.mdp @@ -0,0 +1,20 @@ +; NVT equilibration — intentionally oversized dt to trigger LINCS failure. +; dt = 0.1 ps is ~50x the normal value (0.002 ps). +; At this timestep, constraint violations blow up immediately. +integrator = md +dt = 0.1 +nsteps = 100 +nstlist = 1 +cutoff-scheme = Verlet +coulombtype = PME +rcoulomb = 0.4 +rvdw = 0.4 +pbc = xyz +tcoupl = v-rescale +tc-grps = System +tau-t = 0.1 +ref-t = 300 +gen-vel = yes +gen-temp = 300 +constraints = h-bonds +constraint-algorithm = LINCS diff --git a/mdfactory/tests/fixtures/rescue_test/system.gro b/mdfactory/tests/fixtures/rescue_test/system.gro new file mode 100644 index 0000000..c5f02a5 --- /dev/null +++ b/mdfactory/tests/fixtures/rescue_test/system.gro @@ -0,0 +1,12 @@ +Rescue test: 3 SPC waters in small box + 9 + 1SOL OW 1 0.500 0.500 0.500 + 1SOL HW1 2 0.557 0.574 0.500 + 1SOL HW2 3 0.443 0.574 0.500 + 2SOL OW 4 0.200 0.200 0.200 + 2SOL HW1 5 0.257 0.274 0.200 + 2SOL HW2 6 0.143 0.274 0.200 + 3SOL OW 7 0.800 0.800 0.800 + 3SOL HW1 8 0.857 0.874 0.800 + 3SOL HW2 9 0.743 0.874 0.800 + 1.00000 1.00000 1.00000 diff --git a/mdfactory/tests/fixtures/rescue_test/topology.top b/mdfactory/tests/fixtures/rescue_test/topology.top new file mode 100644 index 0000000..3c343e1 --- /dev/null +++ b/mdfactory/tests/fixtures/rescue_test/topology.top @@ -0,0 +1,5 @@ +; Minimal SPC water topology for rescue integration test. +; Force field includes are replaced at test time by the fixture +; to match whatever is available in the local GROMACS installation. +; +; Placeholder — overwritten by test fixture. diff --git a/mdfactory/tests/test_cli_cluster.py b/mdfactory/tests/test_cli_cluster.py new file mode 100644 index 0000000..cc87ee4 --- /dev/null +++ b/mdfactory/tests/test_cli_cluster.py @@ -0,0 +1,320 @@ +# ABOUTME: Tests for the `mdfactory config cluster` CLI command, verifying +# ABOUTME: SLURM autodiscovery output in human-readable and JSON formats. +# ABOUTME: Also covers the SLURM config-building glue in analysis_run / analysis_artifacts_run. +"""Tests for mdfactory config cluster CLI command and analysis SLURM glue.""" + +import json +from pathlib import Path +from unittest.mock import patch + +import pandas as pd +import pytest + +from mdfactory import cli +from mdfactory.performance import cluster as cluster_mod +from mdfactory.performance.slurm_config import SlurmConfig + + +class TestConfigClusterCommand: + """Tests for the config cluster CLI command.""" + + def test_config_cluster_no_slurm(self, monkeypatch, capsys): + """Test graceful message when SLURM is not available.""" + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: None) + + cli.config_cluster(json_output=False) + + captured = capsys.readouterr() + assert "SLURM cluster not detected" in captured.out + assert "login node" in captured.out + + def test_config_cluster_no_slurm_json(self, monkeypatch, capsys): + """Test JSON output when SLURM is not available.""" + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: None) + + cli.config_cluster(json_output=True) + + captured = capsys.readouterr() + data = json.loads(captured.out) + assert data["cluster"] is None + assert "error" in data + + def test_config_cluster_with_slurm(self, monkeypatch, capsys): + """Test human-readable output with SLURM cluster.""" + mock_partition = cluster_mod.Partition( + name="compute", + state="up", + max_time="7-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType(cpus=64, memory_mb=256 * 1024, gpu_specs=(), count=100), + ], + total_nodes=100, + is_default=True, + ) + gpu_partition = cluster_mod.Partition( + name="gpu", + state="up", + max_time="2-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType( + cpus=32, memory_mb=128 * 1024, gpu_specs=((4, "a100"),), count=20 + ), + ], + total_nodes=20, + is_default=False, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition, gpu_partition], + accounts=["myaccount", "shared"], + qos_policies=["normal", "high"], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + cli.config_cluster(json_output=False) + + captured = capsys.readouterr() + assert "SLURM Cluster Information" in captured.out + assert "Default Account: myaccount" in captured.out + assert "compute" in captured.out + assert "(default)" in captured.out + assert "gpu" in captured.out + assert "a100" in captured.out + assert "64 CPUs" in captured.out + assert "4x a100" in captured.out + + def test_config_cluster_with_slurm_json(self, monkeypatch, capsys): + """Test JSON output with SLURM cluster.""" + mock_partition = cluster_mod.Partition( + name="compute", + state="up", + max_time="7-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType( + cpus=64, + memory_mb=256 * 1024, + gpu_specs=(), + features=("avx512", "intel"), + count=100, + ), + ], + total_nodes=100, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["myaccount"], + qos_policies=["normal"], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + cli.config_cluster(json_output=True) + + captured = capsys.readouterr() + data = json.loads(captured.out) + + assert data["default_account"] == "myaccount" + assert data["accounts"] == ["myaccount"] + assert data["qos_policies"] == ["normal"] + assert len(data["partitions"]) == 1 + + part = data["partitions"][0] + assert part["name"] == "compute" + assert part["is_default"] is True + assert part["total_nodes"] == 100 + assert len(part["node_types"]) == 1 + + nt = part["node_types"][0] + assert nt["cpus"] == 64 + assert nt["memory_mb"] == 256 * 1024 + assert nt["gpu_specs"] == [] # No GPUs + assert nt["features"] == ["avx512", "intel"] + assert nt["count"] == 100 + + def test_config_cluster_down_partition(self, monkeypatch, capsys): + """Test display of partition with down state.""" + mock_partition = cluster_mod.Partition( + name="maintenance", + state="drained", + max_time="1:00:00", + default_time="0:30:00", + node_types=[cluster_mod.NodeType(cpus=16, memory_mb=32 * 1024, count=5)], + total_nodes=5, + is_default=False, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["myaccount"], + qos_policies=[], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + cli.config_cluster(json_output=False) + + captured = capsys.readouterr() + assert "maintenance" in captured.out + assert "[drained]" in captured.out + + +# --------------------------------------------------------------------------- +# Helpers shared by analysis_run / analysis_artifacts_run glue tests +# --------------------------------------------------------------------------- + +_FAKE_CFG = SlurmConfig(account="auto-account", partition="auto-part") +_EMPTY_DF = pd.DataFrame() + + +@pytest.fixture() +def fake_sim_paths(tmp_path: Path) -> list[Path]: + """Return a non-empty list of paths without touching the filesystem.""" + return [tmp_path / "sim1"] + + +# --------------------------------------------------------------------------- +# Tests: analysis_run SLURM config-building glue +# --------------------------------------------------------------------------- + + +class TestAnalysisRunSlurmGlue: + """Tests for the autodiscovery glue inside analysis_run (--slurm mode).""" + + def test_autodiscovery_used_when_no_account(self, monkeypatch, fake_sim_paths): + """account=None triggers SlurmConfig.from_cluster(); result is forwarded.""" + captured = {} + + def fake_submit(sim_paths, analysis_names, *, slurm, **kwargs): + captured["slurm"] = slurm + return _EMPTY_DF + + with ( + patch("mdfactory.cli._resolve_sim_paths", return_value=fake_sim_paths), + patch("mdfactory.cli.SlurmConfig.from_cluster", return_value=_FAKE_CFG) as mock_fc, + patch("mdfactory.cli.submit_analyses_slurm", side_effect=fake_submit), + patch("mdfactory.cli.determine_log_dir", return_value=fake_sim_paths[0].parent), + ): + cli.analysis_run( + source=fake_sim_paths[0].parent, + slurm=True, + account=None, + ) + + mock_fc.assert_called_once() + assert captured["slurm"] is _FAKE_CFG + + def test_autodiscovery_error_reraised_as_value_error(self, monkeypatch, fake_sim_paths): + """RuntimeError from from_cluster() is re-raised as ValueError with guidance.""" + with ( + patch("mdfactory.cli._resolve_sim_paths", return_value=fake_sim_paths), + patch( + "mdfactory.cli.SlurmConfig.from_cluster", + side_effect=RuntimeError("no account"), + ), + ): + with pytest.raises(ValueError, match="Please specify --account explicitly"): + cli.analysis_run( + source=fake_sim_paths[0].parent, + slurm=True, + account=None, + ) + + def test_explicit_account_skips_autodiscovery(self, monkeypatch, fake_sim_paths): + """When account is provided, from_cluster() is never called.""" + captured = {} + + def fake_submit(sim_paths, analysis_names, *, slurm, **kwargs): + captured["slurm"] = slurm + return _EMPTY_DF + + with ( + patch("mdfactory.cli._resolve_sim_paths", return_value=fake_sim_paths), + patch("mdfactory.cli.SlurmConfig.from_cluster") as mock_fc, + patch("mdfactory.cli.submit_analyses_slurm", side_effect=fake_submit), + patch("mdfactory.cli.determine_log_dir", return_value=fake_sim_paths[0].parent), + ): + cli.analysis_run( + source=fake_sim_paths[0].parent, + slurm=True, + account="explicit-account", + ) + + mock_fc.assert_not_called() + assert captured["slurm"].account == "explicit-account" + + +# --------------------------------------------------------------------------- +# Tests: analysis_artifacts_run SLURM config-building glue +# --------------------------------------------------------------------------- + + +class TestAnalysisArtifactsRunSlurmGlue: + """Tests for the autodiscovery glue inside analysis_artifacts_run (--slurm mode).""" + + def test_autodiscovery_used_when_no_account(self, monkeypatch, fake_sim_paths): + """account=None triggers SlurmConfig.from_cluster(); result is forwarded.""" + captured = {} + + def fake_submit(sim_paths, artifact_names, *, slurm, **kwargs): + captured["slurm"] = slurm + return _EMPTY_DF + + with ( + patch("mdfactory.cli._resolve_sim_paths", return_value=fake_sim_paths), + patch("mdfactory.cli.SlurmConfig.from_cluster", return_value=_FAKE_CFG) as mock_fc, + patch("mdfactory.cli.submit_artifacts_slurm", side_effect=fake_submit), + patch("mdfactory.cli.determine_log_dir", return_value=fake_sim_paths[0].parent), + ): + cli.analysis_artifacts_run( + source=fake_sim_paths[0].parent, + slurm=True, + account=None, + ) + + mock_fc.assert_called_once() + assert captured["slurm"] is _FAKE_CFG + + def test_autodiscovery_error_reraised_as_value_error(self, monkeypatch, fake_sim_paths): + """RuntimeError from from_cluster() is re-raised as ValueError with guidance.""" + with ( + patch("mdfactory.cli._resolve_sim_paths", return_value=fake_sim_paths), + patch( + "mdfactory.cli.SlurmConfig.from_cluster", + side_effect=RuntimeError("no account"), + ), + ): + with pytest.raises(ValueError, match="Please specify --account explicitly"): + cli.analysis_artifacts_run( + source=fake_sim_paths[0].parent, + slurm=True, + account=None, + ) + + def test_explicit_account_skips_autodiscovery(self, monkeypatch, fake_sim_paths): + """When account is provided, from_cluster() is never called.""" + captured = {} + + def fake_submit(sim_paths, artifact_names, *, slurm, **kwargs): + captured["slurm"] = slurm + return _EMPTY_DF + + with ( + patch("mdfactory.cli._resolve_sim_paths", return_value=fake_sim_paths), + patch("mdfactory.cli.SlurmConfig.from_cluster") as mock_fc, + patch("mdfactory.cli.submit_artifacts_slurm", side_effect=fake_submit), + patch("mdfactory.cli.determine_log_dir", return_value=fake_sim_paths[0].parent), + ): + cli.analysis_artifacts_run( + source=fake_sim_paths[0].parent, + slurm=True, + account="explicit-account", + ) + + mock_fc.assert_not_called() + assert captured["slurm"].account == "explicit-account" diff --git a/mdfactory/tests/test_cluster.py b/mdfactory/tests/test_cluster.py new file mode 100644 index 0000000..277ce2f --- /dev/null +++ b/mdfactory/tests/test_cluster.py @@ -0,0 +1,541 @@ +# ABOUTME: Unit tests for mdfactory.performance.cluster (SLURM autodiscovery). +# ABOUTME: Uses mocked sinfo/sacctmgr output — no SLURM required to run. +"""Tests for SLURM cluster autodiscovery.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest + +from mdfactory.performance.cluster import ( + ClusterInfo, + NodeType, + Partition, + _parse_accounts, + _parse_gres, + _parse_memory_mb, + _parse_qos, + _parse_sinfo, + discover_cluster, + select_partition, +) + +# --------------------------------------------------------------------------- +# Fixtures: realistic sinfo / sacctmgr output +# --------------------------------------------------------------------------- + +# 9-field format: Partition Node CPUs Mem GRES Features MaxTime DefTime State +SINFO_OUTPUT_MIXED = """\ +cpu* node001 128 512000 (null) epyc9555,avx512 3-00:00:00 1-00:00:00 idle +cpu* node002 128 512000 (null) epyc9555,avx512 3-00:00:00 1-00:00:00 mixed +cpu* node003 128 512000 (null) epyc9555,avx512 3-00:00:00 1-00:00:00 allocated +gpu node010 64 256000 gpu:a100:4 a100,nvlink 1-00:00:00 4:00:00 idle +gpu node011 64 256000 gpu:a100:4 a100,nvlink 1-00:00:00 4:00:00 idle +gpu node012 96 512000 gpu:h100:8 h100,nvlink 1-00:00:00 4:00:00 mixed +bigmem node020 256 2048000 (null) bigmem,epyc 7-00:00:00 1-00:00:00 idle +""" + +SINFO_OUTPUT_SINGLE_PARTITION = """\ +compute node001 64 128000 (null) (null) 2-00:00:00 1-00:00:00 idle +compute node002 64 128000 (null) (null) 2-00:00:00 1-00:00:00 idle +""" + +SINFO_OUTPUT_GPU_ONLY = """\ +gpu-short node001 32 64000 gpu:v100:2 v100 4:00:00 2:00:00 idle +gpu-short node002 32 64000 gpu:v100:2 v100 4:00:00 2:00:00 idle +gpu-long node003 64 128000 gpu:a100:4 a100 2-00:00:00 4:00:00 idle +""" + +SINFO_OUTPUT_NO_TYPE_GPU = """\ +gpu node001 64 256000 gpu:4 (null) 1-00:00:00 4:00:00 idle +""" + +# First node drained, rest healthy → partition should be "up" +SINFO_OUTPUT_MIXED_HEALTH = """\ +cpu node001 64 128000 (null) (null) 2-00:00:00 1-00:00:00 drained +cpu node002 64 128000 (null) (null) 2-00:00:00 1-00:00:00 idle +cpu node003 64 128000 (null) (null) 2-00:00:00 1-00:00:00 idle +""" + +# All nodes unhealthy → partition should report unhealthy state +SINFO_OUTPUT_ALL_DOWN = """\ +cpu node001 64 128000 (null) (null) 2-00:00:00 1-00:00:00 down +cpu node002 64 128000 (null) (null) 2-00:00:00 1-00:00:00 drained +""" + +# Legacy 8-field format (no %L default time) — backward compatibility +SINFO_OUTPUT_LEGACY_8FIELD = """\ +compute node001 64 128000 (null) (null) 2-00:00:00 idle +compute node002 64 128000 (null) (null) 2-00:00:00 idle +""" + +SACCTMGR_ACCOUNTS = """\ +myproject +shared-account +default-account +""" + +SACCTMGR_QOS = """\ +normal|| +high|1-00:00:00|cpu=128,mem=512G +gpu|12:00:00|cpu=64,gres/gpu=4 +""" + + +# --------------------------------------------------------------------------- +# Tests: GRES parsing +# --------------------------------------------------------------------------- + + +class TestParseGres: + """Test GPU GRES string parsing.""" + + def test_gpu_with_type_and_count(self): + assert _parse_gres("gpu:a100:4") == [(4, "a100")] + + def test_gpu_with_count_only(self): + assert _parse_gres("gpu:2") == [(2, None)] + + def test_gpu_with_type_only(self): + assert _parse_gres("gpu:h100") == [(1, "h100")] + + def test_null_gres(self): + assert _parse_gres("(null)") == [] + + def test_empty_string(self): + assert _parse_gres("") == [] + + def test_multi_gres_with_gpu(self): + assert _parse_gres("mps:shared,gpu:a100:4") == [(4, "a100")] + + def test_non_gpu_gres(self): + assert _parse_gres("mps:shared") == [] + + def test_multiple_gpu_types(self): + """Test multiple GPU entries (e.g., MIG slices).""" + result = _parse_gres("gpu:b200:7,gpu:1g.23gb:7") + assert len(result) == 2 + assert (7, "1g.23gb") in result + assert (7, "b200") in result + + def test_socket_binding_stripped(self): + """Test that socket binding suffixes are removed.""" + assert _parse_gres("gpu:l40s:4(S:0-1)") == [(4, "l40s")] + assert _parse_gres("gpu:b200:8(S:0-1),gpu:1g.23gb:7(S:1)") == [(8, "b200"), (7, "1g.23gb")] + + +# --------------------------------------------------------------------------- +# Tests: sinfo parsing +# --------------------------------------------------------------------------- + + +class TestParseSinfo: + """Test sinfo output parsing into Partition objects.""" + + def test_mixed_cluster(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + + # Should find 3 partitions + assert len(partitions) == 3 + names = [p.name for p in partitions] + assert "cpu" in names + assert "gpu" in names + assert "bigmem" in names + + def test_default_partition_marker(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + + cpu_part = next(p for p in partitions if p.name == "cpu") + assert cpu_part.is_default is True + + gpu_part = next(p for p in partitions if p.name == "gpu") + assert gpu_part.is_default is False + + def test_default_partition_sorted_first(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + assert partitions[0].name == "cpu" + assert partitions[0].is_default is True + + def test_cpu_partition_node_types(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + cpu_part = next(p for p in partitions if p.name == "cpu") + + # All 3 nodes have same spec → 1 unique node type + assert len(cpu_part.node_types) == 1 + nt = cpu_part.node_types[0] + assert nt.cpus == 128 + assert nt.memory_mb == 512000 + assert nt.gpu_specs == () # No GPUs + assert "epyc9555" in nt.features + assert nt.count == 3 # 3 nodes with this config + + def test_gpu_partition_multiple_node_types(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + gpu_part = next(p for p in partitions if p.name == "gpu") + + # 2 distinct node types: a100 (64 core) and h100 (96 core) + assert len(gpu_part.node_types) == 2 + + a100_node = next(n for n in gpu_part.node_types if (4, "a100") in n.gpu_specs) + assert a100_node.cpus == 64 + assert a100_node.gpu_specs == ((4, "a100"),) + assert a100_node.count == 2 # 2 a100 nodes + + h100_node = next(n for n in gpu_part.node_types if (8, "h100") in n.gpu_specs) + assert h100_node.cpus == 96 + assert h100_node.gpu_specs == ((8, "h100"),) + assert h100_node.count == 1 # 1 h100 node + + def test_total_node_count(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + + cpu_part = next(p for p in partitions if p.name == "cpu") + assert cpu_part.total_nodes == 3 + + gpu_part = next(p for p in partitions if p.name == "gpu") + assert gpu_part.total_nodes == 3 + + bigmem_part = next(p for p in partitions if p.name == "bigmem") + assert bigmem_part.total_nodes == 1 + + def test_time_limit_parsed(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + cpu_part = next(p for p in partitions if p.name == "cpu") + assert cpu_part.max_time == "3-00:00:00" + + def test_single_partition(self): + partitions = _parse_sinfo(SINFO_OUTPUT_SINGLE_PARTITION) + assert len(partitions) == 1 + assert partitions[0].name == "compute" + assert partitions[0].total_nodes == 2 + + def test_gpu_without_type(self): + partitions = _parse_sinfo(SINFO_OUTPUT_NO_TYPE_GPU) + assert len(partitions) == 1 + nt = partitions[0].node_types[0] + assert nt.gpu_specs == ((4, None),) # 4 GPUs, no type specified + + def test_default_time_parsed_separately(self): + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + cpu_part = next(p for p in partitions if p.name == "cpu") + assert cpu_part.max_time == "3-00:00:00" + assert cpu_part.default_time == "1-00:00:00" + + gpu_part = next(p for p in partitions if p.name == "gpu") + assert gpu_part.max_time == "1-00:00:00" + assert gpu_part.default_time == "4:00:00" + + def test_legacy_8field_format(self): + """Parser handles legacy 8-field sinfo output (no %L).""" + partitions = _parse_sinfo(SINFO_OUTPUT_LEGACY_8FIELD) + assert len(partitions) == 1 + assert partitions[0].name == "compute" + # default_time falls back to max_time + assert partitions[0].default_time == partitions[0].max_time + + def test_partition_state_up_when_any_node_healthy(self): + """Partition with mixed healthy/unhealthy nodes should be 'up'.""" + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED_HEALTH) + assert len(partitions) == 1 + assert partitions[0].state == "up" + + def test_partition_state_unhealthy_when_all_nodes_down(self): + """Partition with no healthy nodes reports unhealthy state.""" + partitions = _parse_sinfo(SINFO_OUTPUT_ALL_DOWN) + assert len(partitions) == 1 + assert partitions[0].state != "up" + assert partitions[0].state in ("down", "drained") + + def test_empty_output(self): + partitions = _parse_sinfo("") + assert partitions == [] + + def test_malformed_lines_skipped(self): + output = "this is not valid sinfo output\n" + SINFO_OUTPUT_SINGLE_PARTITION + partitions = _parse_sinfo(output) + # Should still parse valid lines + assert len(partitions) == 1 + + def test_memory_trailing_plus_stripped(self): + """SLURM reports '128000+' when memory varies across nodes in a partition.""" + assert _parse_memory_mb("128000+") == 128000 + + +# --------------------------------------------------------------------------- +# Tests: account / QOS parsing +# --------------------------------------------------------------------------- + + +class TestParseAccounts: + """Test sacctmgr account output parsing.""" + + def test_multiple_accounts(self): + accounts = _parse_accounts(SACCTMGR_ACCOUNTS) + assert accounts == ["default-account", "myproject", "shared-account"] + + def test_empty_output(self): + assert _parse_accounts("") == [] + + def test_whitespace_handling(self): + assert _parse_accounts(" acc1 \n acc2 \n") == ["acc1", "acc2"] + + def test_deduplication(self): + assert _parse_accounts("acc1\nacc1\nacc2") == ["acc1", "acc2"] + + +class TestParseQos: + """Test sacctmgr QOS output parsing.""" + + def test_multiple_qos(self): + qos = _parse_qos(SACCTMGR_QOS) + assert qos == ["gpu", "high", "normal"] + + def test_empty_output(self): + assert _parse_qos("") == [] + + def test_pipe_separated_format(self): + qos = _parse_qos("standard|2-00:00:00|cpu=256\n") + assert qos == ["standard"] + + +# --------------------------------------------------------------------------- +# Tests: discover_cluster integration (mocked subprocess) +# --------------------------------------------------------------------------- + + +class TestDiscoverCluster: + """Test discover_cluster with mocked subprocess calls.""" + + def setup_method(self): + """Clear LRU cache between tests.""" + discover_cluster.cache_clear() + + def test_returns_none_without_sinfo(self): + with patch("mdfactory.performance.cluster.shutil.which", return_value=None): + result = discover_cluster() + assert result is None + + def test_returns_cluster_info_with_sinfo(self): + """Test full integration when SLURM commands return data.""" + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + accounts = _parse_accounts(SACCTMGR_ACCOUNTS) + qos = _parse_qos(SACCTMGR_QOS) + + with ( + patch("mdfactory.performance.cluster.shutil.which", return_value="/usr/bin/sinfo"), + patch("mdfactory.performance.cluster._discover_partitions", return_value=partitions), + patch("mdfactory.performance.cluster._discover_accounts", return_value=accounts), + patch("mdfactory.performance.cluster._discover_qos", return_value=qos), + patch( + "mdfactory.performance.cluster._discover_default_account", return_value="myproject" + ), + ): + result = discover_cluster() + + assert result is not None + assert isinstance(result, ClusterInfo) + assert len(result.partitions) == 3 + assert len(result.accounts) == 3 + assert len(result.qos_policies) == 3 + assert result.default_account == "myproject" + + def test_default_account_falls_back_to_first(self): + """When default account query fails, fall back to first account.""" + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + accounts = _parse_accounts(SACCTMGR_ACCOUNTS) + qos = _parse_qos(SACCTMGR_QOS) + + with ( + patch("mdfactory.performance.cluster.shutil.which", return_value="/usr/bin/sinfo"), + patch("mdfactory.performance.cluster._discover_partitions", return_value=partitions), + patch("mdfactory.performance.cluster._discover_accounts", return_value=accounts), + patch("mdfactory.performance.cluster._discover_qos", return_value=qos), + patch("mdfactory.performance.cluster._discover_default_account", return_value=None), + ): + result = discover_cluster() + + assert result is not None + # Falls back to first alphabetical account + assert result.default_account == "default-account" + + def test_graceful_without_sacctmgr(self): + """When sacctmgr fails, still return partitions.""" + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + + with ( + patch("mdfactory.performance.cluster.shutil.which", return_value="/usr/bin/sinfo"), + patch("mdfactory.performance.cluster._discover_partitions", return_value=partitions), + patch("mdfactory.performance.cluster._discover_accounts", return_value=None), + patch("mdfactory.performance.cluster._discover_qos", return_value=None), + patch("mdfactory.performance.cluster._discover_default_account", return_value=None), + ): + result = discover_cluster() + + assert result is not None + assert len(result.partitions) == 3 + assert result.accounts == [] + assert result.qos_policies == [] + assert result.default_account is None + + def test_caching(self): + """Second call returns cached result without re-querying.""" + partitions = _parse_sinfo(SINFO_OUTPUT_SINGLE_PARTITION) + + with ( + patch("mdfactory.performance.cluster.shutil.which", return_value="/usr/bin/sinfo"), + patch( + "mdfactory.performance.cluster._discover_partitions", return_value=partitions + ) as mock_partitions, + patch("mdfactory.performance.cluster._discover_accounts", return_value=None), + patch("mdfactory.performance.cluster._discover_qos", return_value=None), + patch("mdfactory.performance.cluster._discover_default_account", return_value=None), + ): + result1 = discover_cluster() + result2 = discover_cluster() + + assert result1 is result2 + # _discover_partitions called only once due to caching + assert mock_partitions.call_count == 1 + + def test_returns_none_when_sinfo_fails(self): + """If sinfo exists but returns error, return None.""" + with ( + patch("mdfactory.performance.cluster.shutil.which", return_value="/usr/bin/sinfo"), + patch("mdfactory.performance.cluster._discover_partitions", return_value=None), + ): + result = discover_cluster() + assert result is None + + +# --------------------------------------------------------------------------- +# Tests: select_partition +# --------------------------------------------------------------------------- + + +class TestSelectPartition: + """Test heuristic partition selection.""" + + @pytest.fixture() + def cluster(self) -> ClusterInfo: + """Build a ClusterInfo from the mixed sinfo output.""" + partitions = _parse_sinfo(SINFO_OUTPUT_MIXED) + return ClusterInfo( + partitions=partitions, + accounts=["myproject"], + qos_policies=["normal"], + default_account="myproject", + ) + + def test_select_default_cpu_partition(self, cluster: ClusterInfo): + result = select_partition(cluster) + assert result is not None + assert result.name == "cpu" + + def test_select_gpu_partition(self, cluster: ClusterInfo): + result = select_partition(cluster, needs_gpu=True) + assert result is not None + assert result.name == "gpu" + + def test_select_with_high_cpu_requirement(self, cluster: ClusterInfo): + # Need 200+ CPUs → only bigmem (256) qualifies + result = select_partition(cluster, min_cpus=200) + assert result is not None + assert result.name == "bigmem" + + def test_select_with_high_memory_requirement(self, cluster: ClusterInfo): + # Need 1TB+ → only bigmem qualifies + result = select_partition(cluster, min_mem_gb=1500) + assert result is not None + assert result.name == "bigmem" + + def test_returns_none_when_impossible(self, cluster: ClusterInfo): + # Need 1000 CPUs — nobody has that + result = select_partition(cluster, min_cpus=1000) + assert result is None + + def test_returns_none_gpu_when_no_gpu_partition(self): + partitions = _parse_sinfo(SINFO_OUTPUT_SINGLE_PARTITION) + cluster = ClusterInfo(partitions=partitions) + result = select_partition(cluster, needs_gpu=True) + assert result is None + + def test_prefers_default_partition(self, cluster: ClusterInfo): + # Both cpu and bigmem meet min_cpus=1 — prefer cpu (default) + result = select_partition(cluster, min_cpus=1) + assert result is not None + assert result.name == "cpu" + + def test_gpu_with_min_cpus(self, cluster: ClusterInfo): + # Need GPU + 90 CPUs → only h100 node qualifies (96 cpus) + result = select_partition(cluster, needs_gpu=True, min_cpus=90) + assert result is not None + assert result.name == "gpu" + + def test_skips_down_partitions(self): + """Partitions where all nodes are down are not selectable.""" + partitions = _parse_sinfo(SINFO_OUTPUT_ALL_DOWN) + cluster = ClusterInfo(partitions=partitions) + result = select_partition(cluster) + assert result is None + + def test_node_count_tiebreak(self): + """When multiple partitions qualify, prefer the one with more nodes.""" + # SINFO_OUTPUT_GPU_ONLY: gpu-short (2 nodes) vs gpu-long (1 node), neither default. + # sort key (not is_default, -total_nodes, name) should pick gpu-short. + partitions = _parse_sinfo(SINFO_OUTPUT_GPU_ONLY) + cluster = ClusterInfo(partitions=partitions) + result = select_partition(cluster, needs_gpu=True) + assert result is not None + assert result.name == "gpu-short" + + +# --------------------------------------------------------------------------- +# Tests: dataclass properties +# --------------------------------------------------------------------------- + + +class TestDataclasses: + """Test dataclass construction and immutability.""" + + def test_node_type_frozen(self): + from pydantic import ValidationError + + nt = NodeType(cpus=64, memory_mb=256000, gpu_specs=((4, "a100"),), count=1) + with pytest.raises(ValidationError): + nt.cpus = 128 # type: ignore[misc] + + def test_partition_frozen(self): + from pydantic import ValidationError + + p = Partition(name="test", state="up", max_time="1-00:00:00", default_time="1:00:00") + with pytest.raises(ValidationError): + p.name = "other" # type: ignore[misc] + + def test_cluster_info_frozen(self): + from pydantic import ValidationError + + ci = ClusterInfo() + with pytest.raises(ValidationError): + ci.default_account = "hack" # type: ignore[misc] + + def test_node_type_defaults(self): + nt = NodeType(cpus=32, memory_mb=64000) + assert nt.gpu_specs == () # No GPUs by default + assert nt.features == () + assert nt.count == 1 # Default count + + def test_node_type_features_immutable(self): + nt = NodeType( + cpus=64, memory_mb=256000, gpu_specs=((4, "a100"),), features=("a100", "nvlink") + ) + assert nt.features == ("a100", "nvlink") + with pytest.raises(TypeError): + nt.features[0] = "other" # type: ignore[index] + + def test_cluster_info_defaults(self): + ci = ClusterInfo() + assert ci.partitions == [] + assert ci.accounts == [] + assert ci.qos_policies == [] + assert ci.default_account is None diff --git a/mdfactory/tests/test_orchestration_build.py b/mdfactory/tests/test_orchestration_build.py new file mode 100644 index 0000000..c718873 --- /dev/null +++ b/mdfactory/tests/test_orchestration_build.py @@ -0,0 +1,487 @@ +# ABOUTME: Tests for orchestration build dispatch and dry-run functions +# ABOUTME: Validates Parsl submission, dry-run output, and error handling +"""Tests for orchestration build dispatch.""" + +from unittest.mock import MagicMock, patch + +import pytest + +parsl = pytest.importorskip("parsl", reason="parsl not installed") + +from mdfactory.orchestration.config import ExecutorConfig # noqa: E402 + + +class FakeBuildInput: + """Fake BuildInput for testing isinstance checks.""" + + def __init__( + self, + hash="TEST", + simulation_type="mixedbox", + parametrization="cgenff", + engine="gromacs", + ): + self.hash = hash + self.simulation_type = simulation_type + self.parametrization = parametrization + self.engine = engine + + def model_dump(self): + """Return dict representation.""" + return { + "simulation_type": self.simulation_type, + "parametrization": self.parametrization, + "engine": self.engine, + } + + +def test_build_systems_dry_run(monkeypatch, tmp_path): + """build_systems with dry_run=True describes planned builds without loading Parsl.""" + import mdfactory.orchestration.build as build_mod + from mdfactory.orchestration.build import build_systems + + mock_model = FakeBuildInput( + hash="ABC123", simulation_type="bilayer", parametrization="smirnoff" + ) + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + + results = build_systems( + [mock_model], + ExecutorConfig(), + output_dir=tmp_path, + dry_run=True, + ) + + assert len(results) == 1 + assert results[0]["hash"] == "ABC123" + assert results[0]["simulation_type"] == "bilayer" + assert str(tmp_path / "ABC123") in results[0]["output_directory"] + + +def test_build_systems_dry_run_multiple(monkeypatch, tmp_path): + """build_systems with dry_run=True handles multiple inputs.""" + import mdfactory.orchestration.build as build_mod + from mdfactory.orchestration.build import build_systems + + models = [FakeBuildInput(hash=f"HASH{i}") for i in range(3)] + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + + results = build_systems(models, ExecutorConfig(), output_dir=tmp_path, dry_run=True) + assert len(results) == 3 + assert [r["hash"] for r in results] == ["HASH0", "HASH1", "HASH2"] + + +def test_build_systems_submits_correct_count(monkeypatch, tmp_path): + """build_systems submits one Parsl task per input.""" + import parsl + + mock_app_fn = MagicMock() + mock_future = MagicMock() + mock_future.result.return_value = {"hash": "H1", "status": "success", "directory": "/tmp/H1"} + mock_app_fn.return_value = mock_future + + import mdfactory.orchestration.build as build_mod + + monkeypatch.setattr(build_mod, "get_build_app", lambda: mock_app_fn) + monkeypatch.setattr(parsl, "load", MagicMock()) + monkeypatch.setattr(parsl, "clear", MagicMock()) + monkeypatch.setattr(parsl, "dfk", MagicMock(side_effect=RuntimeError("No DFK"))) + + mock_model = FakeBuildInput(hash="H1") + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + + cfg = ExecutorConfig() + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + results = build_mod.build_systems([mock_model], cfg, output_dir=tmp_path) + + assert mock_app_fn.call_count == 1 + assert len(results) == 1 + assert results[0]["status"] == "success" + + +def test_build_systems_handles_failed_future(monkeypatch, tmp_path): + """build_systems captures exceptions from failed futures.""" + import parsl + + mock_app_fn = MagicMock() + mock_future = MagicMock() + mock_future.result.side_effect = RuntimeError("CUDA OOM") + mock_app_fn.return_value = mock_future + + import mdfactory.orchestration.build as build_mod + + monkeypatch.setattr(build_mod, "get_build_app", lambda: mock_app_fn) + monkeypatch.setattr(parsl, "load", MagicMock()) + monkeypatch.setattr(parsl, "clear", MagicMock()) + monkeypatch.setattr(parsl, "dfk", MagicMock(side_effect=RuntimeError("No DFK"))) + + mock_model = FakeBuildInput(hash="FAIL1", simulation_type="bilayer") + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + cfg = ExecutorConfig() + results = build_mod.build_systems([mock_model], cfg, output_dir=tmp_path) + + assert len(results) == 1 + assert results[0]["status"] == "failed" + assert "CUDA OOM" in results[0]["error"] + # Finding 10: failure metadata is surfaced for future retry logic. + assert results[0]["failure_type"] == "RuntimeError" + assert results[0]["error_detail"] == "CUDA OOM" + + +def test_build_systems_no_wait(monkeypatch, tmp_path): + """build_systems with wait=False returns futures directly.""" + import parsl + + mock_app_fn = MagicMock() + mock_future = MagicMock() + mock_app_fn.return_value = mock_future + + import mdfactory.orchestration.build as build_mod + + monkeypatch.setattr(build_mod, "get_build_app", lambda: mock_app_fn) + monkeypatch.setattr(parsl, "load", MagicMock()) + + mock_model = FakeBuildInput(hash="NW1") + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + cfg = ExecutorConfig() + futures = build_mod.build_systems([mock_model], cfg, output_dir=tmp_path, wait=False) + + assert futures == [mock_future] + + +def test_build_systems_dry_run_invalid_input_type(tmp_path): + """build_systems with dry_run=True raises TypeError for invalid input.""" + from mdfactory.orchestration.build import build_systems + + with pytest.raises(TypeError, match="Expected BuildInput or dict"): + build_systems([42], ExecutorConfig(), output_dir=tmp_path, dry_run=True) + + +# --- Finding 5: Tests for _build_system_impl --- + + +def test_build_system_impl_strips_internal_keys_and_chdirs(monkeypatch, tmp_path): + """_build_system_impl strips _-prefixed keys and chdirs to build_dir.""" + from mdfactory.orchestration.apps import _build_system_impl + + build_dir = tmp_path / "OUT" + captured_model = {} + + def mock_run_build(model): + import os + + captured_model["hash"] = model.hash + captured_model["cwd"] = os.getcwd() + + monkeypatch.setattr("mdfactory.workflows.run_build_from_dict", mock_run_build) + + # Provide minimal valid BuildInput fields plus internal keys + input_dict = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + "_build_dir": str(build_dir), + "_internal_flag": "should_be_stripped", + } + result = _build_system_impl(input_dict) + + assert result["status"] == "success" + assert result["directory"] == str(build_dir.resolve()) + assert build_dir.exists() + # Verify we chdir'd to build_dir during execution + assert captured_model["cwd"] == str(build_dir) + + +def test_build_system_impl_restores_cwd_on_failure(monkeypatch, tmp_path): + """_build_system_impl restores original cwd even if build fails.""" + import os + + from mdfactory.orchestration.apps import _build_system_impl + + build_dir = tmp_path / "FAIL" + original_cwd = os.getcwd() + + def mock_run_build_fail(model): + raise RuntimeError("Build exploded") + + monkeypatch.setattr("mdfactory.workflows.run_build_from_dict", mock_run_build_fail) + + input_dict = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + "_build_dir": str(build_dir), + } + with pytest.raises(RuntimeError, match="Build exploded"): + _build_system_impl(input_dict) + + # cwd should be restored + assert os.getcwd() == original_cwd + + +# --- Finding 9: Tests for SLURM cleanup functions --- + + +def test_shutdown_parsl_calls_clear_and_scancel(monkeypatch): + """_shutdown_parsl calls parsl.clear() and scancels SLURM jobs.""" + import parsl as parsl_mod + + from mdfactory.orchestration.build import _shutdown_parsl + + mock_dfk = MagicMock() + mock_executor = MagicMock() + mock_executor.provider.resources = {"block-1": {"remote_job_id": "12345"}} + mock_dfk.executors = {"htex": mock_executor} + + monkeypatch.setattr(parsl_mod, "dfk", MagicMock(return_value=mock_dfk)) + monkeypatch.setattr(parsl_mod, "clear", MagicMock()) + + with patch("subprocess.run") as mock_subprocess: + mock_subprocess.return_value = MagicMock(returncode=0) + _shutdown_parsl() + + parsl_mod.clear.assert_called_once() + mock_subprocess.assert_called_once() + assert "12345" in mock_subprocess.call_args[0][0] + + +def test_shutdown_parsl_attempts_scancel_even_if_clear_fails(monkeypatch): + """_shutdown_parsl runs scancel even when parsl.clear() raises.""" + import parsl as parsl_mod + + from mdfactory.orchestration.build import _shutdown_parsl + + mock_dfk = MagicMock() + mock_executor = MagicMock() + mock_executor.provider.resources = {"block-1": {"remote_job_id": "99999"}} + mock_dfk.executors = {"htex": mock_executor} + + monkeypatch.setattr(parsl_mod, "dfk", MagicMock(return_value=mock_dfk)) + monkeypatch.setattr(parsl_mod, "clear", MagicMock(side_effect=RuntimeError("timeout"))) + + with patch("subprocess.run") as mock_subprocess: + mock_subprocess.return_value = MagicMock(returncode=0) + _shutdown_parsl() + + # scancel should still be called despite clear() failing + mock_subprocess.assert_called_once() + assert "99999" in mock_subprocess.call_args[0][0] + + +def test_get_slurm_job_ids_handles_missing_resources(monkeypatch): + """_get_slurm_job_ids handles executors without resources attribute.""" + from mdfactory.orchestration.build import _get_slurm_job_ids + + mock_dfk = MagicMock() + mock_executor = MagicMock(spec=[]) # no attributes + mock_dfk.executors = {"htex": mock_executor} + + result = _get_slurm_job_ids(mock_dfk) + assert result == [] + + +def test_keyboard_interrupt_triggers_shutdown(monkeypatch, tmp_path): + """KeyboardInterrupt during _wait_with_progress triggers _shutdown_parsl.""" + import parsl as parsl_mod + + import mdfactory.orchestration.build as build_mod + + mock_app_fn = MagicMock() + # Future that raises KeyboardInterrupt when polled + mock_future = MagicMock() + mock_future.done.side_effect = KeyboardInterrupt() + mock_app_fn.return_value = mock_future + + monkeypatch.setattr(build_mod, "get_build_app", lambda: mock_app_fn) + monkeypatch.setattr(parsl_mod, "load", MagicMock()) + monkeypatch.setattr(parsl_mod, "clear", MagicMock()) + monkeypatch.setattr(parsl_mod, "dfk", MagicMock(side_effect=RuntimeError("No DFK"))) + + mock_model = FakeBuildInput(hash="INT1") + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + cfg = ExecutorConfig() + with pytest.raises(KeyboardInterrupt): + build_mod.build_systems([mock_model], cfg, output_dir=tmp_path) + + # parsl.clear should have been called via _shutdown_parsl in the finally block + assert parsl_mod.clear.called + + +# --- Finding 10: Tests for dict input path --- + + +def test_build_systems_dry_run_with_dict_input(monkeypatch, tmp_path): + """build_systems with dry_run=True handles dict input correctly.""" + from mdfactory.orchestration.build import build_systems + + input_dict = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + } + + results = build_systems([input_dict], ExecutorConfig(), output_dir=tmp_path, dry_run=True) + + assert len(results) == 1 + assert results[0]["simulation_type"] == "mixedbox" + assert results[0]["parametrization"] == "cgenff" + assert results[0]["hash"] # hash should be computed + + +def test_build_systems_with_dict_input(monkeypatch, tmp_path): + """build_systems handles dict input and injects _build_dir.""" + import parsl as parsl_mod + + import mdfactory.orchestration.build as build_mod + + captured_args = [] + mock_app_fn = MagicMock() + mock_future = MagicMock() + mock_future.result.return_value = {"hash": "X", "status": "success", "directory": "/tmp/X"} + + def capture_app_call(input_dict): + captured_args.append(input_dict) + return mock_future + + mock_app_fn.side_effect = capture_app_call + + monkeypatch.setattr(build_mod, "get_build_app", lambda: mock_app_fn) + monkeypatch.setattr(parsl_mod, "load", MagicMock()) + monkeypatch.setattr(parsl_mod, "clear", MagicMock()) + monkeypatch.setattr(parsl_mod, "dfk", MagicMock(side_effect=RuntimeError("No DFK"))) + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + input_dict = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + } + + cfg = ExecutorConfig() + results = build_mod.build_systems([input_dict], cfg, output_dir=tmp_path) + + assert len(captured_args) == 1 + assert "_build_dir" in captured_args[0] + assert results[0]["status"] == "success" + + +# --- Finding 9: Multi-iteration polling test --- + + +def test_wait_with_progress_multi_iteration(monkeypatch): + """_wait_with_progress exercises multi-iteration polling when future is not immediately done.""" + from mdfactory.orchestration.build import _wait_with_progress + + mock_future = MagicMock() + mock_future.done.side_effect = [False, False, True] + mock_future.result.return_value = {"hash": "POLL1", "status": "success", "directory": "/tmp"} + mock_future.task_status.return_value = "running" + + results = _wait_with_progress([mock_future], hashes=["POLL1"], poll_interval=0.01) + + assert len(results) == 1 + assert results[0]["status"] == "success" + # done() should have been called at least twice (False then True) + assert mock_future.done.call_count >= 2 + + +# --- Finding 10: Assert non-cleanup in no_wait --- + + +def test_build_systems_no_wait_does_not_clear(monkeypatch, tmp_path): + """build_systems with wait=False does NOT call parsl.clear().""" + import parsl + + mock_app_fn = MagicMock() + mock_future = MagicMock() + mock_app_fn.return_value = mock_future + + import mdfactory.orchestration.build as build_mod + + monkeypatch.setattr(build_mod, "get_build_app", lambda: mock_app_fn) + monkeypatch.setattr(parsl, "load", MagicMock()) + mock_clear = MagicMock() + monkeypatch.setattr(parsl, "clear", mock_clear) + + mock_model = FakeBuildInput(hash="NW2") + monkeypatch.setattr(build_mod, "BuildInput", FakeBuildInput) + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + cfg = ExecutorConfig() + futures = build_mod.build_systems([mock_model], cfg, output_dir=tmp_path, wait=False) + + assert futures == [mock_future] + mock_clear.assert_not_called() + + +# --- Finding 10: failure description helper --- + + +def test_describe_failure_plain_exception(): + """_describe_failure returns the exception type name and message.""" + from mdfactory.orchestration.build import _describe_failure + + failure_type, detail = _describe_failure(RuntimeError("boom")) + assert failure_type == "RuntimeError" + assert detail == "boom" + + +def test_describe_failure_unwraps_legacy_e_value(): + """_describe_failure unwraps a legacy Parsl wrapper exposing .e_value.""" + from mdfactory.orchestration.build import _describe_failure + + class FakeAppFailure(Exception): + """Mimic an older Parsl wrapper carrying the underlying error.""" + + def __init__(self, e_value): + super().__init__("wrapped") + self.e_value = e_value + + underlying = ValueError("real GROMACS crash") + failure_type, detail = _describe_failure(FakeAppFailure(underlying)) + assert failure_type == "ValueError" + assert detail == "real GROMACS crash" + + +def test_describe_failure_unwraps_dependency_error_cause(): + """_describe_failure unwraps DependencyError via __cause__ to show root cause.""" + from mdfactory.orchestration.build import _describe_failure + + root = ValueError("grompp fatal error: atom types not found") + wrapper = RuntimeError("Dependency failure for task 5. The representative cause is via task 0") + wrapper.__cause__ = root + + failure_type, detail = _describe_failure(wrapper) + assert failure_type == "ValueError" + assert "grompp fatal error" in detail + + +# --- Finding 12: completeness guard --- + + +def test_collect_results_returns_complete_list(): + """_collect_results returns all results when every slot is captured.""" + from mdfactory.orchestration.build import _collect_results + + results = [{"hash": "A"}, {"hash": "B"}] + assert _collect_results(results, ["A", "B"]) == results + + +def test_collect_results_raises_on_uncaptured_slot(): + """_collect_results raises rather than silently dropping a None slot.""" + from mdfactory.orchestration.build import _collect_results + + results = [{"hash": "A"}, None] + with pytest.raises(RuntimeError, match="never captured"): + _collect_results(results, ["AAAA", "BBBB"]) diff --git a/mdfactory/tests/test_orchestration_cli.py b/mdfactory/tests/test_orchestration_cli.py new file mode 100644 index 0000000..80a65b6 --- /dev/null +++ b/mdfactory/tests/test_orchestration_cli.py @@ -0,0 +1,301 @@ +# ABOUTME: Integration tests for the CLI build command orchestration paths +# ABOUTME: Tests CSV/YAML/summary-YAML dispatch, dry-run, and error handling +"""Integration tests for CLI build command with orchestration.""" + +from unittest.mock import patch + +import pytest +import yaml + +parsl = pytest.importorskip("parsl", reason="parsl not installed") + + +@pytest.fixture() +def sample_csv(tmp_path): + """Create a minimal CSV input file for testing.""" + csv_content = ( + "simulation_type,system.species.SOL.smiles,system.species.SOL.count,parametrization\n" + "mixedbox,O,100,cgenff\n" + ) + csv_path = tmp_path / "input.csv" + csv_path.write_text(csv_content) + return csv_path + + +@pytest.fixture() +def sample_yaml(tmp_path): + """Create a minimal single-build YAML input file.""" + data = { + "simulation_type": "mixedbox", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + "parametrization": "cgenff", + "engine": "gromacs", + } + yaml_path = tmp_path / "build.yaml" + with open(yaml_path, "w") as f: + yaml.safe_dump(data, f) + return yaml_path + + +@pytest.fixture() +def sample_config(tmp_path): + """Create a minimal executor config YAML.""" + data = {"provider": "local", "max_workers_per_node": 1} + cfg_path = tmp_path / "config.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(data, f) + return cfg_path + + +class TestBuildCommandCSV: + """Tests for CSV input dispatch path.""" + + def test_csv_dry_run(self, sample_csv, tmp_path, monkeypatch): + """CSV input with --dry-run calls build_systems with dry_run=True.""" + from mdfactory.cli import _build_from_csv + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [{"hash": "X", "simulation_type": "mixedbox"}] + _build_from_csv(sample_csv, tmp_path, dry_run=True) + + mock_build.assert_called_once() + assert mock_build.call_args[1]["dry_run"] is True + + def test_csv_dry_run_no_filesystem_side_effects(self, sample_csv, tmp_path, monkeypatch): + """CSV dry-run does not create directories or summary YAML.""" + from mdfactory.cli import _build_from_csv + + output_dir = tmp_path / "output" + output_dir.mkdir() + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [] + _build_from_csv(sample_csv, output_dir, dry_run=True) + + # No summary YAML or hash directories should be created in output + assert not (output_dir / "input.yaml").exists() + assert len(list(output_dir.iterdir())) == 0 + + def test_csv_sequential_builds_locally(self, sample_csv, tmp_path, monkeypatch): + """CSV input without --config builds sequentially.""" + from mdfactory.cli import _build_from_csv + + with patch("mdfactory.cli.run_build_from_dict") as mock_build: + _build_from_csv(sample_csv, tmp_path, config=None, dry_run=False) + + mock_build.assert_called_once() + + def test_csv_generates_summary_yaml(self, sample_csv, tmp_path, monkeypatch): + """CSV build generates a summary YAML matching prepare-build format.""" + from mdfactory.cli import _build_from_csv + + with patch("mdfactory.cli.run_build_from_dict"): + _build_from_csv(sample_csv, tmp_path, config=None, dry_run=False) + + summary_path = tmp_path / "input.yaml" + assert summary_path.exists() + with open(summary_path) as f: + summary = yaml.safe_load(f) + assert summary["n_systems"] == 1 + assert str(sample_csv) == summary["input"] + assert len(summary["hash"]) == 1 + assert len(summary["system_directory"]) == 1 + assert "date" in summary + assert "simulation_type" in summary + + def test_csv_with_config_uses_parsl(self, sample_csv, sample_config, tmp_path, monkeypatch): + """CSV input with --config dispatches via build_systems.""" + from mdfactory.cli import _build_from_csv + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [{"hash": "X", "status": "success"}] + _build_from_csv(sample_csv, tmp_path, config=sample_config, dry_run=False) + + mock_build.assert_called_once() + + +class TestBuildCommandYAML: + """Tests for single-YAML input dispatch path.""" + + def test_yaml_sequential_builds_into_hash_dir(self, sample_yaml, tmp_path, monkeypatch): + """Single YAML without --config builds into output/{hash}/.""" + from mdfactory.cli import _build_from_yaml + + with patch("mdfactory.cli.run_build_from_file") as mock_build: + _build_from_yaml(sample_yaml, tmp_path) + + mock_build.assert_called_once() + # Verify the working directory was a hash-based subdirectory + call_args = mock_build.call_args + # run_build_from_file is called with the input path + assert call_args[0][0] == sample_yaml + + def test_yaml_dry_run(self, sample_yaml, tmp_path, monkeypatch): + """Single YAML with --dry-run calls build_systems with dry_run=True.""" + from mdfactory.cli import _build_from_yaml + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [{"hash": "X", "simulation_type": "mixedbox"}] + _build_from_yaml(sample_yaml, tmp_path, dry_run=True) + + mock_build.assert_called_once() + assert mock_build.call_args[1]["dry_run"] is True + + def test_yaml_with_config_uses_parsl(self, sample_yaml, sample_config, tmp_path, monkeypatch): + """Single YAML with --config dispatches via build_systems.""" + from mdfactory.cli import _build_from_yaml + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [{"hash": "X", "status": "success"}] + _build_from_yaml(sample_yaml, tmp_path, config=sample_config, dry_run=False) + + mock_build.assert_called_once() + + +class TestBuildCommandSummaryYAML: + """Tests for summary YAML (prepare-build output) dispatch path.""" + + def test_summary_yaml_empty_dirs_exits(self, tmp_path): + """Summary YAML with no directories causes exit.""" + from mdfactory.cli import _build_from_summary_yaml + + data = {"system_directory": [], "hash": []} + with pytest.raises(SystemExit): + _build_from_summary_yaml(data, tmp_path) + + def test_summary_yaml_missing_build_file_exits(self, tmp_path): + """Summary YAML referencing non-existent build file exits.""" + from mdfactory.cli import _build_from_summary_yaml + + data = {"system_directory": [str(tmp_path / "nonexistent")], "hash": ["ABC123"]} + with pytest.raises(SystemExit): + _build_from_summary_yaml(data, tmp_path) + + def test_summary_yaml_mismatched_lengths_exits(self, tmp_path): + """Summary YAML with mismatched list lengths causes exit.""" + from mdfactory.cli import _build_from_summary_yaml + + data = {"system_directory": [str(tmp_path), str(tmp_path)], "hash": ["A"]} + with pytest.raises(SystemExit): + _build_from_summary_yaml(data, tmp_path) + + def test_summary_yaml_dry_run(self, tmp_path): + """Summary YAML with --dry-run calls build_systems with dry_run=True.""" + from mdfactory.cli import _build_from_summary_yaml + + hash_val = "TESTHASH123" + build_dir = tmp_path / hash_val + build_dir.mkdir() + yaml_data = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + } + (build_dir / f"{hash_val}.yaml").write_text(yaml.safe_dump(yaml_data)) + data = {"system_directory": [str(build_dir)], "hash": [hash_val]} + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [{"hash": hash_val}] + _build_from_summary_yaml(data, tmp_path, dry_run=True) + + mock_build.assert_called_once() + assert mock_build.call_args[1]["dry_run"] is True + + def test_summary_yaml_sequential(self, tmp_path): + """Summary YAML without --config builds sequentially.""" + from mdfactory.cli import _build_from_summary_yaml + + hash_val = "SEQHASH456" + build_dir = tmp_path / hash_val + build_dir.mkdir() + yaml_data = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + } + (build_dir / f"{hash_val}.yaml").write_text(yaml.safe_dump(yaml_data)) + data = {"system_directory": [str(build_dir)], "hash": [hash_val]} + + with patch("mdfactory.cli.run_build_from_dict") as mock_build: + _build_from_summary_yaml(data, tmp_path, config=None, dry_run=False) + + mock_build.assert_called_once() + + def test_summary_yaml_parallel(self, tmp_path, sample_config): + """Summary YAML with --config dispatches via build_systems.""" + from mdfactory.cli import _build_from_summary_yaml + + hash_val = "PARHASH789" + build_dir = tmp_path / hash_val + build_dir.mkdir() + yaml_data = { + "simulation_type": "mixedbox", + "parametrization": "cgenff", + "engine": "gromacs", + "system": {"species": [{"smiles": "O", "count": 100, "resname": "SOL"}]}, + } + (build_dir / f"{hash_val}.yaml").write_text(yaml.safe_dump(yaml_data)) + data = {"system_directory": [str(build_dir)], "hash": [hash_val]} + + with patch("mdfactory.orchestration.build_systems") as mock_build: + mock_build.return_value = [{"hash": hash_val, "status": "success"}] + _build_from_summary_yaml(data, tmp_path, config=sample_config, dry_run=False) + + mock_build.assert_called_once() + + +class TestBuildCommandErrors: + """Tests for error handling in build command.""" + + def test_unsupported_extension_exits(self, tmp_path): + """Unsupported file extension causes sys.exit.""" + from mdfactory.cli import build_system + + txt_file = tmp_path / "input.txt" + txt_file.write_text("hello") + with pytest.raises(SystemExit): + build_system(txt_file, output=tmp_path) + + def test_malformed_csv_exits(self, tmp_path): + """CSV with missing required columns causes sys.exit.""" + from mdfactory.cli import _build_from_csv + + # Missing simulation_type column + csv_content = "system.species.SOL.smiles,system.species.SOL.count\nO,100\n" + csv_path = tmp_path / "bad.csv" + csv_path.write_text(csv_content) + with pytest.raises(SystemExit): + _build_from_csv(csv_path, tmp_path, config=None, dry_run=False) + + def test_empty_yaml_exits(self, tmp_path): + """Empty YAML file causes sys.exit with clear message.""" + from mdfactory.cli import _build_from_yaml + + empty_yaml = tmp_path / "empty.yaml" + empty_yaml.write_text("") + with pytest.raises(SystemExit): + _build_from_yaml(empty_yaml, tmp_path) + + +class TestBuildCommandRouting: + """Tests for build_system() top-level routing logic.""" + + def test_csv_routes_to_csv_handler(self, sample_csv, tmp_path): + """build_system() with .csv file routes to CSV handler.""" + from mdfactory.cli import build_system + + with patch("mdfactory.cli.run_build_from_dict") as mock_build: + build_system(sample_csv, output=tmp_path) + + mock_build.assert_called_once() + + def test_yaml_routes_to_yaml_handler(self, sample_yaml, tmp_path): + """build_system() with .yaml file routes to YAML handler.""" + from mdfactory.cli import build_system + + with patch("mdfactory.cli.run_build_from_file") as mock_build: + build_system(sample_yaml, output=tmp_path) + + mock_build.assert_called_once() diff --git a/mdfactory/tests/test_orchestration_cli_simulate.py b/mdfactory/tests/test_orchestration_cli_simulate.py new file mode 100644 index 0000000..f1197ec --- /dev/null +++ b/mdfactory/tests/test_orchestration_cli_simulate.py @@ -0,0 +1,332 @@ +# ABOUTME: Unit tests for the CLI simulate command and its helper functions +# ABOUTME: Covers path resolution, hash filtering, and result reporting +"""Unit tests for CLI simulate command helpers (Finding 8).""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from mdfactory.cli import ( + _discover_sim_dirs, + _filter_sim_paths_by_hash_prefix, + _report_simulation_results, + _resolve_sim_paths_for_simulate, +) + +# --------------------------------------------------------------------------- +# _discover_sim_dirs +# --------------------------------------------------------------------------- + + +def test_discover_sim_dirs_direct(tmp_path): + """Root containing system.pdb is returned as a single-element list.""" + (tmp_path / "system.pdb").touch() + result = _discover_sim_dirs(tmp_path) + assert result == [tmp_path] + + +def test_discover_sim_dirs_children(tmp_path): + """Children that contain system.pdb are returned as sorted list.""" + for name in ["abc123", "def456"]: + d = tmp_path / name + d.mkdir() + (d / "system.pdb").touch() + result = _discover_sim_dirs(tmp_path) + assert len(result) == 2 + assert result == sorted([tmp_path / "abc123", tmp_path / "def456"]) + + +def test_discover_sim_dirs_ignores_non_sim_children(tmp_path): + """Children without system.pdb are silently ignored.""" + good = tmp_path / "abc123" + good.mkdir() + (good / "system.pdb").touch() + + bad = tmp_path / "not_a_sim" + bad.mkdir() + # No system.pdb → should not appear + + result = _discover_sim_dirs(tmp_path) + assert result == [good] + + +def test_discover_sim_dirs_no_sims_raises(tmp_path): + """No simulation-ready directories → ValueError with helpful message.""" + with pytest.raises(ValueError, match="No simulation directories"): + _discover_sim_dirs(tmp_path) + + +# --------------------------------------------------------------------------- +# _filter_sim_paths_by_hash_prefix +# --------------------------------------------------------------------------- + + +def test_filter_sim_paths_exact_prefix_match(tmp_path): + """Exact hash prefix matches the correct directory.""" + abc = tmp_path / "ABC123" + def_ = tmp_path / "DEF456" + result = _filter_sim_paths_by_hash_prefix([abc, def_], ["ABC123"]) + assert result == [abc] + + +def test_filter_sim_paths_case_insensitive(tmp_path): + """Hash prefix matching is case-insensitive.""" + abc = tmp_path / "ABC123" + def_ = tmp_path / "DEF456" + result = _filter_sim_paths_by_hash_prefix([abc, def_], ["abc"]) + assert result == [abc] + + +def test_filter_sim_paths_multiple_prefixes(tmp_path): + """Multiple prefixes can match multiple directories.""" + abc = tmp_path / "ABC123" + def_ = tmp_path / "DEF456" + result = _filter_sim_paths_by_hash_prefix([abc, def_], ["abc", "def"]) + assert len(result) == 2 + + +def test_filter_sim_paths_no_match_raises(tmp_path): + """No matching prefix → ValueError listing available directories.""" + paths = [tmp_path / "ABC123"] + with pytest.raises(ValueError, match="No simulation directories match"): + _filter_sim_paths_by_hash_prefix(paths, ["xyz"]) + + +def test_filter_sim_paths_returns_sorted(tmp_path): + """Result is sorted by path name.""" + b = tmp_path / "BBB" + a = tmp_path / "AAA" + result = _filter_sim_paths_by_hash_prefix([b, a], ["A", "B"]) + assert result == [a, b] + + +# --------------------------------------------------------------------------- +# _resolve_sim_paths_for_simulate +# --------------------------------------------------------------------------- + + +def test_resolve_sim_paths_directory_delegates_to_discover(tmp_path): + """A directory source delegates to _discover_sim_dirs.""" + (tmp_path / "system.pdb").touch() + result = _resolve_sim_paths_for_simulate(tmp_path) + assert result == [tmp_path] + + +def test_resolve_sim_paths_yaml_source(tmp_path): + """A YAML source reads 'system_directory' entries.""" + import yaml + + dirs = [str(tmp_path / "sim1"), str(tmp_path / "sim2")] + summary = {"system_directory": dirs} + yaml_path = tmp_path / "summary.yaml" + yaml_path.write_text(yaml.dump(summary)) + + result = _resolve_sim_paths_for_simulate(yaml_path) + assert result == [Path(d) for d in dirs] + + +def test_resolve_sim_paths_invalid_source_raises(tmp_path): + """Non-directory, non-YAML source raises ValueError.""" + txt_file = tmp_path / "not_valid.txt" + txt_file.touch() + with pytest.raises(ValueError, match="Invalid source"): + _resolve_sim_paths_for_simulate(txt_file) + + +# --------------------------------------------------------------------------- +# _report_simulation_results — all status branches (Findings 8, 17) +# --------------------------------------------------------------------------- + + +def test_report_simulation_results_success_only(capsys): + """All success → correct succeeded count, no errors reported.""" + results = [ + {"hash": "a", "status": "success"}, + {"hash": "b", "status": "success"}, + ] + _report_simulation_results(results) + # Just assert it doesn't raise; logger output not captured by capsys + + +def test_report_simulation_results_counts_all_statuses(): + """succeeded + failed + skipped are all counted separately.""" + results = [ + {"hash": "a", "status": "success"}, + {"hash": "b", "status": "failed", "error": "mdrun died"}, + {"hash": "c", "status": "skipped"}, + ] + # Patch the logger to capture what was logged + with patch("mdfactory.cli.logger") as mock_logger: + _report_simulation_results(results) + + # At least one info call should mention all three counts + info_calls = [str(c) for c in mock_logger.info.call_args_list] + combined = " ".join(info_calls) + assert "1" in combined # 1 succeeded / 1 failed / 1 skipped + assert "skipped" in combined.lower() + + +def test_report_simulation_results_failed_details_logged(): + """Failed results include hash and error message in the log output.""" + results = [{"hash": "xyz789", "status": "failed", "error": "segfault in mdrun"}] + with patch("mdfactory.cli.logger") as mock_logger: + _report_simulation_results(results) + + error_calls = [str(c) for c in mock_logger.error.call_args_list] + combined = " ".join(error_calls) + assert "xyz789" in combined + assert "segfault" in combined + + +def test_report_simulation_results_skipped_not_in_error_log(): + """Skipped results do not generate error-level log entries.""" + results = [{"hash": "skp", "status": "skipped", "reason": "build incomplete"}] + with patch("mdfactory.cli.logger") as mock_logger: + _report_simulation_results(results) + + # No error calls for skipped entries + mock_logger.error.assert_not_called() + + +# --------------------------------------------------------------------------- +# simulate_systems — end-to-end with mocked run_simulations (Finding 8) +# --------------------------------------------------------------------------- + + +_SUCCESS_RESULTS = [{"hash": "abc", "status": "success"}] + + +@patch("mdfactory.orchestration.run_simulations", return_value=_SUCCESS_RESULTS) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_calls_run_simulations( + mock_slurm, mock_config, mock_run, tmp_path, capsys +): + """simulate_systems resolves paths and delegates to run_simulations.""" + from mdfactory.cli import simulate_systems + + # Prepare a minimal simulation directory + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + simulate_systems(source=tmp_path) + + mock_run.assert_called_once() + call_args = mock_run.call_args + sim_paths = call_args[0][0] # first positional arg + assert any(p.name == "abc123" for p in sim_paths) + + +@patch( + "mdfactory.orchestration.run_simulations", + return_value=[{"hash": "abc", "status": "failed", "error": "boom"}], +) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_reports_failed_results(mock_slurm, mock_config, mock_run, tmp_path): + """simulate_systems passes results with status=failed to _report_simulation_results.""" + from mdfactory.cli import simulate_systems + + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + with patch("mdfactory.cli._report_simulation_results") as mock_report: + simulate_systems(source=tmp_path) + mock_report.assert_called_once() + results_arg = mock_report.call_args[0][0] + failed = [r for r in results_arg if r.get("status") == "failed"] + assert len(failed) == 1 + + +@patch("mdfactory.orchestration.run_simulations", return_value=_SUCCESS_RESULTS) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_forwards_max_rescue(mock_slurm, mock_config, mock_run, tmp_path): + """--max-rescue value is forwarded from CLI to run_simulations.""" + from mdfactory.cli import simulate_systems + + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + simulate_systems(source=tmp_path, max_rescue=2) + + mock_run.assert_called_once() + _, kwargs = mock_run.call_args + assert kwargs["max_rescue"] == 2 + + +@patch("mdfactory.orchestration.run_simulations", return_value=_SUCCESS_RESULTS) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_max_rescue_default_is_3(mock_slurm, mock_config, mock_run, tmp_path): + """--max-rescue defaults to 3 when not specified.""" + from mdfactory.cli import simulate_systems + + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + simulate_systems(source=tmp_path) + + mock_run.assert_called_once() + _, kwargs = mock_run.call_args + assert kwargs["max_rescue"] == 3 + + +@patch("mdfactory.orchestration.run_simulations", return_value=_SUCCESS_RESULTS) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_forwards_clean_flag(mock_slurm, mock_config, mock_run, tmp_path): + """--clean is forwarded from CLI to run_simulations.""" + from mdfactory.cli import simulate_systems + + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + simulate_systems(source=tmp_path, clean=True) + + mock_run.assert_called_once() + _, kwargs = mock_run.call_args + assert kwargs["clean"] is True + + +@patch("mdfactory.orchestration.run_simulations", return_value=_SUCCESS_RESULTS) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_clean_default_is_false(mock_slurm, mock_config, mock_run, tmp_path): + """--clean defaults to False when not specified.""" + from mdfactory.cli import simulate_systems + + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + simulate_systems(source=tmp_path) + + mock_run.assert_called_once() + _, kwargs = mock_run.call_args + assert kwargs["clean"] is False + + +@patch("mdfactory.orchestration.run_simulations", return_value=[]) +@patch("mdfactory.cli._load_executor_config", return_value=MagicMock(provider="local")) +@patch("mdfactory.cli._resolve_slurm_flag", return_value=None) +def test_simulate_systems_clean_with_dry_run(mock_slurm, mock_config, mock_run, tmp_path): + """--clean combined with --dry-run passes both flags through.""" + from mdfactory.cli import simulate_systems + + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + (sim_dir / "system.pdb").touch() + + simulate_systems(source=tmp_path, clean=True, dry_run=True) + + mock_run.assert_called_once() + _, kwargs = mock_run.call_args + assert kwargs["clean"] is True + assert kwargs["dry_run"] is True diff --git a/mdfactory/tests/test_orchestration_config.py b/mdfactory/tests/test_orchestration_config.py new file mode 100644 index 0000000..27df9a1 --- /dev/null +++ b/mdfactory/tests/test_orchestration_config.py @@ -0,0 +1,612 @@ +# ABOUTME: Tests for orchestration executor configuration models +# ABOUTME: Validates config construction, serialization, and Parsl config generation +"""Tests for orchestration executor configuration.""" + +import pytest +import yaml + +pytest.importorskip("parsl", reason="parsl not installed") + +from mdfactory.orchestration.config import ExecutorConfig, SlurmExecutorConfig +from mdfactory.orchestration.environment import EnvironmentConfig + + +def test_executor_config_defaults(): + """ExecutorConfig has sensible defaults.""" + cfg = ExecutorConfig() + assert cfg.provider == "local" + assert cfg.environment == EnvironmentConfig() + assert cfg.working_directory is None + assert cfg.max_workers_per_node == 1 + + +def test_slurm_executor_config_account_optional(): + """SlurmExecutorConfig works without an account for clusters that don't need one. + + account defaults to "" so that clusters without account-based scheduling + (or users relying on their default SLURM account) can write a YAML config + without an account field. Parsl omits --account from the sbatch script + when the string is empty. + """ + cfg = SlurmExecutorConfig() + assert cfg.account == "" + + +def test_slurm_executor_config_defaults(): + """SlurmExecutorConfig has expected SLURM defaults.""" + cfg = SlurmExecutorConfig(account="my_account") + assert cfg.provider == "slurm" + assert cfg.account == "my_account" + assert cfg.partition == "cpu" + assert cfg.walltime == "02:00:00" + assert cfg.nodes == 1 + assert cfg.cpus_per_node == 12 + assert cfg.gres is None + assert cfg.mem is None + + +def test_executor_config_from_yaml_local(tmp_path): + """from_yaml loads a local executor config.""" + cfg_data = {"provider": "local", "max_workers_per_node": 4} + cfg_path = tmp_path / "config.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(cfg_data, f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert isinstance(loaded, ExecutorConfig) + assert not isinstance(loaded, SlurmExecutorConfig) + assert loaded.max_workers_per_node == 4 + + +def test_executor_config_from_yaml_slurm(tmp_path): + """from_yaml loads a SLURM executor config.""" + cfg_data = { + "provider": "slurm", + "account": "hpc_team", + "partition": "gpu-large", + "walltime": "4:00:00", + "cpus_per_node": 24, + "gres": "gpu:l40s:2", + } + cfg_path = tmp_path / "slurm.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(cfg_data, f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert isinstance(loaded, SlurmExecutorConfig) + assert loaded.account == "hpc_team" + assert loaded.partition == "gpu-large" + assert loaded.cpus_per_node == 24 + assert loaded.gres == "gpu:l40s:2" + + +def test_executor_config_to_parsl_config(): + """to_parsl_config produces a valid Parsl Config for local provider.""" + env = EnvironmentConfig(modules=["cuda/12.0"]) + cfg = ExecutorConfig(max_workers_per_node=2, environment=env) + result = cfg.to_parsl_config() + + import parsl + + assert isinstance(result, parsl.Config) + assert len(result.executors) == 1 + assert result.executors[0].label == "local" + + +def test_slurm_executor_config_to_parsl_config(): + """to_parsl_config produces a valid Parsl Config for SLURM provider.""" + cfg = SlurmExecutorConfig(account="my_account", gres="gpu:l40s:1") + result = cfg.to_parsl_config() + + import parsl + + assert isinstance(result, parsl.Config) + assert len(result.executors) == 1 + assert result.executors[0].label == "slurm" + + +def test_environment_propagation(): + """environment.compose_worker_init() is passed to the SLURM provider.""" + env = EnvironmentConfig(modules=["cuda/12.0"]) + cfg = SlurmExecutorConfig(account="acc", environment=env) + result = cfg.to_parsl_config() + + provider = result.executors[0].provider + assert "module load cuda/12.0" in provider.worker_init + + +def test_environment_combined_propagation(): + """Multiple environment fields are composed into the provider worker_init.""" + env = EnvironmentConfig( + modules=["gromacs/2024.3-gpu"], + pixi_manifest="/project", + ) + cfg = SlurmExecutorConfig(account="acc", environment=env) + result = cfg.to_parsl_config() + + provider = result.executors[0].provider + assert "module load gromacs/2024.3-gpu" in provider.worker_init + assert "pixi shell-hook" in provider.worker_init + + +def test_gres_in_scheduler_options(): + """gres field becomes --gres in scheduler_options.""" + cfg = SlurmExecutorConfig(account="acc", gres="gpu:l40s:2") + result = cfg.to_parsl_config() + + provider = result.executors[0].provider + assert "#SBATCH --gres=gpu:l40s:2" in provider.scheduler_options + + +def test_mem_and_qos_in_scheduler_options(): + """mem and qos fields are included in scheduler_options.""" + cfg = SlurmExecutorConfig(account="acc", mem="64G", qos="high") + result = cfg.to_parsl_config() + + provider = result.executors[0].provider + assert "#SBATCH --mem=64G" in provider.scheduler_options + assert "#SBATCH --qos=high" in provider.scheduler_options + + +def test_constraint_in_scheduler_options(): + """constraint field becomes --constraint in scheduler_options.""" + cfg = SlurmExecutorConfig(account="acc", constraint="a100") + result = cfg.to_parsl_config() + + provider = result.executors[0].provider + assert "#SBATCH --constraint=a100" in provider.scheduler_options + + +def test_slurm_executor_config_inherits_base_slurm_config(): + """SlurmExecutorConfig unifies with the shared BaseSlurmConfig hierarchy. + + Guards against re-introducing a divergent third copy of the SLURM fields + (issue #20): account/partition/qos/constraint and from_cluster() must come + from BaseSlurmConfig, not be redeclared here. + """ + from mdfactory.performance.slurm_config import BaseSlurmConfig + + assert issubclass(SlurmExecutorConfig, BaseSlurmConfig) + # from_cluster is inherited, not reimplemented. + assert SlurmExecutorConfig.from_cluster.__func__ is BaseSlurmConfig.from_cluster.__func__ + # The shared SLURM fields are not redeclared on the subclass itself. + own_fields = set(vars(SlurmExecutorConfig).get("__annotations__", {})) + assert not ({"account", "qos", "constraint"} & own_fields) + + +def test_slurm_executor_config_is_mutable(): + """Despite BaseSlurmConfig being frozen, the executor config stays mutable.""" + cfg = SlurmExecutorConfig(account="acc") + cfg.partition = "gpu" # must not raise (frozen=False) + assert cfg.partition == "gpu" + + +def test_raw_scheduler_options_appended(): + """Raw scheduler_options are appended after structured fields.""" + cfg = SlurmExecutorConfig(account="acc", gres="gpu:1", scheduler_options="#SBATCH --exclusive") + result = cfg.to_parsl_config() + + provider = result.executors[0].provider + assert "#SBATCH --gres=gpu:1" in provider.scheduler_options + assert "#SBATCH --exclusive" in provider.scheduler_options + + +def test_run_dir_default_and_expanduser(): + """run_dir defaults under the home dir and expands ``~`` in supplied values.""" + cfg = ExecutorConfig() + assert str(cfg.run_dir).endswith("/.parsl/mdfactory") + assert "~" not in str(cfg.run_dir) + + cfg2 = ExecutorConfig(run_dir="~/scratch/parsl") + assert "~" not in str(cfg2.run_dir) + assert str(cfg2.run_dir).endswith("/scratch/parsl") + + # run_dir flows into the Parsl Config + assert str(cfg.run_dir) == cfg.to_parsl_config().run_dir + + +def test_paths_serialize_as_strings(): + """run_dir / working_directory serialize as plain strings for YAML.""" + cfg = ExecutorConfig(working_directory="/tmp/work") + dumped = cfg.model_dump() + assert isinstance(dumped["run_dir"], str) + assert isinstance(dumped["working_directory"], str) + # Round-trips through yaml without RepresenterError + yaml.safe_dump(dumped) + + +def test_available_accelerators_wired_local(): + """available_accelerators is forwarded to the local HighThroughputExecutor.""" + cfg = ExecutorConfig(max_workers_per_node=2, available_accelerators=2) + executor = cfg.to_parsl_config().executors[0] + # Parsl normalises an int count into a list of device IDs. + assert executor.available_accelerators == ["0", "1"] + + +def test_available_accelerators_wired_slurm(): + """available_accelerators is forwarded to the SLURM HighThroughputExecutor.""" + cfg = SlurmExecutorConfig(account="acc", available_accelerators=["0", "1"]) + executor = cfg.to_parsl_config().executors[0] + assert executor.available_accelerators == ["0", "1"] + + +def test_launch_options_sets_srun_launcher(): + """launch_options wires a SrunLauncher with the given overrides.""" + from parsl.launchers import SrunLauncher + + cfg = SlurmExecutorConfig( + account="acc", + launch_options="--cpu-bind=cores --distribution=block:block", + ) + provider = cfg.to_parsl_config().executors[0].provider + assert isinstance(provider.launcher, SrunLauncher) + assert provider.launcher.overrides == "--cpu-bind=cores --distribution=block:block" + + +def test_no_launch_options_keeps_default_launcher(): + """Without launch_options, Parsl's default launcher is left untouched.""" + from parsl.launchers import SrunLauncher + + cfg = SlurmExecutorConfig(account="acc") + provider = cfg.to_parsl_config().executors[0].provider + assert not isinstance(provider.launcher, SrunLauncher) + + +def test_launch_options_roundtrip_yaml(tmp_path): + """launch_options survives a YAML round-trip.""" + original = SlurmExecutorConfig(account="acc", launch_options="--cpu-bind=cores") + cfg_path = tmp_path / "launch.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(original.model_dump(), f) + loaded = ExecutorConfig.from_yaml(cfg_path) + assert loaded.launch_options == "--cpu-bind=cores" + + +def test_config_yaml_roundtrip(tmp_path): + """Config can be written to YAML and loaded back identically.""" + env = EnvironmentConfig(modules=["gromacs/2024"], extra_init="source activate env") + original = SlurmExecutorConfig( + account="test_account", + partition="gpu-dev", + walltime="1:00:00", + gres="gpu:l40s:1", + environment=env, + ) + cfg_path = tmp_path / "roundtrip.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(original.model_dump(), f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert isinstance(loaded, SlurmExecutorConfig) + assert loaded.account == original.account + assert loaded.partition == original.partition + assert loaded.walltime == original.walltime + assert loaded.gres == original.gres + assert loaded.environment.modules == ["gromacs/2024"] + assert loaded.environment.extra_init == "source activate env" + + +# === EnvironmentConfig integration tests === + + +def test_environment_field_defaults_to_empty(): + """ExecutorConfig defaults environment to an empty EnvironmentConfig.""" + cfg = ExecutorConfig() + assert cfg.environment == EnvironmentConfig() + assert cfg.environment.compose_worker_init() == "" + + +def test_to_parsl_config_uses_environment(): + """to_parsl_config passes environment-composed string to the provider.""" + env = EnvironmentConfig(modules=["gromacs/2024.3-gpu"]) + cfg = ExecutorConfig(environment=env) + result = cfg.to_parsl_config() + provider = result.executors[0].provider + assert "module load gromacs/2024.3-gpu" in provider.worker_init + + +def test_from_yaml_environment_section(tmp_path): + """YAML with environment section loads correctly.""" + cfg_data = { + "provider": "slurm", + "account": "acc", + "environment": { + "modules": ["gromacs/2024.3-gpu"], + "pixi_manifest": "/project/root", + "extra_init": "ulimit -s unlimited", + }, + } + cfg_path = tmp_path / "new.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(cfg_data, f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert isinstance(loaded, SlurmExecutorConfig) + assert loaded.environment.modules == ["gromacs/2024.3-gpu"] + assert str(loaded.environment.pixi_manifest) == "/project/root" + assert loaded.environment.extra_init == "ulimit -s unlimited" + worker_init = loaded.environment.compose_worker_init() + assert "gromacs" in worker_init + assert "pixi shell-hook" in worker_init + + +def test_from_yaml_no_environment(tmp_path, monkeypatch): + """YAML without environment section gets empty EnvironmentConfig when no global exists.""" + # Point config dir to an empty tmp dir so no global file is found + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path / "config")) + cfg_data = {"provider": "slurm", "account": "acc"} + cfg_path = tmp_path / "minimal.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(cfg_data, f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert loaded.environment == EnvironmentConfig() + assert loaded.environment.compose_worker_init() == "" + + +def test_from_yaml_auto_loads_global_environment(tmp_path, monkeypatch): + """YAML without environment section auto-loads from global config.""" + # Create a global environment config + config_dir = tmp_path / "config" + config_dir.mkdir() + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(config_dir)) + global_env = EnvironmentConfig( + modules=["gromacs/2024"], + conda_env="md", + ) + global_env.save_yaml(config_dir / "environment.yaml") + + # Load a SLURM YAML with no environment section + cfg_data = {"provider": "slurm", "account": "acc"} + cfg_path = tmp_path / "slurm.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(cfg_data, f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert loaded.environment.modules == ["gromacs/2024"] + assert loaded.environment.conda_env == "md" + assert "gromacs/2024" in loaded.environment.compose_worker_init() + + +def test_from_yaml_explicit_environment_overrides_global(tmp_path, monkeypatch): + """YAML with explicit environment section ignores global config.""" + # Create a global environment config + config_dir = tmp_path / "config" + config_dir.mkdir() + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(config_dir)) + global_env = EnvironmentConfig(modules=["gromacs/2024"]) + global_env.save_yaml(config_dir / "environment.yaml") + + # Load a YAML that has its own environment section + cfg_data = { + "provider": "slurm", + "account": "acc", + "environment": {"modules": ["gromacs/2025"], "conda_env": "custom"}, + } + cfg_path = tmp_path / "slurm.yaml" + with open(cfg_path, "w") as f: + yaml.safe_dump(cfg_data, f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert loaded.environment.modules == ["gromacs/2025"] + assert loaded.environment.conda_env == "custom" + + +# === _load_executor_config tests === + + +def test_load_executor_config_none_without_global(tmp_path, monkeypatch): + """_load_executor_config(None) returns default ExecutorConfig when no global env exists.""" + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path / "no-config")) + from mdfactory.cli import _load_executor_config + + cfg = _load_executor_config(None) + assert cfg.environment == EnvironmentConfig() + assert cfg.environment.compose_worker_init() == "" + + +def test_load_executor_config_none_with_global(tmp_path, monkeypatch): + """_load_executor_config(None) loads global env config when it exists.""" + config_dir = tmp_path / "config" + config_dir.mkdir() + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(config_dir)) + global_env = EnvironmentConfig(modules=["gromacs/2024"], conda_env="md") + global_env.save_yaml(config_dir / "environment.yaml") + from mdfactory.cli import _load_executor_config + + cfg = _load_executor_config(None) + assert cfg.environment.modules == ["gromacs/2024"] + assert cfg.environment.conda_env == "md" + + +def test_load_executor_config_none_with_corrupt_global(tmp_path, monkeypatch): + """_load_executor_config(None) raises ValueError on corrupt global env config.""" + config_dir = tmp_path / "config" + config_dir.mkdir() + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(config_dir)) + (config_dir / "environment.yaml").write_text("- not a mapping\n") + from mdfactory.cli import _load_executor_config + + with pytest.raises(ValueError, match="empty or invalid"): + _load_executor_config(None) + + +# === Decision 6: Per-stage resource config tests === + + +def test_stage_overrides_empty_by_default(): + """SlurmExecutorConfig has empty stage_overrides by default.""" + cfg = SlurmExecutorConfig(account="acct") + assert cfg.stage_overrides == {} + + +def test_stage_overrides_loaded_from_yaml(tmp_path): + """stage_overrides field is preserved through YAML roundtrip.""" + cfg = SlurmExecutorConfig( + account="acct", + cpus_per_node=12, + stage_overrides={"EM": {"cpus_per_node": 4, "gres": None}}, + ) + yaml_path = tmp_path / "cfg.yaml" + with open(yaml_path, "w") as f: + import yaml + + yaml.safe_dump(cfg.model_dump(), f) + loaded = SlurmExecutorConfig.from_yaml(yaml_path) + assert loaded.stage_overrides == {"EM": {"cpus_per_node": 4, "gres": None}} + + +def test_get_stage_config_no_override_returns_self(): + """get_stage_config returns self when no override defined for stage.""" + cfg = SlurmExecutorConfig(account="acct", cpus_per_node=12) + result = cfg.get_stage_config("NVT") + assert result is cfg + + +def test_get_stage_config_applies_override(): + """get_stage_config merges override fields into a copy.""" + cfg = SlurmExecutorConfig( + account="acct", + cpus_per_node=12, + gres="gpu:l40s:1", + stage_overrides={"EM": {"cpus_per_node": 4, "gres": None}}, + ) + em_cfg = cfg.get_stage_config("EM") + # Override applied + assert em_cfg.cpus_per_node == 4 + assert em_cfg.gres is None + # Original unchanged + assert cfg.cpus_per_node == 12 + assert cfg.gres == "gpu:l40s:1" + + +def test_get_stage_config_inherits_non_overridden_fields(): + """get_stage_config preserves base fields not in the override dict.""" + cfg = SlurmExecutorConfig( + account="my_account", + partition="gpu", + cpus_per_node=12, + gres="gpu:l40s:1", + stage_overrides={"EM": {"cpus_per_node": 4}}, + ) + em_cfg = cfg.get_stage_config("EM") + # Overridden + assert em_cfg.cpus_per_node == 4 + # Inherited from base + assert em_cfg.account == "my_account" + assert em_cfg.partition == "gpu" + assert em_cfg.gres == "gpu:l40s:1" + + +def test_get_stage_config_does_not_mutate_original(): + """get_stage_config returns a distinct object and leaves original intact.""" + cfg = SlurmExecutorConfig( + account="acct", + cpus_per_node=12, + stage_overrides={"Production": {"cpus_per_node": 24}}, + ) + prod_cfg = cfg.get_stage_config("Production") + assert prod_cfg is not cfg + assert prod_cfg.cpus_per_node == 24 + assert cfg.cpus_per_node == 12 + + +def test_get_stage_config_multiple_stages(): + """Each stage can have independent overrides.""" + cfg = SlurmExecutorConfig( + account="acct", + cpus_per_node=12, + gres="gpu:l40s:1", + stage_overrides={ + "EM": {"cpus_per_node": 4, "gres": None}, + "Production": {"cpus_per_node": 24}, + }, + ) + em = cfg.get_stage_config("EM") + nvt = cfg.get_stage_config("NVT") + prod = cfg.get_stage_config("Production") + + assert em.cpus_per_node == 4 + assert em.gres is None + assert nvt is cfg # no override, returns self + assert prod.cpus_per_node == 24 + assert prod.gres == "gpu:l40s:1" # inherited + + +def test_get_stage_config_rejects_unhonored_keys(): + """Invalid override keys (e.g. walltime, mem) raise ValueError immediately.""" + cfg = SlurmExecutorConfig( + account="acct", + stage_overrides={"EM": {"walltime": "1:00:00"}}, + ) + with pytest.raises(ValueError, match="walltime"): + cfg.get_stage_config("EM") + + +def test_get_stage_config_rejects_multiple_unhonored_keys(): + """Multiple invalid keys are all reported in the error message.""" + cfg = SlurmExecutorConfig( + account="acct", + stage_overrides={"NVT": {"walltime": "2:00:00", "mem": "64G"}}, + ) + with pytest.raises(ValueError, match="walltime|mem"): + cfg.get_stage_config("NVT") + + +# === gmx_binary field tests === + + +def test_executor_config_gmx_binary_default(): + """gmx_binary defaults to 'auto' on both local and SLURM configs.""" + local = ExecutorConfig() + assert local.gmx_binary == "auto" + + slurm = SlurmExecutorConfig(account="acct") + assert slurm.gmx_binary == "auto" + + +def test_executor_config_gmx_binary_explicit_gmx(): + """gmx_binary accepts 'gmx' for thread-MPI builds.""" + cfg = ExecutorConfig(gmx_binary="gmx") + assert cfg.gmx_binary == "gmx" + + +def test_executor_config_gmx_binary_explicit_gmx_mpi(): + """gmx_binary accepts 'gmx_mpi' for pure-MPI builds.""" + cfg = ExecutorConfig(gmx_binary="gmx_mpi") + assert cfg.gmx_binary == "gmx_mpi" + + +def test_executor_config_gmx_binary_invalid(): + """Invalid gmx_binary value raises a Pydantic validation error.""" + from pydantic import ValidationError + + with pytest.raises(ValidationError): + ExecutorConfig(gmx_binary="gmx_openmp") # not a valid literal + + +def test_slurm_executor_config_gmx_binary_stage_override(): + """gmx_binary can be overridden per-stage via stage_overrides.""" + cfg = SlurmExecutorConfig( + account="acct", + gmx_binary="auto", + stage_overrides={"EM": {"gmx_binary": "gmx_mpi"}}, + ) + em_cfg = cfg.get_stage_config("EM") + nvt_cfg = cfg.get_stage_config("NVT") + + assert em_cfg.gmx_binary == "gmx_mpi" + assert nvt_cfg.gmx_binary == "auto" # inherited, no override + + +def test_executor_config_gmx_binary_yaml_roundtrip(tmp_path): + """gmx_binary survives a YAML round-trip.""" + original = SlurmExecutorConfig(account="acct", gmx_binary="gmx_mpi") + cfg_path = tmp_path / "cfg.yaml" + import yaml + + with open(cfg_path, "w") as f: + yaml.safe_dump(original.model_dump(), f) + + loaded = ExecutorConfig.from_yaml(cfg_path) + assert loaded.gmx_binary == "gmx_mpi" diff --git a/mdfactory/tests/test_orchestration_environment.py b/mdfactory/tests/test_orchestration_environment.py new file mode 100644 index 0000000..cd9f657 --- /dev/null +++ b/mdfactory/tests/test_orchestration_environment.py @@ -0,0 +1,332 @@ +# ABOUTME: Tests for EnvironmentConfig model +# ABOUTME: Validates compose_worker_init(), detect(), and YAML serialization +"""Tests for the EnvironmentConfig structured environment model.""" + +from pathlib import Path + +import pytest +import yaml + +from mdfactory.orchestration.environment import EnvironmentConfig + + +class TestComposeWorkerInit: + """Tests for EnvironmentConfig.compose_worker_init().""" + + def test_empty_produces_empty_string(self): + """All defaults produce an empty worker_init string.""" + env = EnvironmentConfig() + assert env.compose_worker_init() == "" + + def test_modules_only(self): + """Module list produces 'module load' commands.""" + env = EnvironmentConfig(modules=["gromacs/2024.3-gpu"]) + assert env.compose_worker_init() == "module load gromacs/2024.3-gpu" + + def test_multiple_modules(self): + """Multiple modules are joined with semicolons.""" + env = EnvironmentConfig(modules=["cuda/12.2", "gromacs/2024.3-gpu"]) + result = env.compose_worker_init() + assert result == "module load cuda/12.2; module load gromacs/2024.3-gpu" + + def test_pixi_manifest(self): + """pixi_manifest produces a pixi shell-hook command.""" + env = EnvironmentConfig(pixi_manifest=Path("/project/root")) + result = env.compose_worker_init() + assert result == 'eval "$(pixi shell-hook --manifest-path /project/root -e default)"' + + def test_conda_env(self): + """conda_env produces a conda activate command.""" + env = EnvironmentConfig(conda_env="myenv") + assert env.compose_worker_init() == "conda activate myenv" + + def test_venv_path(self): + """venv_path produces a source activate command.""" + env = EnvironmentConfig(venv_path=Path("/home/user/.venvs/md")) + assert env.compose_worker_init() == "source /home/user/.venvs/md/bin/activate" + + def test_pixi_takes_precedence_over_conda(self): + """When pixi_manifest and conda_env are both set, pixi wins.""" + env = EnvironmentConfig( + pixi_manifest=Path("/project"), + conda_env="myenv", + ) + result = env.compose_worker_init() + assert "pixi shell-hook" in result + assert "conda" not in result + + def test_pixi_takes_precedence_over_venv(self): + """When pixi_manifest and venv_path are both set, pixi wins.""" + env = EnvironmentConfig( + pixi_manifest=Path("/project"), + venv_path=Path("/venv"), + ) + result = env.compose_worker_init() + assert "pixi shell-hook" in result + assert "source" not in result + + def test_conda_takes_precedence_over_venv(self): + """When conda_env and venv_path are both set, conda wins.""" + env = EnvironmentConfig( + conda_env="myenv", + venv_path=Path("/venv"), + ) + result = env.compose_worker_init() + assert "conda activate myenv" in result + assert "source" not in result + + def test_extra_init_only(self): + """extra_init alone is returned verbatim.""" + env = EnvironmentConfig(extra_init="export OMP_NUM_THREADS=4") + assert env.compose_worker_init() == "export OMP_NUM_THREADS=4" + + def test_combined_modules_pixi_extra(self): + """All components are joined with semicolons in correct order.""" + env = EnvironmentConfig( + modules=["gromacs/2024.3-gpu"], + pixi_manifest=Path("/project"), + extra_init="export OMP_NUM_THREADS=4", + ) + result = env.compose_worker_init() + parts = result.split("; ") + assert parts[0] == "module load gromacs/2024.3-gpu" + assert "pixi shell-hook" in parts[1] + assert parts[2] == "export OMP_NUM_THREADS=4" + + def test_combined_modules_conda(self): + """Modules + conda are correctly combined.""" + env = EnvironmentConfig( + modules=["cuda/12.2"], + conda_env="gromacs_env", + ) + result = env.compose_worker_init() + assert result == "module load cuda/12.2; conda activate gromacs_env" + + +class TestDetect: + """Tests for EnvironmentConfig.detect() auto-detection.""" + + def test_detect_pixi_from_env_var(self, monkeypatch, tmp_path): + """PIXI_PROJECT_MANIFEST env var is detected.""" + manifest = tmp_path / "pixi.toml" + manifest.touch() + monkeypatch.setenv("PIXI_PROJECT_MANIFEST", str(manifest)) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + + env = EnvironmentConfig.detect() + assert env.pixi_manifest == tmp_path + assert env.conda_env is None + assert env.venv_path is None + + def test_detect_pixi_from_filesystem(self, monkeypatch, tmp_path): + """Pixi is detected from mdfactory's project root .pixi/ dir.""" + monkeypatch.delenv("PIXI_PROJECT_MANIFEST", raising=False) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + + # Create a fake mdfactory package structure + fake_pkg = tmp_path / "mdfactory" + fake_pkg.mkdir() + (fake_pkg / "__init__.py").write_text("__file__ = __file__\n") + pixi_env = tmp_path / ".pixi" / "envs" / "default" + pixi_env.mkdir(parents=True) + + # Patch mdfactory.__file__ to point to our fake + import mdfactory + + monkeypatch.setattr(mdfactory, "__file__", str(fake_pkg / "__init__.py")) + + env = EnvironmentConfig.detect() + assert env.pixi_manifest == tmp_path + + def test_detect_conda_from_env(self, monkeypatch): + """CONDA_PREFIX env var is detected when pixi is absent.""" + monkeypatch.delenv("PIXI_PROJECT_MANIFEST", raising=False) + monkeypatch.setenv("CONDA_PREFIX", "/opt/conda/envs/gromacs") + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + + # Ensure pixi filesystem detection also fails + import mdfactory + + monkeypatch.setattr(mdfactory, "__file__", "/nonexistent/mdfactory/__init__.py") + + env = EnvironmentConfig.detect() + assert env.pixi_manifest is None + assert env.conda_env == "gromacs" + assert env.venv_path is None + + def test_detect_venv_from_env(self, monkeypatch): + """VIRTUAL_ENV env var is detected when pixi and conda are absent.""" + monkeypatch.delenv("PIXI_PROJECT_MANIFEST", raising=False) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.setenv("VIRTUAL_ENV", "/home/user/.venvs/md") + + import mdfactory + + monkeypatch.setattr(mdfactory, "__file__", "/nonexistent/mdfactory/__init__.py") + + env = EnvironmentConfig.detect() + assert env.pixi_manifest is None + assert env.conda_env is None + assert env.venv_path == Path("/home/user/.venvs/md") + + def test_detect_nothing(self, monkeypatch): + """Clean environment produces empty EnvironmentConfig.""" + monkeypatch.delenv("PIXI_PROJECT_MANIFEST", raising=False) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + + import mdfactory + + monkeypatch.setattr(mdfactory, "__file__", "/nonexistent/mdfactory/__init__.py") + + env = EnvironmentConfig.detect() + assert env.pixi_manifest is None + assert env.conda_env is None + assert env.venv_path is None + assert env.modules == [] + assert env.extra_init == "" + assert env.compose_worker_init() == "" + + def test_detect_pixi_takes_precedence_over_conda(self, monkeypatch, tmp_path): + """When both PIXI_PROJECT_MANIFEST and CONDA_PREFIX are set, pixi wins.""" + manifest = tmp_path / "pixi.toml" + manifest.touch() + monkeypatch.setenv("PIXI_PROJECT_MANIFEST", str(manifest)) + monkeypatch.setenv("CONDA_PREFIX", "/opt/conda/envs/other") + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + + env = EnvironmentConfig.detect() + assert env.pixi_manifest == tmp_path + assert env.conda_env is None + + def test_detect_with_overrides(self, monkeypatch, tmp_path): + """Keyword overrides take precedence over auto-detection.""" + manifest = tmp_path / "pixi.toml" + manifest.touch() + monkeypatch.setenv("PIXI_PROJECT_MANIFEST", str(manifest)) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + + env = EnvironmentConfig.detect(modules=["gromacs/2024"], extra_init="ulimit -s unlimited") + assert env.pixi_manifest == tmp_path + assert env.modules == ["gromacs/2024"] + assert env.extra_init == "ulimit -s unlimited" + + +class TestSerialization: + """Tests for EnvironmentConfig YAML serialization.""" + + def test_paths_serialize_as_strings(self): + """Path fields serialize as plain strings for YAML compatibility.""" + env = EnvironmentConfig( + pixi_manifest=Path("/project/root"), + venv_path=Path("/home/user/.venvs/md"), + ) + dumped = env.model_dump() + assert isinstance(dumped["pixi_manifest"], str) + assert isinstance(dumped["venv_path"], str) + # Round-trips through yaml without RepresenterError + yaml.safe_dump(dumped) + + def test_yaml_roundtrip(self): + """EnvironmentConfig survives YAML round-trip.""" + env = EnvironmentConfig( + modules=["gromacs/2024.3-gpu", "cuda/12.2"], + pixi_manifest=Path("/project"), + extra_init="export OMP_NUM_THREADS=4", + ) + dumped = yaml.safe_dump(env.model_dump()) + loaded = yaml.safe_load(dumped) + restored = EnvironmentConfig(**loaded) + assert restored.modules == env.modules + assert restored.pixi_manifest == env.pixi_manifest + assert restored.extra_init == env.extra_init + assert restored.compose_worker_init() == env.compose_worker_init() + + def test_none_paths_excluded_with_exclude_none(self): + """None-valued path fields are excluded with exclude_none=True.""" + env = EnvironmentConfig(modules=["gromacs/2024"]) + dumped = env.model_dump(exclude_none=True) + assert "pixi_manifest" not in dumped + assert "venv_path" not in dumped + assert "conda_env" not in dumped + + +class TestSaveLoadYaml: + """Tests for EnvironmentConfig.save_yaml() and from_yaml().""" + + def test_save_and_load_roundtrip(self, tmp_path): + """save_yaml then from_yaml produces equivalent config.""" + env = EnvironmentConfig( + modules=["gromacs/2024.3-gpu"], + pixi_manifest=Path("/project"), + extra_init="export OMP_NUM_THREADS=4", + ) + path = tmp_path / "env.yaml" + env.save_yaml(path) + loaded = EnvironmentConfig.from_yaml(path) + assert loaded.modules == env.modules + assert loaded.pixi_manifest == env.pixi_manifest + assert loaded.extra_init == env.extra_init + assert loaded.compose_worker_init() == env.compose_worker_init() + + def test_save_omits_empty_fields(self, tmp_path): + """Empty modules and extra_init are not written to YAML.""" + env = EnvironmentConfig(conda_env="gromacs") + path = tmp_path / "env.yaml" + env.save_yaml(path) + data = yaml.safe_load(path.read_text()) + assert "modules" not in data + assert "extra_init" not in data + assert data["conda_env"] == "gromacs" + + def test_save_creates_parent_directories(self, tmp_path): + """save_yaml creates parent directories if they don't exist.""" + path = tmp_path / "deep" / "nested" / "env.yaml" + env = EnvironmentConfig(modules=["mod1"]) + env.save_yaml(path) + assert path.is_file() + + def test_from_yaml_file_not_found(self, tmp_path): + """from_yaml raises FileNotFoundError for missing file.""" + with pytest.raises(FileNotFoundError): + EnvironmentConfig.from_yaml(tmp_path / "nope.yaml") + + def test_from_yaml_invalid_content(self, tmp_path): + """from_yaml raises ValueError for non-mapping YAML.""" + path = tmp_path / "bad.yaml" + path.write_text("- item1\n- item2\n") + with pytest.raises(ValueError, match="empty or invalid"): + EnvironmentConfig.from_yaml(path) + + +class TestLoadGlobal: + """Tests for EnvironmentConfig.load_global().""" + + def test_returns_none_when_no_file(self, tmp_path, monkeypatch): + """load_global returns None when no global config exists.""" + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path)) + result = EnvironmentConfig.load_global() + assert result is None + + def test_loads_existing_global_config(self, tmp_path, monkeypatch): + """load_global returns config when global file exists.""" + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path)) + env = EnvironmentConfig( + modules=["gromacs/2024"], + conda_env="md", + ) + env.save_yaml(tmp_path / "environment.yaml") + result = EnvironmentConfig.load_global() + assert result is not None + assert result.modules == ["gromacs/2024"] + assert result.conda_env == "md" + + def test_raises_on_corrupt_file(self, tmp_path, monkeypatch): + """load_global raises ValueError if file exists but cannot be parsed.""" + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path)) + (tmp_path / "environment.yaml").write_text("- not a mapping\n") + with pytest.raises(ValueError, match="empty or invalid"): + EnvironmentConfig.load_global() diff --git a/mdfactory/tests/test_orchestration_errors.py b/mdfactory/tests/test_orchestration_errors.py new file mode 100644 index 0000000..e49ff86 --- /dev/null +++ b/mdfactory/tests/test_orchestration_errors.py @@ -0,0 +1,186 @@ +# ABOUTME: Tests for GROMACS error classification and Parsl exception unwrapping +# ABOUTME: Verifies physics vs infrastructure failure detection +"""Tests for mdfactory.orchestration.errors.""" + +from mdfactory.orchestration.errors import ( + FailureType, + _matches_physics_patterns, + _unwrap_parsl_exception, + classify_failure, +) + + +class TestMatchesPhysicsPatterns: + def test_lincs_warning(self): + assert _matches_physics_patterns("LINCS WARNING: relative constraint deviation") + + def test_coordinate_constraints(self): + assert _matches_physics_patterns( + "step 1234: the coordinate constraints could not be satisfied" + ) + + def test_particle_moved(self): + assert _matches_physics_patterns("Particle 42 in box 3 moved more than 5.0 nm") + + def test_domain_decomposition(self): + assert _matches_physics_patterns("The domain decomposition cell size is too small") + + def test_blowing_up(self): + assert _matches_physics_patterns("Fatal error: blowing up") + + def test_force_not_finite(self): + assert _matches_physics_patterns("force 1.#INF is not finite") + + def test_water_molecule_starting_at(self): + assert _matches_physics_patterns( + "step 1000: Water molecule starting at (1.2, 3.4, 5.6) cannot be settled" + ) + + def test_too_many_lincs_warnings(self): + assert _matches_physics_patterns("Too many LINCS warnings") + + def test_can_not_continue(self): + assert _matches_physics_patterns("can not continue") + + def test_abnormal_termination(self): + assert _matches_physics_patterns("Abnormal termination of GROMACS") + + def test_perturbed_nonbonded_pairs(self): + assert _matches_physics_patterns( + "There are 42 perturbed non-bonded pair interactions beyond the pair-list cutoff" + ) + + def test_atoms_too_many_constraints(self): + assert _matches_physics_patterns( + "atoms 123 and 456 are involved in more than 6 constraints" + ) + + def test_normal_output_no_match(self): + assert not _matches_physics_patterns("Step 1000, time 2.0 ps") + + def test_empty_string_no_match(self): + assert not _matches_physics_patterns("") + + +class TestClassifyFailure: + def test_physics_from_exception_message(self): + exc = RuntimeError("LINCS WARNING: bonds to H are constrained") + assert classify_failure(exc) == FailureType.PHYSICS + + def test_physics_from_coordinate_error(self): + exc = RuntimeError("step 500: the coordinate constraints could not be satisfied") + assert classify_failure(exc) == FailureType.PHYSICS + + def test_infrastructure_oom(self): + exc = RuntimeError("slurmstepd: error: Detected 1 oom-kill event") + assert classify_failure(exc) == FailureType.INFRASTRUCTURE + + def test_infrastructure_timeout(self): + exc = RuntimeError("JOB 12345 CANCELLED AT DUE TO TIME LIMIT") + assert classify_failure(exc) == FailureType.INFRASTRUCTURE + + def test_infrastructure_preempted(self): + exc = RuntimeError("Job was preempted by higher priority job") + assert classify_failure(exc) == FailureType.INFRASTRUCTURE + + def test_unknown_for_generic_error(self): + exc = RuntimeError("Something went wrong") + assert classify_failure(exc) == FailureType.UNKNOWN + + def test_unwraps_parsl_legacy_exception(self): + class LegacyWrapper(Exception): + pass + + exc = LegacyWrapper("wrapper") + exc.e_value = RuntimeError("LINCS WARNING in bonds") + assert classify_failure(exc) == FailureType.PHYSICS + + def test_physics_from_log_file(self, tmp_path, monkeypatch): + from mdfactory.orchestration.stages import StageSpec + + mock_spec = StageSpec( + name="EM", + deffnm="min", + mdp_file="em.mdp", + gro_in="system.pdb", + gro_out="min.gro", + tpr_file="min.tpr", + cpt_file="min.cpt", + prereq_cpt=None, + traj_files=(), + ref_file=None, + maxwarn=1, + supports_pme_gpu=False, + ) + monkeypatch.setattr("mdfactory.orchestration.stages.STAGE_BY_NAME", {"EM": mock_spec}) + log_file = tmp_path / "min.log" + log_file.write_text("Step 100\nLINCS WARNING\nStep 101\n") + + exc = RuntimeError("Process returned non-zero exit code 1") + result = classify_failure(exc, sim_dir=tmp_path, stage="EM") + assert result == FailureType.PHYSICS + + def test_unwraps_dependency_error_with_cause(self): + """DependencyError (via __cause__) is unwrapped to classify the root cause.""" + root = RuntimeError("LINCS WARNING: bonds to H are constrained") + wrapper = RuntimeError("Dependency failure for task 5") + wrapper.__cause__ = root + assert classify_failure(wrapper) == FailureType.PHYSICS + + def test_dependency_error_infra_from_root_cause(self): + """DependencyError wrapping an infrastructure failure is classified correctly.""" + root = RuntimeError("slurmstepd: error: Detected 1 oom-kill event") + wrapper = RuntimeError("Dependency failure for task 3") + wrapper.__cause__ = root + assert classify_failure(wrapper) == FailureType.INFRASTRUCTURE + + def test_dependency_error_unknown_root_cause(self): + """DependencyError wrapping an unrecognised error yields UNKNOWN.""" + root = RuntimeError("Neither gmx nor gmx_mpi found in PATH") + wrapper = RuntimeError("Dependency failure for task 1") + wrapper.__cause__ = root + assert classify_failure(wrapper) == FailureType.UNKNOWN + + +class TestUnwrapParslException: + """Tests for _unwrap_parsl_exception helper.""" + + def test_plain_exception_returned_as_is(self): + exc = RuntimeError("simple error") + assert _unwrap_parsl_exception(exc) is exc + + def test_unwraps_cause(self): + root = ValueError("the real error") + wrapper = RuntimeError("Dependency failure for task 5") + wrapper.__cause__ = root + assert _unwrap_parsl_exception(wrapper) is root + + def test_unwraps_legacy_e_value(self): + class LegacyWrapper(Exception): + pass + + root = RuntimeError("LINCS WARNING in bonds") + wrapper = LegacyWrapper("wrapper message") + wrapper.e_value = root + assert _unwrap_parsl_exception(wrapper) is root + + def test_cause_takes_precedence_over_e_value(self): + """If both __cause__ and .e_value exist, __cause__ wins.""" + cause_exc = ValueError("from __cause__") + e_val_exc = RuntimeError("from e_value") + wrapper = RuntimeError("wrapper") + wrapper.__cause__ = cause_exc + wrapper.e_value = e_val_exc + assert _unwrap_parsl_exception(wrapper) is cause_exc + + def test_chained_cause(self): + """Deeply nested __cause__ chain — returns the immediate __cause__.""" + deepest = ValueError("deepest root") + middle = RuntimeError("middle") + middle.__cause__ = deepest + outer = RuntimeError("Dependency failure") + outer.__cause__ = middle + # Returns the immediate __cause__ (middle), not the deepest. + # Parsl's _find_any_root_cause already resolves __cause__ to + # the deepest, so this is the expected behaviour. + assert _unwrap_parsl_exception(outer) is middle diff --git a/mdfactory/tests/test_orchestration_from_cluster.py b/mdfactory/tests/test_orchestration_from_cluster.py new file mode 100644 index 0000000..00afa50 --- /dev/null +++ b/mdfactory/tests/test_orchestration_from_cluster.py @@ -0,0 +1,140 @@ +# ABOUTME: Tests for SlurmExecutorConfig.from_cluster() and walltime validation +# ABOUTME: Verifies 3-tier autodiscovery precedence and time normalization +"""Tests for SlurmExecutorConfig cluster autodiscovery and walltime.""" + +from unittest.mock import MagicMock, patch + +import pytest + +from mdfactory.orchestration.config import SlurmExecutorConfig + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _mock_settings(**overrides): + s = MagicMock() + s.slurm_account = overrides.get("slurm_account", None) + s.slurm_partition_cpu = overrides.get("slurm_partition_cpu", None) + s.slurm_partition_gpu = overrides.get("slurm_partition_gpu", None) + s.slurm_qos = overrides.get("slurm_qos", None) + return s + + +def _mock_cluster(default_account="test_acc", accounts=None): + c = MagicMock() + c.default_account = default_account + c.accounts = accounts or [default_account] + return c + + +# --------------------------------------------------------------------------- +# Walltime normalisation +# --------------------------------------------------------------------------- + + +class TestWalltimeNormalization: + """Walltime shorthand is expanded on construction.""" + + def test_walltime_normalization(self): + assert SlurmExecutorConfig(account="x", walltime="2h").walltime == "02:00:00" + assert SlurmExecutorConfig(account="x", walltime="30m").walltime == "00:30:00" + assert SlurmExecutorConfig(account="x", walltime="1d").walltime == "1-00:00:00" + assert SlurmExecutorConfig(account="x", walltime="01:30:00").walltime == "01:30:00" + + def test_walltime_default_normalized(self): + cfg = SlurmExecutorConfig(account="x") + assert cfg.walltime == "02:00:00" + + +# --------------------------------------------------------------------------- +# from_cluster() tests +# --------------------------------------------------------------------------- + +# Patch targets — from_cluster imports these inside the method body, so we +# patch the canonical module locations. +_DISCOVER = "mdfactory.performance.cluster.discover_cluster" +_SELECT = "mdfactory.performance.cluster.select_partition" +_SETTINGS = "mdfactory.settings.settings" + + +class TestFromCluster: + """Tests for the three-tier autodiscovery in from_cluster().""" + + @patch(_DISCOVER, return_value=None) + @patch(_SETTINGS, _mock_settings()) + def test_explicit_kwargs(self, _discover): + cfg = SlurmExecutorConfig.from_cluster(account="myacc", partition="gpu") + assert cfg.account == "myacc" + assert cfg.partition == "gpu" + + @patch(_DISCOVER, return_value=None) + @patch( + _SETTINGS, + _mock_settings(slurm_account="cfg_account", slurm_partition_cpu="cfg_partition"), + ) + def test_config_ini_fallback(self, _discover): + cfg = SlurmExecutorConfig.from_cluster() + assert cfg.account == "cfg_account" + assert cfg.partition == "cfg_partition" + + @patch(_SELECT) + @patch(_DISCOVER) + @patch(_SETTINGS, _mock_settings()) + def test_autodiscovery(self, mock_discover, mock_select): + mock_discover.return_value = _mock_cluster(default_account="discovered_acc") + part = MagicMock() + part.name = "auto_part" + mock_select.return_value = part + + cfg = SlurmExecutorConfig.from_cluster() + assert cfg.account == "discovered_acc" + assert cfg.partition == "auto_part" + + @patch(_SELECT) + @patch(_DISCOVER) + @patch(_SETTINGS, _mock_settings(slurm_account="cfg_acc")) + def test_precedence_explicit_wins(self, mock_discover, mock_select): + mock_discover.return_value = _mock_cluster(default_account="disc_acc") + part = MagicMock() + part.name = "disc_part" + mock_select.return_value = part + + cfg = SlurmExecutorConfig.from_cluster(account="explicit") + assert cfg.account == "explicit" + + @patch(_DISCOVER, return_value=None) + @patch(_SETTINGS, _mock_settings()) + def test_no_account_raises(self, _discover): + with pytest.raises(RuntimeError, match="account"): + SlurmExecutorConfig.from_cluster() + + @patch(_DISCOVER, return_value=None) + @patch(_SETTINGS, _mock_settings(slurm_account="has_acc")) + def test_no_partition_raises(self, _discover): + with pytest.raises(RuntimeError, match="partition"): + SlurmExecutorConfig.from_cluster() + + @patch(_SELECT) + @patch(_DISCOVER) + @patch(_SETTINGS, _mock_settings()) + def test_extra_fields_forwarded(self, mock_discover, mock_select): + mock_discover.return_value = _mock_cluster() + part = MagicMock() + part.name = "cpu" + mock_select.return_value = part + + cfg = SlurmExecutorConfig.from_cluster(walltime="4h", cpus_per_node=24, gres="gpu:a100:1") + assert cfg.walltime == "04:00:00" + assert cfg.cpus_per_node == 24 + assert cfg.gres == "gpu:a100:1" + + @patch(_DISCOVER, return_value=None) + @patch( + _SETTINGS, + _mock_settings(slurm_account="acc", slurm_partition_gpu="gpu-big"), + ) + def test_needs_gpu_selects_gpu_partition(self, _discover): + cfg = SlurmExecutorConfig.from_cluster(needs_gpu=True) + assert cfg.partition == "gpu-big" diff --git a/mdfactory/tests/test_orchestration_mdp.py b/mdfactory/tests/test_orchestration_mdp.py new file mode 100644 index 0000000..6f02a69 --- /dev/null +++ b/mdfactory/tests/test_orchestration_mdp.py @@ -0,0 +1,218 @@ +# ABOUTME: Tests for MDP parser/modifier used in rescue retry +# ABOUTME: Verifies parsing, modification, and binary-division rescue tier logic +"""Tests for mdfactory.orchestration.mdp.""" + +import pytest + +from mdfactory.orchestration.mdp import ( + RESCUE_ELIGIBLE_STAGES, + RESCUE_PARAMS, + apply_rescue_tier, + get_mdp_value, + modify_mdp_value, + parse_mdp, + write_mdp, +) + + +@pytest.fixture +def em_mdp(tmp_path): + """Create a minimal EM MDP file.""" + content = """; minim.mdp +integrator = steep +emtol = 1000.0 ; Stop at max force < 1000 +emstep = 0.01 ; Step size +nsteps = 50000 ; Max steps +nstlist = 1 +""" + p = tmp_path / "em.mdp" + p.write_text(content) + return p + + +@pytest.fixture +def nvt_mdp(tmp_path): + """Create a minimal NVT MDP file.""" + content = """; NVT equilibration +integrator = md +dt = 0.002 ; 2 fs +nsteps = 50000 ; 100 ps +constraints = h-bonds +""" + p = tmp_path / "nvt.mdp" + p.write_text(content) + return p + + +class TestParseMdp: + def test_parses_key_value_pairs(self, em_mdp): + parsed = parse_mdp(em_mdp) + assert get_mdp_value(parsed, "integrator") == "steep" + assert get_mdp_value(parsed, "emtol") == "1000.0" + assert get_mdp_value(parsed, "emstep") == "0.01" + assert get_mdp_value(parsed, "nsteps") == "50000" + + def test_preserves_comment_lines(self, em_mdp): + parsed = parse_mdp(em_mdp) + assert parsed[0][1] is None + assert parsed[0][2] is None + + def test_handles_semicolon_inline_comments(self, em_mdp): + parsed = parse_mdp(em_mdp) + assert get_mdp_value(parsed, "emtol") == "1000.0" + + def test_normalizes_dashes_to_underscores(self, tmp_path): + p = tmp_path / "test.mdp" + p.write_text("lincs-iter = 2\nnstxout-compressed = 500\n") + parsed = parse_mdp(p) + assert get_mdp_value(parsed, "lincs_iter") == "2" + assert get_mdp_value(parsed, "nstxout_compressed") == "500" + + def test_missing_key_returns_none(self, em_mdp): + parsed = parse_mdp(em_mdp) + assert get_mdp_value(parsed, "nonexistent") is None + + +class TestModifyMdpValue: + def test_replaces_value(self, em_mdp): + parsed = parse_mdp(em_mdp) + modified = modify_mdp_value(parsed, "emstep", "0.005") + assert get_mdp_value(modified, "emstep") == "0.005" + + def test_preserves_other_values(self, em_mdp): + parsed = parse_mdp(em_mdp) + modified = modify_mdp_value(parsed, "emstep", "0.005") + assert get_mdp_value(modified, "nsteps") == "50000" + assert get_mdp_value(modified, "emtol") == "1000.0" + + +class TestWriteMdp: + def test_roundtrip_preserves_structure(self, em_mdp, tmp_path): + parsed = parse_mdp(em_mdp) + out = tmp_path / "out.mdp" + write_mdp(parsed, out) + reparsed = parse_mdp(out) + assert get_mdp_value(reparsed, "emstep") == "0.01" + assert get_mdp_value(reparsed, "nsteps") == "50000" + + def test_modified_roundtrip(self, em_mdp, tmp_path): + parsed = parse_mdp(em_mdp) + modified = modify_mdp_value(parsed, "emstep", "0.005") + out = tmp_path / "modified.mdp" + write_mdp(modified, out) + reparsed = parse_mdp(out) + assert get_mdp_value(reparsed, "emstep") == "0.005" + + +class TestApplyRescueTier: + def test_em_tier1_halves_emstep_doubles_nsteps(self, tmp_path, em_mdp, monkeypatch): + from mdfactory.orchestration.stages import StageSpec + + mock_spec = StageSpec( + name="EM", + deffnm="min", + mdp_file="em.mdp", + gro_in="system.pdb", + gro_out="min.gro", + tpr_file="min.tpr", + cpt_file="min.cpt", + prereq_cpt=None, + traj_files=(), + ref_file=None, + maxwarn=1, + supports_pme_gpu=False, + ) + monkeypatch.setattr("mdfactory.orchestration.stages.STAGE_BY_NAME", {"EM": mock_spec}) + rescue_path = apply_rescue_tier(tmp_path, "EM", 1) + assert rescue_path.name == "em_rescue_t1.mdp" + assert rescue_path.exists() + parsed = parse_mdp(rescue_path) + assert get_mdp_value(parsed, "emstep") == "0.005" + assert get_mdp_value(parsed, "nsteps") == "100000" + + def test_em_tier2_compounds(self, tmp_path, em_mdp, monkeypatch): + from mdfactory.orchestration.stages import StageSpec + + mock_spec = StageSpec( + name="EM", + deffnm="min", + mdp_file="em.mdp", + gro_in="system.pdb", + gro_out="min.gro", + tpr_file="min.tpr", + cpt_file="min.cpt", + prereq_cpt=None, + traj_files=(), + ref_file=None, + maxwarn=1, + supports_pme_gpu=False, + ) + monkeypatch.setattr("mdfactory.orchestration.stages.STAGE_BY_NAME", {"EM": mock_spec}) + rescue_path = apply_rescue_tier(tmp_path, "EM", 2) + parsed = parse_mdp(rescue_path) + assert get_mdp_value(parsed, "emstep") == "0.0025" + assert get_mdp_value(parsed, "nsteps") == "200000" + + def test_nvt_tier1_halves_dt_doubles_nsteps(self, tmp_path, nvt_mdp, monkeypatch): + from mdfactory.orchestration.stages import StageSpec + + mock_spec = StageSpec( + name="NVT", + deffnm="nvt", + mdp_file="nvt.mdp", + gro_in="min.gro", + gro_out="nvt.gro", + tpr_file="nvt.tpr", + cpt_file="nvt.cpt", + prereq_cpt=None, + traj_files=(), + ref_file="min.gro", + maxwarn=1, + supports_pme_gpu=True, + ) + monkeypatch.setattr("mdfactory.orchestration.stages.STAGE_BY_NAME", {"NVT": mock_spec}) + rescue_path = apply_rescue_tier(tmp_path, "NVT", 1) + assert rescue_path.name == "nvt_rescue_t1.mdp" + parsed = parse_mdp(rescue_path) + assert get_mdp_value(parsed, "dt") == "0.001" + assert get_mdp_value(parsed, "nsteps") == "100000" + + def test_invalid_stage_raises(self, tmp_path): + with pytest.raises(ValueError, match="not rescue-eligible"): + apply_rescue_tier(tmp_path, "Production", 1) + + def test_tier_zero_raises(self, tmp_path): + with pytest.raises(ValueError, match="tier must be >= 1"): + apply_rescue_tier(tmp_path, "EM", 0) + + def test_missing_mdp_raises(self, tmp_path, monkeypatch): + from mdfactory.orchestration.stages import StageSpec + + mock_spec = StageSpec( + name="EM", + deffnm="min", + mdp_file="em.mdp", + gro_in="system.pdb", + gro_out="min.gro", + tpr_file="min.tpr", + cpt_file="min.cpt", + prereq_cpt=None, + traj_files=(), + ref_file=None, + maxwarn=1, + supports_pme_gpu=False, + ) + monkeypatch.setattr("mdfactory.orchestration.stages.STAGE_BY_NAME", {"EM": mock_spec}) + with pytest.raises(FileNotFoundError): + apply_rescue_tier(tmp_path, "EM", 1) + + +class TestRescueConstants: + def test_eligible_stages(self): + assert RESCUE_ELIGIBLE_STAGES == {"EM", "NVT", "NPT"} + + def test_production_not_eligible(self): + assert "Production" not in RESCUE_ELIGIBLE_STAGES + + def test_rescue_params_keys_match_eligible(self): + assert set(RESCUE_PARAMS.keys()) == RESCUE_ELIGIBLE_STAGES diff --git a/mdfactory/tests/test_orchestration_progress.py b/mdfactory/tests/test_orchestration_progress.py new file mode 100644 index 0000000..87f54aa --- /dev/null +++ b/mdfactory/tests/test_orchestration_progress.py @@ -0,0 +1,140 @@ +# ABOUTME: Tests for per-stage simulation progress tracking +# ABOUTME: Covers StageProgressTracker state transitions, thread safety, and result collection +"""Tests for the per-stage progress tracker.""" + +import threading + +from mdfactory.orchestration.progress import StageProgressTracker + + +class TestStageProgressTracker: + """Unit tests for StageProgressTracker.""" + + def _make_tracker(self): + return StageProgressTracker( + stages=["EM", "NVT", "NPT", "Production"], + sim_hashes=["aaa", "bbb", "ccc"], + ) + + def test_initial_state_all_pending(self): + tracker = self._make_tracker() + snap = tracker.snapshot() + for stage in ["EM", "NVT", "NPT", "Production"]: + assert snap[stage]["pending"] == 3 + assert snap[stage]["running"] == 0 + + def test_mark_running(self): + tracker = self._make_tracker() + tracker.mark_running("EM", "aaa") + snap = tracker.snapshot() + assert snap["EM"]["running"] == 1 + assert snap["EM"]["pending"] == 2 + + def test_mark_succeeded(self): + tracker = self._make_tracker() + tracker.mark_running("EM", "aaa") + tracker.mark_succeeded("EM", "aaa") + snap = tracker.snapshot() + assert snap["EM"]["succeeded"] == 1 + assert snap["EM"]["running"] == 0 + + def test_mark_failed(self): + tracker = self._make_tracker() + tracker.mark_running("NVT", "bbb") + tracker.mark_failed("NVT", "bbb") + snap = tracker.snapshot() + assert snap["NVT"]["failed"] == 1 + + def test_mark_skipped(self): + tracker = self._make_tracker() + tracker.mark_skipped("NPT", "ccc") + snap = tracker.snapshot() + assert snap["NPT"]["skipped"] == 1 + + def test_all_done_false_initially(self): + tracker = self._make_tracker() + assert not tracker.all_done() + + def test_all_done_true_when_last_stage_terminal(self): + tracker = self._make_tracker() + tracker.mark_succeeded("Production", "aaa") + tracker.mark_failed("Production", "bbb") + tracker.mark_skipped("Production", "ccc") + assert tracker.all_done() + + def test_all_done_false_with_running_last_stage(self): + tracker = self._make_tracker() + tracker.mark_succeeded("Production", "aaa") + tracker.mark_succeeded("Production", "bbb") + tracker.mark_running("Production", "ccc") + assert not tracker.all_done() + + def test_store_and_collect_results(self): + tracker = self._make_tracker() + tracker.store_result("bbb", {"hash": "bbb", "status": "success"}) + tracker.store_result("aaa", {"hash": "aaa", "status": "failed"}) + tracker.store_result("ccc", {"hash": "ccc", "status": "success"}) + + results = tracker.collect_results() + assert len(results) == 3 + assert results[0]["hash"] == "aaa" + assert results[1]["hash"] == "bbb" + assert results[2]["hash"] == "ccc" + + def test_collect_results_missing_returns_unknown(self): + tracker = self._make_tracker() + tracker.store_result("aaa", {"hash": "aaa", "status": "success"}) + results = tracker.collect_results() + assert results[1] == {"hash": "bbb", "status": "unknown"} + + def test_thread_safety(self): + """Concurrent mark calls from multiple threads don't crash.""" + tracker = StageProgressTracker( + stages=["EM", "Production"], + sim_hashes=[f"sim{i}" for i in range(20)], + ) + errors = [] + + def _worker(i): + try: + h = f"sim{i}" + tracker.mark_running("EM", h) + tracker.mark_succeeded("EM", h) + tracker.mark_running("Production", h) + tracker.mark_succeeded("Production", h) + tracker.store_result(h, {"hash": h, "status": "success"}) + except Exception as exc: + errors.append(exc) + + threads = [threading.Thread(target=_worker, args=(i,)) for i in range(20)] + for t in threads: + t.start() + for t in threads: + t.join() + + assert not errors + assert tracker.all_done() + snap = tracker.snapshot() + assert snap["EM"]["succeeded"] == 20 + assert snap["Production"]["succeeded"] == 20 + + def test_failure_cascade_pattern(self): + """Simulates a pipeline failing at NVT — remaining stages skipped.""" + tracker = self._make_tracker() + tracker.mark_running("EM", "aaa") + tracker.mark_succeeded("EM", "aaa") + tracker.mark_running("NVT", "aaa") + tracker.mark_failed("NVT", "aaa") + tracker.mark_skipped("NPT", "aaa") + tracker.mark_skipped("Production", "aaa") + + # Other sims succeed + for h in ["bbb", "ccc"]: + for s in ["EM", "NVT", "NPT", "Production"]: + tracker.mark_succeeded(s, h) + + assert tracker.all_done() + snap = tracker.snapshot() + assert snap["NVT"]["failed"] == 1 + assert snap["NPT"]["skipped"] == 1 + assert snap["Production"]["skipped"] == 1 diff --git a/mdfactory/tests/test_orchestration_rescue.py b/mdfactory/tests/test_orchestration_rescue.py new file mode 100644 index 0000000..f29b456 --- /dev/null +++ b/mdfactory/tests/test_orchestration_rescue.py @@ -0,0 +1,214 @@ +# ABOUTME: Tests for rescue retry loop +# ABOUTME: Verifies rescue activation, tier progression, and failure handling +"""Tests for mdfactory.orchestration.rescue.""" + +from unittest.mock import MagicMock, patch + +import pytest + +from mdfactory.orchestration.errors import FailureType +from mdfactory.orchestration.rescue import ( + _clean_partial_outputs, + execute_stage_with_rescue, +) + + +@pytest.fixture +def sim_dir(tmp_path): + """Create a simulation directory with MDP files.""" + (tmp_path / "em.mdp").write_text("integrator = steep\nemstep = 0.01\nnsteps = 50000\n") + (tmp_path / "nvt.mdp").write_text("integrator = md\ndt = 0.002\nnsteps = 50000\n") + (tmp_path / "system.pdb").touch() + (tmp_path / "topology.top").touch() + return tmp_path + + +class TestCleanPartialOutputs: + def test_removes_stage_files(self, tmp_path): + (tmp_path / "min.tpr").touch() + (tmp_path / "min.cpt").touch() + (tmp_path / "min.gro").touch() + (tmp_path / "min.log").touch() + (tmp_path / "min.edr").touch() + + _clean_partial_outputs(tmp_path, "EM") + + assert not (tmp_path / "min.tpr").exists() + assert not (tmp_path / "min.cpt").exists() + assert not (tmp_path / "min.gro").exists() + assert not (tmp_path / "min.log").exists() + assert not (tmp_path / "min.edr").exists() + + def test_handles_missing_files_gracefully(self, tmp_path): + _clean_partial_outputs(tmp_path, "EM") + + def test_production_cleans_trajectory(self, tmp_path): + (tmp_path / "prod.xtc").touch() + (tmp_path / "prod.trr").touch() + (tmp_path / "prod.tpr").touch() + _clean_partial_outputs(tmp_path, "Production") + assert not (tmp_path / "prod.xtc").exists() + assert not (tmp_path / "prod.trr").exists() + + +class TestExecuteStageWithRescue: + @patch("mdfactory.orchestration.rescue.run_stage") + def test_success_on_first_try(self, mock_run_stage, sim_dir): + mock_future = MagicMock() + mock_future.result.return_value = None + mock_run_stage.return_value = mock_future + + result = execute_stage_with_rescue( + sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3 + ) + assert result is mock_future + assert mock_run_stage.call_count == 1 + + @patch("mdfactory.orchestration.rescue.classify_failure") + @patch("mdfactory.orchestration.rescue.run_stage") + def test_rescue_on_physics_failure(self, mock_run_stage, mock_classify, sim_dir): + fail_future = MagicMock() + fail_future.result.side_effect = RuntimeError("LINCS WARNING") + success_future = MagicMock() + success_future.result.return_value = None + mock_run_stage.side_effect = [fail_future, success_future] + mock_classify.return_value = FailureType.PHYSICS + + result = execute_stage_with_rescue( + sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3 + ) + assert result is success_future + assert mock_run_stage.call_count == 2 + assert (sim_dir / "em_rescue_t1.mdp").exists() + + @patch("mdfactory.orchestration.rescue.classify_failure") + @patch("mdfactory.orchestration.rescue.run_stage") + def test_rescue_exhausted_raises(self, mock_run_stage, mock_classify, sim_dir): + fail_future = MagicMock() + fail_future.result.side_effect = RuntimeError("LINCS WARNING") + mock_run_stage.return_value = fail_future + mock_classify.return_value = FailureType.PHYSICS + + with pytest.raises(RuntimeError, match="LINCS WARNING"): + execute_stage_with_rescue(sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=2) + # tier 0 + tier 1 + tier 2 = 3 attempts + assert mock_run_stage.call_count == 3 + + @patch("mdfactory.orchestration.rescue.classify_failure") + @patch("mdfactory.orchestration.rescue.run_stage") + def test_infra_failure_not_rescued(self, mock_run_stage, mock_classify, sim_dir): + fail_future = MagicMock() + fail_future.result.side_effect = RuntimeError("Out of memory") + mock_run_stage.return_value = fail_future + mock_classify.return_value = FailureType.INFRASTRUCTURE + + with pytest.raises(RuntimeError, match="Out of memory"): + execute_stage_with_rescue(sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3) + assert mock_run_stage.call_count == 1 + + @patch("mdfactory.orchestration.rescue.classify_failure") + @patch("mdfactory.orchestration.rescue.run_stage") + def test_mdp_override_passed_to_run_stage(self, mock_run_stage, mock_classify, sim_dir): + fail_future = MagicMock() + fail_future.result.side_effect = RuntimeError("LINCS") + success_future = MagicMock() + success_future.result.return_value = None + mock_run_stage.side_effect = [fail_future, success_future] + mock_classify.return_value = FailureType.PHYSICS + + execute_stage_with_rescue(sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3) + + # First call: no mdp_override (tier 0) + first_call = mock_run_stage.call_args_list[0] + assert first_call.kwargs.get("mdp_override") is None + + # Second call: has mdp_override (tier 1) + second_call = mock_run_stage.call_args_list[1] + assert second_call.kwargs.get("mdp_override") == "em_rescue_t1.mdp" + + @patch("mdfactory.orchestration.rescue.classify_failure") + @patch("mdfactory.orchestration.rescue.run_stage") + def test_unknown_failure_not_rescued(self, mock_run_stage, mock_classify, sim_dir): + fail_future = MagicMock() + fail_future.result.side_effect = RuntimeError("mysterious error") + mock_run_stage.return_value = fail_future + mock_classify.return_value = FailureType.UNKNOWN + + with pytest.raises(RuntimeError, match="mysterious error"): + execute_stage_with_rescue(sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3) + assert mock_run_stage.call_count == 1 + + @patch("mdfactory.orchestration.rescue.classify_failure") + @patch("mdfactory.orchestration.rescue.run_stage") + def test_rescue_logs_warning_with_tier_and_stage(self, mock_run_stage, mock_classify, sim_dir): + """Rescue activation emits WARNING log with tier number and stage name.""" + from loguru import logger + + fail_future = MagicMock() + fail_future.result.side_effect = RuntimeError("LINCS WARNING") + success_future = MagicMock() + success_future.result.return_value = None + mock_run_stage.side_effect = [fail_future, success_future] + mock_classify.return_value = FailureType.PHYSICS + + warnings = [] + handler_id = logger.add( + lambda msg: warnings.append(str(msg)), + level="WARNING", + format="{message}", + ) + try: + execute_stage_with_rescue(sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3) + finally: + logger.remove(handler_id) + + # Should have at least one warning mentioning rescue tier and stage + rescue_warnings = [w for w in warnings if "RESCUE" in w and "EM" in w] + assert len(rescue_warnings) >= 1 + # Check tier info is present + assert any("tier 1" in w for w in rescue_warnings) + + @patch("mdfactory.orchestration.rescue.run_stage") + def test_dependency_error_surfaces_root_cause(self, mock_run_stage, sim_dir): + """A grompp DependencyError is unwrapped to classify the real root cause. + + When grompp fails, mdrun receives a DependencyError with __cause__ + pointing to the actual grompp exception. The rescue loop must + unwrap this to classify correctly and show an actionable message. + """ + # Simulate Parsl DependencyError wrapping a grompp failure + root_cause = RuntimeError("Neither gmx nor gmx_mpi found in PATH") + dependency_error = RuntimeError( + "Dependency failure for task 5. The representative cause is via task 0" + ) + dependency_error.__cause__ = root_cause + + fail_future = MagicMock() + fail_future.result.side_effect = dependency_error + mock_run_stage.return_value = fail_future + + # Should raise the original DependencyError (not retry it), + # since "gmx not found" is UNKNOWN, not PHYSICS. + with pytest.raises(RuntimeError, match="Dependency failure"): + execute_stage_with_rescue(sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3) + # Should NOT retry — gmx-not-found is not a physics failure + assert mock_run_stage.call_count == 1 + + @patch("mdfactory.orchestration.rescue.run_stage") + def test_dependency_error_with_physics_root_is_rescued(self, mock_run_stage, sim_dir): + """A DependencyError wrapping a physics failure IS rescued.""" + root_cause = RuntimeError("LINCS WARNING: bonds to H are constrained") + dependency_error = RuntimeError("Dependency failure for task 5") + dependency_error.__cause__ = root_cause + + fail_future = MagicMock() + fail_future.result.side_effect = dependency_error + success_future = MagicMock() + success_future.result.return_value = None + mock_run_stage.side_effect = [fail_future, success_future] + + result = execute_stage_with_rescue( + sim_dir, "EM", None, MagicMock(), MagicMock(), max_rescue=3 + ) + assert result is success_future + assert mock_run_stage.call_count == 2 diff --git a/mdfactory/tests/test_orchestration_session.py b/mdfactory/tests/test_orchestration_session.py new file mode 100644 index 0000000..25a1fb2 --- /dev/null +++ b/mdfactory/tests/test_orchestration_session.py @@ -0,0 +1,71 @@ +# ABOUTME: Tests for the reusable Parsl session context manager +# ABOUTME: Validates DFK guard, load, shutdown, and detach semantics +"""Tests for orchestration Parsl session management.""" + +from unittest.mock import MagicMock + +import pytest + +parsl = pytest.importorskip("parsl", reason="parsl not installed") + +from mdfactory.orchestration.config import ExecutorConfig # noqa: E402 +from mdfactory.orchestration.session import ( # noqa: E402 + ParslSession, + parsl_session, +) + + +def _patch_parsl(monkeypatch, *, active_dfk=False): + """Patch parsl.load/clear/dfk for session tests.""" + monkeypatch.setattr(parsl, "load", MagicMock()) + monkeypatch.setattr(parsl, "clear", MagicMock()) + if active_dfk: + monkeypatch.setattr(parsl, "dfk", MagicMock(return_value=MagicMock(executors={}))) + else: + monkeypatch.setattr(parsl, "dfk", MagicMock(side_effect=RuntimeError("No DFK"))) + monkeypatch.setattr(ExecutorConfig, "to_parsl_config", lambda self: MagicMock()) + + +def test_parsl_session_loads_and_shuts_down(monkeypatch): + """parsl_session loads the config and clears the DFK on exit.""" + _patch_parsl(monkeypatch) + + with parsl_session(ExecutorConfig()) as session: + assert isinstance(session, ParslSession) + assert session.detached is False + + parsl.load.assert_called_once() + parsl.clear.assert_called_once() + + +def test_parsl_session_detach_skips_shutdown(monkeypatch): + """A detached session does not shut down the DFK on exit.""" + _patch_parsl(monkeypatch) + + with parsl_session(ExecutorConfig()) as session: + session.detach() + + parsl.load.assert_called_once() + parsl.clear.assert_not_called() + + +def test_parsl_session_guards_active_dfk(monkeypatch): + """parsl_session raises if a DFK is already active and does not load.""" + _patch_parsl(monkeypatch, active_dfk=True) + + with pytest.raises(RuntimeError, match="already active"): + with parsl_session(ExecutorConfig()): + pass + + parsl.load.assert_not_called() + + +def test_parsl_session_shuts_down_on_exception(monkeypatch): + """parsl_session clears the DFK even when the body raises.""" + _patch_parsl(monkeypatch) + + with pytest.raises(ValueError, match="boom"): + with parsl_session(ExecutorConfig()): + raise ValueError("boom") + + parsl.clear.assert_called_once() diff --git a/mdfactory/tests/test_orchestration_simulate.py b/mdfactory/tests/test_orchestration_simulate.py new file mode 100644 index 0000000..8f42844 --- /dev/null +++ b/mdfactory/tests/test_orchestration_simulate.py @@ -0,0 +1,2106 @@ +# ABOUTME: Unit tests for Parsl-based GROMACS simulation orchestration +# ABOUTME: Tests checkpoint detection, validation, and dry-run mode +"""Unit tests for simulation orchestration.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from mdfactory.orchestration.apps import ( + _assemble_mdrun_command, + _build_mdrun_script, + _resolve_binary_token, + _resolve_gpu_flags, + _resolve_is_mpi, + _resolve_restart_flags, + _resolve_thread_count_expr, + _resolve_thread_flags, +) +from mdfactory.orchestration.config import ExecutorConfig +from mdfactory.orchestration.simulate import ( + _detect_needed_stages, + _execute_stage_list, + _log_dry_run_plan, + _missing_build_files, + _validate_stage_prerequisites, + _validate_trajectory_complete, + run_simulations, +) + + +@pytest.fixture +def mock_sim_dir(tmp_path): + """Create mock simulation directory with required files.""" + sim_dir = tmp_path / "abc123" + sim_dir.mkdir() + + # Required files + (sim_dir / "system.pdb").write_text("FAKE PDB") + (sim_dir / "topology.top").write_text("FAKE TOP") + (sim_dir / "em.mdp").write_text("FAKE MDP") + (sim_dir / "nvt.mdp").write_text("FAKE MDP") + (sim_dir / "npt.mdp").write_text("FAKE MDP") + (sim_dir / "md.mdp").write_text("FAKE MDP") + + return sim_dir + + +# --------------------------------------------------------------------------- +# _missing_build_files +# --------------------------------------------------------------------------- + + +def test_missing_build_files_complete(mock_sim_dir): + """Returns empty list when all required files are present.""" + assert _missing_build_files(mock_sim_dir, ["EM", "NVT", "NPT", "Production"]) == [] + + +def test_missing_build_files_empty_dir(tmp_path): + """Returns all required files when directory is empty.""" + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + missing = _missing_build_files(sim_dir, ["EM"]) + assert "system.pdb" in missing + assert "topology.top" in missing + assert "em.mdp" in missing + + +def test_missing_build_files_partial(mock_sim_dir): + """Returns only the missing file when one is removed.""" + (mock_sim_dir / "npt.mdp").unlink() + missing = _missing_build_files(mock_sim_dir, ["EM", "NVT", "NPT", "Production"]) + assert missing == ["npt.mdp"] + + +# --------------------------------------------------------------------------- +# run_simulations — incomplete-build filtering +# --------------------------------------------------------------------------- + + +def test_run_simulations_skips_incomplete_build(tmp_path): + """run_simulations warns and skips dirs with missing build outputs. + + Skipped dirs appear in the results as status='skipped' entries; only the + ready dir appears in the dry-run work-plan portion of the results. + """ + ready = tmp_path / "ready" + ready.mkdir() + for f in ["system.pdb", "topology.top", "em.mdp", "nvt.mdp", "npt.mdp", "md.mdp"]: + (ready / f).write_text("FAKE") + + incomplete = tmp_path / "incomplete" + incomplete.mkdir() + # No files written — build never completed + + config = ExecutorConfig() + results = run_simulations([ready, incomplete], config, dry_run=True) + + # The incomplete dir is reported as skipped, not silently dropped. + skipped = [r for r in results if r.get("status") == "skipped"] + skipped_hashes = [r["hash"] for r in skipped] + assert "incomplete" in skipped_hashes + + # The ready dir appears in the work-plan portion (has a sim_dir key). + plan_items = [r for r in results if "sim_dir" in r] + plan_dirs = [item["sim_dir"] for item in plan_items] + assert ready in plan_dirs + assert incomplete not in plan_dirs + + +def test_run_simulations_all_incomplete_returns_skipped_entries(tmp_path): + """run_simulations returns skipped entries (not []) when no directories are ready.""" + empty = tmp_path / "empty" + empty.mkdir() + + config = ExecutorConfig() + results = run_simulations([empty], config, dry_run=True) + assert len(results) == 1 + assert results[0]["status"] == "skipped" + assert results[0]["hash"] == "empty" + + +def test_checkpoint_auto_skips_completed(mock_sim_dir): + """Checkpoint mode auto skips stages with outputs.""" + (mock_sim_dir / "min.gro").write_text("FAKE OUTPUT") + + needed = _detect_needed_stages(mock_sim_dir, ["EM", "NVT"], "auto") + assert "EM" not in needed + assert "NVT" in needed + + +def test_checkpoint_auto_skips_empty_file(mock_sim_dir): + """Checkpoint mode auto does not skip empty files.""" + (mock_sim_dir / "min.gro").write_text("") # Empty file + + needed = _detect_needed_stages(mock_sim_dir, ["EM"], "auto") + assert "EM" in needed + + +def test_checkpoint_skip_always_skips(mock_sim_dir): + """Checkpoint mode skip always skips if file exists.""" + (mock_sim_dir / "min.gro").write_text("") # Empty file + + needed = _detect_needed_stages(mock_sim_dir, ["EM"], "skip") + assert "EM" not in needed + + +def test_checkpoint_force_never_skips(mock_sim_dir): + """Checkpoint mode force reruns everything.""" + (mock_sim_dir / "min.gro").write_text("FAKE OUTPUT") + + needed = _detect_needed_stages(mock_sim_dir, ["EM"], "force") + assert "EM" in needed + + +@patch("mdfactory.orchestration.simulate._validate_trajectory_complete", return_value=True) +def test_checkpoint_all_stages_complete(mock_validate, mock_sim_dir): + """All stages skipped when outputs AND prerequisite checkpoints exist.""" + (mock_sim_dir / "min.gro").write_text("FAKE") + (mock_sim_dir / "nvt.gro").write_text("FAKE") + (mock_sim_dir / "nvt.cpt").write_text("FAKE CPT") # NPT needs this + (mock_sim_dir / "npt.gro").write_text("FAKE") + (mock_sim_dir / "npt.cpt").write_text("FAKE CPT") # Production needs this + (mock_sim_dir / "prod.xtc").write_text("FAKE") # Trajectory present + + needed = _detect_needed_stages(mock_sim_dir, ["EM", "NVT", "NPT", "Production"], "auto") + assert len(needed) == 0 + + +def test_dry_run_no_parsl_load(mock_sim_dir): + """Dry-run logs plan without loading Parsl.""" + results = run_simulations([mock_sim_dir], ExecutorConfig(), dry_run=True) + + assert len(results) == 1 + assert results[0]["hash"] == "abc123" + assert results[0]["stages"] == ["EM", "NVT", "NPT", "Production"] + + +def test_dry_run_with_checkpoint(mock_sim_dir): + """Dry-run respects checkpoint detection.""" + (mock_sim_dir / "min.gro").write_text("FAKE OUTPUT") + (mock_sim_dir / "nvt.gro").write_text("FAKE OUTPUT") + + results = run_simulations( + [mock_sim_dir], ExecutorConfig(), checkpoint_mode="auto", dry_run=True + ) + + # EM and NVT should be skipped + assert results[0]["stages"] == ["NPT", "Production"] + + +def test_log_dry_run_plan_format(mock_sim_dir): + """Dry-run plan includes expected fields.""" + work_plan = [ + { + "sim_dir": mock_sim_dir, + "hash": "abc123", + "stages": ["EM", "NVT"], + } + ] + + config = ExecutorConfig() + result = _log_dry_run_plan(work_plan, config) + + assert result == work_plan + + +@patch("mdfactory.orchestration.progress.display_stage_progress") +@patch("mdfactory.orchestration.simulate.parsl_session") +@patch("mdfactory.orchestration.simulate._execute_stage_list") +def test_parallel_submission(mock_execute, mock_session, mock_display, tmp_path): + """Multiple simulations submit in parallel.""" + # Create 10 mock simulations + sim_dirs = [] + for i in range(10): + sim_dir = tmp_path / f"sim{i:03d}" + sim_dir.mkdir() + (sim_dir / "system.pdb").write_text("FAKE") + (sim_dir / "topology.top").write_text("FAKE") + for mdp in ["em", "nvt", "npt", "md"]: + (sim_dir / f"{mdp}.mdp").write_text("FAKE") + sim_dirs.append(sim_dir) + + # Mock execute_stage_list returns future + mock_future = MagicMock() + mock_future.result.return_value = {"status": "success"} + mock_future.done.return_value = True + mock_execute.return_value = mock_future + + # Mock session context + mock_session.return_value.__enter__.return_value = MagicMock() + + # Run + run_simulations(sim_dirs, ExecutorConfig(), stages=["EM", "NVT", "NPT", "Production"]) + + # Verify 10 stage lists executed + assert mock_execute.call_count == 10 + + +def test_stage_filtering(mock_sim_dir): + """Only requested stages are included in work plan.""" + results = run_simulations( + [mock_sim_dir], + ExecutorConfig(), + stages=["EM", "NVT"], + dry_run=True, + ) + + # Plan should only include EM and NVT + assert results[0]["stages"] == ["EM", "NVT"] + + +def test_validation_before_submission(tmp_path): + """Incomplete builds are skipped before Parsl submission with a skipped entry.""" + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "system.pdb").write_text("FAKE") + # Missing topology.top and all MDP files — build never completed + + # Should not raise; returns a skipped entry (not []) so callers can account + # for the skipped directory. + results = run_simulations([sim_dir], ExecutorConfig(), dry_run=True) + assert len(results) == 1 + assert results[0]["status"] == "skipped" + + +def test_multiple_simulations_mixed_states(tmp_path): + """Handle multiple simulations in different completion states.""" + # Sim 1: nothing done + sim1 = tmp_path / "sim1" + sim1.mkdir() + for f in ["system.pdb", "topology.top", "em.mdp", "nvt.mdp", "npt.mdp", "md.mdp"]: + (sim1 / f).write_text("FAKE") + + # Sim 2: EM complete + sim2 = tmp_path / "sim2" + sim2.mkdir() + for f in ["system.pdb", "topology.top", "em.mdp", "nvt.mdp", "npt.mdp", "md.mdp"]: + (sim2 / f).write_text("FAKE") + (sim2 / "min.gro").write_text("FAKE") + + # Sim 3: all complete (with prerequisite checkpoints) + sim3 = tmp_path / "sim3" + sim3.mkdir() + for f in ["system.pdb", "topology.top", "em.mdp", "nvt.mdp", "npt.mdp", "md.mdp"]: + (sim3 / f).write_text("FAKE") + (sim3 / "min.gro").write_text("FAKE") + (sim3 / "nvt.gro").write_text("FAKE") + (sim3 / "nvt.cpt").write_text("FAKE CPT") # NPT prerequisite + (sim3 / "npt.gro").write_text("FAKE") + (sim3 / "npt.cpt").write_text("FAKE CPT") # Production prerequisite + (sim3 / "prod.xtc").write_text("FAKE") # Trajectory present + + # Mock trajectory validation so fake XTC counts as complete + with patch("mdfactory.orchestration.simulate._validate_trajectory_complete", return_value=True): + results = run_simulations([sim1, sim2, sim3], ExecutorConfig(), dry_run=True) + + assert len(results) == 3 + assert results[0]["stages"] == ["EM", "NVT", "NPT", "Production"] + assert results[1]["stages"] == ["NVT", "NPT", "Production"] + assert results[2]["stages"] == [] + + +@patch("mdfactory.orchestration.simulate._validate_trajectory_complete", return_value=True) +def test_checkpoint_production_output(mock_validate, mock_sim_dir): + """Production stage skipped when prod.xtc is validated as complete.""" + (mock_sim_dir / "min.gro").write_text("FAKE") + (mock_sim_dir / "nvt.gro").write_text("FAKE") + (mock_sim_dir / "npt.gro").write_text("FAKE") + (mock_sim_dir / "npt.cpt").write_text("FAKE CPT") # Production prerequisite + (mock_sim_dir / "prod.xtc").write_text("FAKE") # Trajectory present + + needed = _detect_needed_stages(mock_sim_dir, ["Production"], "auto") + assert "Production" not in needed + + +def test_stage_order_preservation(mock_sim_dir): + """Requested stages maintain order.""" + needed = _detect_needed_stages(mock_sim_dir, ["Production", "NPT", "EM", "NVT"], "force") + assert needed == ["Production", "NPT", "EM", "NVT"] + + +@patch("mdfactory.orchestration.simulate.parsl_session") +@patch("mdfactory.orchestration.simulate._execute_stage_list") +def test_wait_false_returns_futures(mock_execute, mock_session, mock_sim_dir): + """When wait=False, returns raw futures.""" + mock_future = MagicMock() + mock_execute.return_value = mock_future + + mock_session_obj = MagicMock() + mock_session.return_value.__enter__.return_value = mock_session_obj + + results = run_simulations([mock_sim_dir], ExecutorConfig(), wait=False) + + # Should return futures, not results + assert results == [mock_future] + # Session should be detached + mock_session_obj.detach.assert_called_once() + + +@patch("mdfactory.orchestration.simulate.parsl_session") +@patch("mdfactory.orchestration.simulate._execute_stage_list") +def test_wait_false_emits_warning(mock_execute, mock_session, mock_sim_dir): + """wait=False logs a warning that raw futures resolve to None, not dicts.""" + mock_execute.return_value = MagicMock() + mock_session.return_value.__enter__.return_value = MagicMock() + + with patch("mdfactory.orchestration.simulate.logger") as mock_logger: + run_simulations([mock_sim_dir], ExecutorConfig(), wait=False) + + # Warning must be emitted and mention the None / parsl.clear() contract + mock_logger.warning.assert_called() + warning_text = " ".join(str(a) for call in mock_logger.warning.call_args_list for a in call[0]) + assert "None" in warning_text or "raw" in warning_text + assert "parsl.clear()" in warning_text + + +def test_empty_sim_list(): + """Handle empty simulation list gracefully.""" + results = run_simulations([], ExecutorConfig(), dry_run=True) + assert results == [] + + +# === Decision 2: Strict Checkpoint Validation Tests === + + +def test_checkpoint_auto_requires_cpt_files(mock_sim_dir): + """NPT requires both npt.gro AND nvt.cpt for auto mode.""" + # Create npt.gro but NOT nvt.cpt + (mock_sim_dir / "min.gro").write_text("FAKE") + (mock_sim_dir / "nvt.gro").write_text("FAKE") + (mock_sim_dir / "npt.gro").write_text("FAKE") + # nvt.cpt is missing! + + needed = _detect_needed_stages(mock_sim_dir, ["EM", "NVT", "NPT"], "auto") + + # NPT should still be needed because nvt.cpt is missing + assert "EM" not in needed # min.gro exists + assert "NVT" not in needed # nvt.gro exists + assert "NPT" in needed # nvt.cpt missing + + +def test_checkpoint_auto_skips_with_all_files(mock_sim_dir): + """NPT skipped when both npt.gro AND nvt.cpt exist.""" + (mock_sim_dir / "min.gro").write_text("FAKE") + (mock_sim_dir / "nvt.gro").write_text("FAKE") + (mock_sim_dir / "nvt.cpt").write_text("FAKE CPT") + (mock_sim_dir / "npt.gro").write_text("FAKE") + + needed = _detect_needed_stages(mock_sim_dir, ["NPT"], "auto") + + assert "NPT" not in needed # Both npt.gro and nvt.cpt exist + + +def test_checkpoint_production_requires_npt_cpt(mock_sim_dir): + """Production requires prod.xtc AND npt.cpt.""" + (mock_sim_dir / "prod.xtc").write_text("FAKE TRAJ") + # npt.cpt is missing! + + needed = _detect_needed_stages(mock_sim_dir, ["Production"], "auto") + + # Production should be needed because npt.cpt is missing + assert "Production" in needed + + +@patch("mdfactory.orchestration.simulate._validate_trajectory_complete", return_value=True) +def test_checkpoint_production_skips_with_all_files(mock_validate, mock_sim_dir): + """Production skipped when both prod.xtc AND npt.cpt exist (trajectory validated).""" + (mock_sim_dir / "npt.cpt").write_text("FAKE CPT") + (mock_sim_dir / "prod.xtc").write_text("FAKE") # Trajectory present + + needed = _detect_needed_stages(mock_sim_dir, ["Production"], "auto") + + assert "Production" not in needed + + +@patch("mdfactory.orchestration.simulate._validate_trajectory_complete", return_value=True) +def test_checkpoint_production_complete_with_trr_only(mock_validate, mock_sim_dir): + """Production marked complete when only prod.trr exists (TRR-only MDP config).""" + (mock_sim_dir / "npt.cpt").write_text("FAKE CPT") + # XTC absent; TRR present and mocked as validated + (mock_sim_dir / "prod.trr").write_text("FAKE") + + needed = _detect_needed_stages(mock_sim_dir, ["Production"], "auto") + + assert "Production" not in needed + + +def test_checkpoint_production_partial_with_neither_traj(mock_sim_dir): + """Production is partial (not complete) when neither prod.xtc nor prod.trr exists.""" + (mock_sim_dir / "npt.cpt").write_text("FAKE CPT") + (mock_sim_dir / "prod.cpt").write_text("FAKE CPT") + (mock_sim_dir / "prod.tpr").write_text("FAKE TPR") + + needed = _detect_needed_stages(mock_sim_dir, ["Production"], "auto") + + assert "Production" in needed + + +def test_validate_prerequisites_em_no_requirements(): + """EM has no prerequisites.""" + # EM doesn't need any input files (uses system.pdb which is validated elsewhere) + from pathlib import Path + + sim_dir = Path("/tmp/fake") + _validate_stage_prerequisites(sim_dir, "EM") # Should not raise + + +def test_validate_prerequisites_nvt_requires_min_gro(mock_sim_dir): + """NVT requires min.gro from EM.""" + # min.gro is missing + with pytest.raises(FileNotFoundError, match="Cannot start NVT.*min.gro"): + _validate_stage_prerequisites(mock_sim_dir, "NVT") + + +def test_validate_prerequisites_nvt_succeeds_with_min_gro(mock_sim_dir): + """NVT validation passes when min.gro exists.""" + (mock_sim_dir / "min.gro").write_text("FAKE") + _validate_stage_prerequisites(mock_sim_dir, "NVT") # Should not raise + + +def test_validate_prerequisites_npt_requires_nvt_files(mock_sim_dir): + """NPT requires both nvt.gro AND nvt.cpt.""" + # Only create nvt.gro, not nvt.cpt + (mock_sim_dir / "nvt.gro").write_text("FAKE") + + with pytest.raises(FileNotFoundError, match="Cannot start NPT.*nvt.cpt"): + _validate_stage_prerequisites(mock_sim_dir, "NPT") + + +def test_validate_prerequisites_npt_succeeds_with_all_files(mock_sim_dir): + """NPT validation passes when nvt.gro AND nvt.cpt exist.""" + (mock_sim_dir / "nvt.gro").write_text("FAKE") + (mock_sim_dir / "nvt.cpt").write_text("FAKE CPT") + _validate_stage_prerequisites(mock_sim_dir, "NPT") # Should not raise + + +def test_validate_prerequisites_production_requires_npt_files(mock_sim_dir): + """Production requires both npt.gro AND npt.cpt.""" + # Only create npt.gro, not npt.cpt + (mock_sim_dir / "npt.gro").write_text("FAKE") + + with pytest.raises(FileNotFoundError, match="Cannot start Production.*npt.cpt"): + _validate_stage_prerequisites(mock_sim_dir, "Production") + + +def test_validate_prerequisites_production_succeeds_with_all_files(mock_sim_dir): + """Production validation passes when npt.gro AND npt.cpt exist.""" + (mock_sim_dir / "npt.gro").write_text("FAKE") + (mock_sim_dir / "npt.cpt").write_text("FAKE CPT") + _validate_stage_prerequisites(mock_sim_dir, "Production") # Should not raise + + +def test_validate_prerequisites_error_message_includes_fix(mock_sim_dir): + """Error message includes actionable fix commands.""" + with pytest.raises(FileNotFoundError) as exc_info: + _validate_stage_prerequisites(mock_sim_dir, "NPT") + + error_msg = str(exc_info.value) + assert "mdfactory simulate" in error_msg # Fix command included + assert "--stages EM NVT" in error_msg # Suggests running earlier stages + assert "--checkpoint force" in error_msg # Alternative fix + + +@patch("mdfactory.orchestration.simulate.parsl_session") +def test_prerequisite_validation_before_parsl_session(mock_session, mock_sim_dir): + """Prerequisites validated before Parsl session starts.""" + # Create incomplete state: NVT complete but missing nvt.cpt (needed for NPT) + (mock_sim_dir / "min.gro").write_text("FAKE") + (mock_sim_dir / "nvt.gro").write_text("FAKE") + # nvt.cpt is missing! + + # Try to run NPT (which requires nvt.cpt) + with pytest.raises(FileNotFoundError, match="Cannot start NPT"): + run_simulations( + [mock_sim_dir], + ExecutorConfig(), + stages=["NPT", "Production"], + checkpoint_mode="force", # Force to ensure NPT is in work plan + ) + + # Parsl session should NOT have been started + mock_session.assert_not_called() + + +# === Decision 3: Resource Auto-Detection Tests === +# Tests refactored to target resolver functions directly (pure Python, no bash parsing). + + +def test_resolve_thread_count_expr_auto(): + """Auto thread count uses three-level SLURM > OMP > nproc fallback.""" + expr = _resolve_thread_count_expr(0) + assert expr == "${SLURM_CPUS_PER_TASK:-${OMP_NUM_THREADS:-$(nproc)}}" + + +def test_resolve_thread_count_expr_explicit(): + """Explicit ntasks produces a literal decimal string.""" + assert _resolve_thread_count_expr(8) == "8" + assert _resolve_thread_count_expr(12) == "12" + + +def test_resolve_thread_count_expr_nproc_fallback(): + """Three-level fallback uses nproc (not a fixed number) as the last resort. + + Guards against accidental regression to the old two-level form + ``${SLURM_CPUS_PER_TASK:-$(nproc)}``, which ignores OMP_NUM_THREADS + and would cause a thread-count mismatch in Parsl worker environments. + """ + expr = _resolve_thread_count_expr(0) + # All three levels must be present in the correct order + assert "SLURM_CPUS_PER_TASK" in expr + assert "OMP_NUM_THREADS" in expr + assert "$(nproc)" in expr + assert expr.index("SLURM_CPUS_PER_TASK") < expr.index("OMP_NUM_THREADS") < expr.index("nproc") + + +def test_resolve_gpu_flags_gpu_and_pme_gpu(): + """GPU mode with PME-on-GPU emits correct GPU flags.""" + flags = _resolve_gpu_flags(has_gpu=True, pme_gpu=True) + assert flags == "-nb gpu -pme gpu -gpu_id $GPU_ID" + + +def test_resolve_gpu_flags_gpu_no_pme(): + """GPU mode with PME-on-CPU (e.g. EM) emits -pme cpu.""" + flags = _resolve_gpu_flags(has_gpu=True, pme_gpu=False) + assert flags == "-nb gpu -pme cpu -gpu_id $GPU_ID" + + +def test_resolve_gpu_flags_no_gpu(): + """CPU-only mode emits an empty string.""" + assert _resolve_gpu_flags(has_gpu=False, pme_gpu=True) == "" + assert _resolve_gpu_flags(has_gpu=False, pme_gpu=False) == "" + + +def test_resolve_is_mpi(): + """Binary selector resolves to expected is_mpi values.""" + assert _resolve_is_mpi("gmx_mpi") is True + assert _resolve_is_mpi("gmx") is False + assert _resolve_is_mpi("auto") is None + + +def test_resolve_thread_flags_explicit_mpi(): + """MPI build always uses -ntomp regardless of GPU mode.""" + assert _resolve_thread_flags(is_mpi=True, has_gpu=True) == "-ntomp $NTHR" + assert _resolve_thread_flags(is_mpi=True, has_gpu=False) == "-ntomp $NTHR" + + +def test_resolve_thread_flags_tmpi_gpu(): + """Thread-MPI build with GPU uses -ntmpi 1 -ntomp.""" + assert _resolve_thread_flags(is_mpi=False, has_gpu=True) == "-ntmpi 1 -ntomp $NTHR" + + +def test_resolve_thread_flags_tmpi_cpu(): + """Thread-MPI build without GPU uses -nt (all-thread count).""" + assert _resolve_thread_flags(is_mpi=False, has_gpu=False) == "-nt $NTHR" + + +def test_resolve_thread_flags_auto(): + """Auto mode defers to shell variable $MDRUN_THREAD_FLAGS.""" + assert _resolve_thread_flags(is_mpi=None, has_gpu=True) == "$MDRUN_THREAD_FLAGS" + assert _resolve_thread_flags(is_mpi=None, has_gpu=False) == "$MDRUN_THREAD_FLAGS" + + +def test_resolve_binary_token(): + """Binary token resolver returns expected strings.""" + assert _resolve_binary_token("gmx") == "gmx" + assert _resolve_binary_token("gmx_mpi") == "gmx_mpi" + assert _resolve_binary_token("auto") == "$GMX_BIN" + + +def test_assemble_mdrun_command_gpu_tmpi(): + """Assembler produces correct GPU + thread-MPI command.""" + cmd = _assemble_mdrun_command( + "gmx", + "prod", + "-cpi prod.cpt -append", + "-ntmpi 1 -ntomp $NTHR", + "-nb gpu -pme gpu -gpu_id $GPU_ID", + ) + assert cmd == ( + "gmx mdrun -deffnm prod -cpi prod.cpt -append " + "-ntmpi 1 -ntomp $NTHR -nb gpu -pme gpu -gpu_id $GPU_ID" + ) + + +def test_assemble_mdrun_command_cpu_mpi_no_restart(): + """Assembler skips empty parts — no duplicate spaces.""" + cmd = _assemble_mdrun_command("gmx_mpi", "min", "", "-ntomp $NTHR", "") + assert cmd == "gmx_mpi mdrun -deffnm min -ntomp $NTHR" + assert " " not in cmd # no double spaces + + +def test_mdrun_app_logging(): + """Mdrun bash app logs starting message to stderr.""" + bash_script = _build_mdrun_script(deffnm="test", work_dir="/tmp/test") + + # Starting message is always present + assert 'echo "GROMACS mdrun: Starting test with $NTHR threads' in bash_script + assert ">&2" in bash_script # Logs to stderr + + +def test_mdrun_app_slurm_priority(): + """SLURM_CPUS_PER_TASK has priority over OMP_NUM_THREADS in the fallback chain.""" + expr = _resolve_thread_count_expr(0) + # Three-level form; SLURM_CPUS_PER_TASK is checked before OMP_NUM_THREADS + assert "SLURM_CPUS_PER_TASK" in expr + assert "OMP_NUM_THREADS" in expr + assert expr.index("SLURM_CPUS_PER_TASK") < expr.index("OMP_NUM_THREADS") + + +def test_stage_functions_no_hardcoded_nt(): + """run_stage doesn't pass hardcoded nt parameter to mdrun.""" + import inspect + + from mdfactory.orchestration.stages import run_stage + + source = inspect.getsource(run_stage) + assert "nt=12" not in source, "run_stage still has hardcoded nt=12" + assert "nt=24" not in source, "run_stage has hardcoded nt" + + +# === Decision 1: Explicit Stage Execution Tests === + + +def test_execute_stage_list_empty(): + """Execute stage list returns None when no stages provided.""" + result = _execute_stage_list(Path("/tmp/test"), [], None, None, max_rescue=0) + assert result is None + + +def test_execute_stage_list_validates_order(): + """Execute stage list validates stages are in dependency order.""" + with pytest.raises(ValueError, match="must be in dependency order"): + # NPT before NVT is invalid + _execute_stage_list(Path("/tmp/test"), ["NPT", "NVT"], None, None, max_rescue=0) + + +def test_execute_stage_list_validates_unknown_stage(): + """Execute stage list rejects unknown stage names.""" + with pytest.raises(ValueError, match="Unknown stage.*Equilibration"): + _execute_stage_list(Path("/tmp/test"), ["Equilibration"], None, None, max_rescue=0) + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_em_only(mock_run_stage): + """Execute stage list dispatches EM via run_stage with correct spec and no prev_future.""" + mock_future = MagicMock() + mock_run_stage.return_value = mock_future + + result = _execute_stage_list(Path("/tmp/test"), ["EM"], MagicMock(), MagicMock(), max_rescue=0) + + mock_run_stage.assert_called_once() + call_args = mock_run_stage.call_args[0] + assert call_args[0].name == "EM" # spec + assert call_args[2] is None # prev_future = None for first stage + assert result == mock_future + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_em_nvt_chain(mock_run_stage): + """Execute stage list chains EM → NVT: NVT receives EM's future as prev_future.""" + em_future = MagicMock() + nvt_future = MagicMock() + mock_run_stage.side_effect = [em_future, nvt_future] + + grompp_app = MagicMock() + mdrun_app = MagicMock() + sim_dir = Path("/tmp/test") + + result = _execute_stage_list(sim_dir, ["EM", "NVT"], grompp_app, mdrun_app, max_rescue=0) + + assert mock_run_stage.call_count == 2 + calls = mock_run_stage.call_args_list + # First call: EM, prev_future=None + assert calls[0][0][0].name == "EM" + assert calls[0][0][2] is None + # Second call: NVT, prev_future=em_future (chained) + assert calls[1][0][0].name == "NVT" + assert calls[1][0][2] is em_future + assert result == nvt_future + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_full_pipeline(mock_run_stage): + """Execute stage list chains all 4 stages via run_stage with correct futures.""" + stage_futures = [MagicMock() for _ in range(4)] + mock_run_stage.side_effect = stage_futures + + grompp_app = MagicMock() + mdrun_app = MagicMock() + sim_dir = Path("/tmp/test") + + result = _execute_stage_list( + sim_dir, ["EM", "NVT", "NPT", "Production"], grompp_app, mdrun_app, max_rescue=0 + ) + + assert mock_run_stage.call_count == 4 + calls = mock_run_stage.call_args_list + # Verify spec names and future chaining + assert calls[0][0][0].name == "EM" + assert calls[0][0][2] is None # no prev for first stage + assert calls[1][0][0].name == "NVT" + assert calls[1][0][2] is stage_futures[0] + assert calls[2][0][0].name == "NPT" + assert calls[2][0][2] is stage_futures[1] + assert calls[3][0][0].name == "Production" + assert calls[3][0][2] is stage_futures[2] + assert result is stage_futures[3] + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_partial_pipeline_from_checkpoint(mock_run_stage): + """Checkpoint resume: first stage is not EM; prev_future starts as None.""" + nvt_fut = MagicMock() + npt_fut = MagicMock() + mock_run_stage.side_effect = [nvt_fut, npt_fut] + + grompp_app = MagicMock() + mdrun_app = MagicMock() + sim_dir = Path("/tmp/test") + + result = _execute_stage_list(sim_dir, ["NVT", "NPT"], grompp_app, mdrun_app, max_rescue=0) + + calls = mock_run_stage.call_args_list + assert calls[0][0][0].name == "NVT" + assert calls[0][0][2] is None # no preceding future + assert calls[1][0][0].name == "NPT" + assert calls[1][0][2] is nvt_fut # chained + assert result is npt_fut + + +@patch("mdfactory.orchestration.rescue.execute_stage_with_rescue") +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_routes_to_rescue_when_enabled(mock_run_stage, mock_rescue): + """Rescue-eligible stages dispatch to execute_stage_with_rescue when max_rescue > 0.""" + rescue_future = MagicMock() + mock_rescue.return_value = rescue_future + sim_dir = Path("/tmp/test") + + result = _execute_stage_list(sim_dir, ["EM"], MagicMock(), MagicMock(), max_rescue=3) + + mock_rescue.assert_called_once() + mock_run_stage.assert_not_called() + assert result is rescue_future + + +@patch("mdfactory.orchestration.rescue.execute_stage_with_rescue") +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_production_bypasses_rescue(mock_run_stage, mock_rescue): + """Production stage uses run_stage even when max_rescue > 0.""" + prod_future = MagicMock() + mock_run_stage.return_value = prod_future + sim_dir = Path("/tmp/test") + + result = _execute_stage_list(sim_dir, ["Production"], MagicMock(), MagicMock(), max_rescue=3) + + mock_run_stage.assert_called_once() + mock_rescue.assert_not_called() + assert result is prod_future + + +@patch("mdfactory.orchestration.progress.display_stage_progress") +@patch("mdfactory.orchestration.simulate.parsl_session") +@patch("mdfactory.orchestration.simulate._execute_stage_list") +def test_run_simulations_uses_execute_stage_list( + mock_execute, mock_session, mock_display, mock_sim_dir +): + """run_simulations dispatcher now uses _execute_stage_list.""" + mock_future = MagicMock() + mock_future.done.return_value = True + mock_future.result.return_value = {"status": "success"} + mock_execute.return_value = mock_future + + mock_session.return_value.__enter__.return_value = MagicMock() + + # Create partial checkpoint state (EM complete) + (mock_sim_dir / "min.gro").write_text("FAKE") + + run_simulations([mock_sim_dir], ExecutorConfig(), checkpoint_mode="auto") + + # Should call _execute_stage_list with only needed stages + mock_execute.assert_called_once() + call_args = mock_execute.call_args + # Second argument is the stages list + stages_arg = call_args[0][1] + # EM should be skipped, others needed + assert "EM" not in stages_arg + assert "NVT" in stages_arg + + +# === Decision 4: GROMACS Error Handling Tests === + + +def test_grompp_app_has_error_handling(): + """Grompp bash app includes robust error handling.""" + from mdfactory.orchestration.apps import _build_grompp_script + + bash_script = _build_grompp_script( + mdp_file="em.mdp", + gro_file="system.pdb", + top_file="topology.top", + tpr_file="min.tpr", + work_dir="/tmp/test", + ) + + # Should have bash strict mode + assert "set -euo pipefail" in bash_script + # Should check grompp exit code (with dynamic binary) + assert "if ! $GMX_BIN grompp" in bash_script + # Should verify TPR was created + assert "if [ ! -f min.tpr ]" in bash_script + # Should log errors to stderr + assert ">&2" in bash_script + + +def test_grompp_app_logs_descriptive_errors(): + """Grompp error messages include actionable guidance.""" + from mdfactory.orchestration.apps import _build_grompp_script + + bash_script = _build_grompp_script( + mdp_file="em.mdp", + gro_file="system.pdb", + top_file="topology.top", + tpr_file="min.tpr", + work_dir="/tmp/test", + ) + + # Error message should mention the failed file + assert "ERROR: grompp failed for min.tpr" in bash_script + # Should suggest checking inputs + assert "Check that input files" in bash_script + + +def test_mdrun_app_has_error_handling(): + """Mdrun bash app uses strict mode and spec-driven output verification. + + The new implementation relies on ``set -euo pipefail`` (not inline + ``if ! mdrun ...``) to propagate mdrun errors, and uses a single + unconditional mdrun command with no surrounding if/then/fi wrapper. + """ + # Pass gro_out so output-verification block is included + bash_script = _build_mdrun_script(deffnm="min", work_dir="/tmp/test", gro_out="min.gro") + + # bash strict mode is present + assert "set -euo pipefail" in bash_script + # Output check is present (spec-driven, not inline command check) + assert 'if [ ! -f "min.gro" ]' in bash_script + # No 4-branch if/then/fi command wrappers — mdrun appears as a single line + assert bash_script.count("if ! $GMX_BIN mdrun") == 0 + # The mdrun command is on exactly one line (no surrounding if/fi) + mdrun_lines = [ln for ln in bash_script.splitlines() if "mdrun -deffnm" in ln] + assert len(mdrun_lines) == 1, f"Expected 1 mdrun command line, found: {mdrun_lines}" + + +def test_mdrun_app_verifies_stage_specific_outputs(): + """Mdrun verifies correct output file for each stage (spec-driven, no deffnm comparisons).""" + # EM stage: spec passes gro_out="min.gro" + em_script = _build_mdrun_script(deffnm="min", work_dir="/tmp/test", gro_out="min.gro") + assert "min.gro" in em_script + assert 'deffnm" == "min"' not in em_script # no hardcoded deffnm comparison + + # Production: spec passes traj_files + prod_script = _build_mdrun_script( + deffnm="prod", work_dir="/tmp/test", traj_files=("prod.xtc", "prod.trr") + ) + assert "prod.xtc" in prod_script + assert "prod.trr" in prod_script + assert 'deffnm" == "prod"' not in prod_script # no hardcoded deffnm comparison + + # NVT: spec passes gro_out="nvt.gro" + nvt_script = _build_mdrun_script(deffnm="nvt", work_dir="/tmp/test", gro_out="nvt.gro") + assert "nvt.gro" in nvt_script + + +def test_mdrun_app_error_message_includes_log_hint(): + """Mdrun command appears as a single unconditional line (no if/fi wrapper). + + The new implementation emits ``set -euo pipefail`` for error propagation + instead of inline ``if ! mdrun ...`` guards, producing a readable script + with one unconditional mdrun command. + """ + bash_script = _build_mdrun_script(deffnm="prod", work_dir="/tmp/test") + + # mdrun command is a single line — no surrounding if/then/fi + mdrun_lines = [ln.strip() for ln in bash_script.splitlines() if "mdrun -deffnm" in ln] + assert len(mdrun_lines) == 1, f"Expected 1 mdrun line, got: {mdrun_lines}" + cmd_line = mdrun_lines[0] + assert not cmd_line.startswith("if"), "mdrun command must not be wrapped in an if-check" + assert "mdrun -deffnm prod" in cmd_line + + +def test_bash_apps_use_strict_mode(): + """All bash apps use set -euo pipefail.""" + from mdfactory.orchestration.apps import _build_grompp_script + + grompp_script = _build_grompp_script( + mdp_file="em.mdp", + gro_file="system.pdb", + top_file="topology.top", + tpr_file="min.tpr", + work_dir="/tmp/test", + ) + mdrun_script = _build_mdrun_script(deffnm="min", work_dir="/tmp/test") + + # Both should use bash strict mode + assert "set -euo pipefail" in grompp_script + assert "set -euo pipefail" in mdrun_script + + +# === Decision 8: Trajectory Frame Count Validation Tests === + + +def test_validate_trajectory_returns_false_for_missing_file(mock_sim_dir): + """Trajectory validation returns False if file doesn't exist.""" + + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc") + assert result is False + + +def test_validate_trajectory_returns_false_for_empty_file(mock_sim_dir): + """Trajectory validation returns False for empty files.""" + + (mock_sim_dir / "prod.xtc").write_text("") # Empty file + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc") + assert result is False + + +def test_validate_trajectory_incomplete_without_structure(mock_sim_dir): + """Trajectory validation returns False when no structure file is available. + + Without a topology we cannot parse the trajectory, so the result is + conservatively False — the caller should trigger a partial restart rather + than silently skipping a stage with an unverifiable trajectory. + """ + # Create trajectory but no structure files + (mock_sim_dir / "prod.xtc").write_bytes(b"X" * 15_000_000) # 15 MB + + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc") + # No structure → cannot validate → treat as incomplete + assert result is False + + +def test_validate_trajectory_returns_false_for_non_parseable_file(mock_sim_dir): + """Trajectory validation returns False for non-parseable trajectory data.""" + # Small file that can't be parsed as an XTC/TRR + (mock_sim_dir / "prod.xtc").write_bytes(b"X" * 1000) + + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc") + assert result is False + + +@patch("mdfactory.orchestration.simulate.mda") +def test_validate_trajectory_with_mdanalysis_complete(mock_mda, mock_sim_dir): + """Trajectory validation uses MDAnalysis to count frames (complete).""" + + # Setup mocks + (mock_sim_dir / "prod.xtc").write_bytes(b"FAKE XTC") + (mock_sim_dir / "system.pdb").write_text("FAKE PDB") + + mock_traj = MagicMock() + mock_traj.__len__.return_value = 100 # 100 frames + mock_universe = MagicMock() + mock_universe.trajectory = mock_traj + mock_mda.Universe.return_value = mock_universe + + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc", expected_frames=100) + + # Should pass (100 >= 100) + assert result is True + + +@patch("mdfactory.orchestration.simulate.mda") +def test_validate_trajectory_with_mdanalysis_incomplete(mock_mda, mock_sim_dir): + """Trajectory validation detects incomplete trajectories.""" + + # Setup mocks + (mock_sim_dir / "prod.xtc").write_bytes(b"FAKE XTC") + (mock_sim_dir / "system.pdb").write_text("FAKE PDB") + + mock_traj = MagicMock() + mock_traj.__len__.return_value = 50 # Only 50 frames + mock_universe = MagicMock() + mock_universe.trajectory = mock_traj + mock_mda.Universe.return_value = mock_universe + + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc", expected_frames=100) + + # Should fail (50 < 100) + assert result is False + + +@patch("mdfactory.orchestration.simulate.mda") +def test_validate_trajectory_without_expected_frames(mock_mda, mock_sim_dir): + """Trajectory validation without expected_frames checks readability only.""" + + # Setup mocks + (mock_sim_dir / "prod.xtc").write_bytes(b"FAKE XTC") + (mock_sim_dir / "system.pdb").write_text("FAKE PDB") + + mock_traj = MagicMock() + mock_traj.__len__.return_value = 1 # Just 1 frame + mock_universe = MagicMock() + mock_universe.trajectory = mock_traj + mock_mda.Universe.return_value = mock_universe + + result = _validate_trajectory_complete(mock_sim_dir, "prod.xtc") + + # Should pass (> 0 frames, no expectation) + assert result is True + + +def test_find_structure_file_priority_order(mock_sim_dir): + """Find structure file uses correct priority order.""" + from mdfactory.orchestration.simulate import find_structure_file + + # Create files in reverse priority order + (mock_sim_dir / "system.pdb").write_text("FAKE") + (mock_sim_dir / "min.gro").write_text("FAKE") + (mock_sim_dir / "npt.gro").write_text("FAKE") + + # Should prioritize npt.gro over min.gro over system.pdb + result = find_structure_file(mock_sim_dir) + assert result == mock_sim_dir / "npt.gro" + + +def test_find_structure_file_returns_none_if_missing(tmp_path): + """Find structure file returns None if no candidates exist.""" + from mdfactory.orchestration.simulate import find_structure_file + + # Create empty directory with no structure files + empty_dir = tmp_path / "empty" + empty_dir.mkdir() + + result = find_structure_file(empty_dir) + assert result is None + + +def test_find_structure_file_candidates_derived_from_registry(): + """_STRUCTURE_CANDIDATES matches the current STAGE_REGISTRY gro_out fields.""" + from mdfactory.orchestration.simulate import _STRUCTURE_CANDIDATES + from mdfactory.orchestration.stages import STAGE_REGISTRY + + expected = [spec.gro_out for spec in reversed(STAGE_REGISTRY) if spec.gro_out] + ["system.pdb"] + assert _STRUCTURE_CANDIDATES == expected + + +def test_find_structure_file_picks_up_new_stage_gro(tmp_path, monkeypatch): + """find_structure_file returns a new stage's .gro when _STRUCTURE_CANDIDATES is extended.""" + import mdfactory.orchestration.simulate as sim_module + from mdfactory.orchestration.simulate import find_structure_file + + # Simulate a hypothetical future 'Heating' stage by patching the candidate list. + extended = ["heat.gro"] + sim_module._STRUCTURE_CANDIDATES + monkeypatch.setattr(sim_module, "_STRUCTURE_CANDIDATES", extended) + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "heat.gro").write_text("FAKE HEATED COORDS") + + result = find_structure_file(sim_dir) + assert result == sim_dir / "heat.gro" + + +def test_extract_expected_frames_from_mdp(mock_sim_dir): + """Extract expected frames from MDP file.""" + from mdfactory.orchestration.simulate import _extract_expected_frames_from_mdp + + # Create MDP with nsteps and nstxout-compressed + mdp_content = """ + ; Production MDP + nsteps = 500000 + nstxout-compressed = 5000 ; Save every 5000 steps + dt = 0.002 + """ + (mock_sim_dir / "md.mdp").write_text(mdp_content) + + result = _extract_expected_frames_from_mdp(mock_sim_dir, "Production") + + # 500000 / 5000 = 100 frames + assert result == 100 + + +def test_extract_expected_frames_handles_comments(mock_sim_dir): + """MDP parser ignores comments correctly.""" + from mdfactory.orchestration.simulate import _extract_expected_frames_from_mdp + + mdp_content = """ + ; nsteps = 999999 ; This is a comment + nsteps = 100000 ; Real value + ; nstxout-compressed = 123 + nstxout-compressed = 1000 ; Real value + """ + (mock_sim_dir / "md.mdp").write_text(mdp_content) + + result = _extract_expected_frames_from_mdp(mock_sim_dir, "Production") + + # 100000 / 1000 = 100 frames + assert result == 100 + + +def test_extract_expected_frames_returns_none_for_missing_file(mock_sim_dir): + """MDP parser returns None if file doesn't exist.""" + from mdfactory.orchestration.simulate import _extract_expected_frames_from_mdp + + result = _extract_expected_frames_from_mdp(mock_sim_dir, "Production") + assert result is None + + +def test_extract_expected_frames_returns_none_for_incomplete_mdp(mock_sim_dir): + """MDP parser returns None if required parameters missing.""" + from mdfactory.orchestration.simulate import _extract_expected_frames_from_mdp + + # Only nsteps, no nstxout-compressed + (mock_sim_dir / "md.mdp").write_text("nsteps = 100000\n") + + result = _extract_expected_frames_from_mdp(mock_sim_dir, "Production") + assert result is None + + +# Tests for GROMACS binary detection + + +def test_build_gmx_detect_block_no_extras(): + """Core detection block sets GMX_BIN only when no extras supplied.""" + from mdfactory.orchestration.apps import _build_gmx_detect_block + + block = _build_gmx_detect_block() + assert "GMX_BIN=gmx\n" in block + assert "GMX_BIN=gmx_mpi" in block + assert "MDRUN_THREAD_FLAGS" not in block + assert block.startswith("if command -v gmx") + assert block.endswith("fi") + + +def test_build_gmx_detect_block_with_extras(): + """Extra assignments are appended to each branch via semicolons.""" + from mdfactory.orchestration.apps import _build_gmx_detect_block + + block = _build_gmx_detect_block( + gmx_extra='MDRUN_THREAD_FLAGS="-nt $NTHR"', + gmx_mpi_extra='MDRUN_THREAD_FLAGS="-ntomp $NTHR"', + ) + assert 'GMX_BIN=gmx; MDRUN_THREAD_FLAGS="-nt $NTHR"' in block + assert 'GMX_BIN=gmx_mpi; MDRUN_THREAD_FLAGS="-ntomp $NTHR"' in block + + +def test_build_gmx_detect_block_error_handling(): + """Block contains the canonical error message and exit 1.""" + from mdfactory.orchestration.apps import _build_gmx_detect_block + + block = _build_gmx_detect_block() + assert "ERROR: Neither gmx nor gmx_mpi found in PATH" in block + assert "exit 1" in block + + +def test_get_gromacs_detect_script_returns_string(): + """GROMACS detect function returns valid bash script.""" + from mdfactory.orchestration.apps import _get_gromacs_detect_script + + script = _get_gromacs_detect_script() + + assert isinstance(script, str) + assert len(script) > 0 + + +def test_get_gromacs_detect_script_contains_required_logic(): + """Script contains binary detection logic.""" + from mdfactory.orchestration.apps import _get_gromacs_detect_script + + script = _get_gromacs_detect_script() + + # Check for gmx detection + assert "command -v gmx" in script + assert "command -v gmx_mpi" in script + + # Check for GMX_BIN assignment (unquoted, consistent with _build_gmx_detect_block) + assert "GMX_BIN=gmx\n" in script # gmx branch (not gmx_mpi) + assert "GMX_BIN=gmx_mpi" in script + + # Check for error handling + assert "ERROR" in script + assert "exit 1" in script + + +def test_get_gromacs_detect_script_prefers_gmx_over_gmx_mpi(): + """Script checks for gmx first (thread-MPI preferred for local execution).""" + from mdfactory.orchestration.apps import _get_gromacs_detect_script + + script = _get_gromacs_detect_script() + + # gmx should be checked before gmx_mpi + gmx_pos = script.find("command -v gmx ") + gmx_mpi_pos = script.find("command -v gmx_mpi") + + assert gmx_pos < gmx_mpi_pos, "gmx should be checked before gmx_mpi" + + +# === Decision 7: Checkpoint restart flag tests === + + +# === Decision 7: Restart wiring through stage functions and execute_stage_list === + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_passes_restart_to_stage_fn(mock_run_stage): + """_execute_stage_list forwards stage_restarts to run_stage as restart_from_cpt.""" + mock_run_stage.return_value = MagicMock() + sim_dir = Path("/tmp/test") + + _execute_stage_list( + sim_dir, + ["NVT"], + MagicMock(), + MagicMock(), + stage_restarts={"NVT": "/sim/nvt.cpt"}, + ) + + mock_run_stage.assert_called_once() + call_kwargs = mock_run_stage.call_args[1] + assert call_kwargs.get("restart_from_cpt") == "/sim/nvt.cpt" + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_restart_only_for_matching_stage(mock_run_stage): + """Only the stage with a cpt entry gets a non-empty restart_from_cpt.""" + nvt_fut = MagicMock() + mock_run_stage.side_effect = [nvt_fut, MagicMock()] + sim_dir = Path("/tmp/test") + + _execute_stage_list( + sim_dir, + ["NVT", "NPT"], + MagicMock(), + MagicMock(), + stage_restarts={"NPT": "/sim/npt.cpt"}, + max_rescue=0, + ) + + calls = mock_run_stage.call_args_list + # NVT: no restart (empty string) + assert calls[0][1].get("restart_from_cpt") == "" + # NPT: gets restart path + assert calls[1][1].get("restart_from_cpt") == "/sim/npt.cpt" + + +def test_nvt_stage_skips_grompp_on_restart(): + """run_stage for NVT skips grompp and passes restart_from_cpt to mdrun_app.""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock() + mdrun_app = MagicMock() + mdrun_app.return_value = MagicMock() + + run_stage( + STAGE_BY_NAME["NVT"], + Path("/sim"), + None, + grompp_app, + mdrun_app, + restart_from_cpt="/sim/nvt.cpt", + ) + + grompp_app.assert_not_called() + mdrun_app.assert_called_once() + call_kwargs = mdrun_app.call_args[1] + assert call_kwargs.get("restart_from_cpt") == "/sim/nvt.cpt" + + +def test_npt_stage_skips_grompp_on_restart(): + """run_stage for NPT skips grompp and passes restart_from_cpt to mdrun_app.""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock() + mdrun_app = MagicMock() + mdrun_app.return_value = MagicMock() + + run_stage( + STAGE_BY_NAME["NPT"], + Path("/sim"), + None, + grompp_app, + mdrun_app, + restart_from_cpt="/sim/npt.cpt", + ) + + grompp_app.assert_not_called() + mdrun_app.assert_called_once() + call_kwargs = mdrun_app.call_args[1] + assert call_kwargs.get("restart_from_cpt") == "/sim/npt.cpt" + + +def test_production_stage_skips_grompp_on_restart(): + """run_stage for Production skips grompp and passes restart_from_cpt to mdrun_app.""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock() + mdrun_app = MagicMock() + mdrun_app.return_value = MagicMock() + + run_stage( + STAGE_BY_NAME["Production"], + Path("/sim"), + None, + grompp_app, + mdrun_app, + restart_from_cpt="/sim/prod.cpt", + ) + + grompp_app.assert_not_called() + mdrun_app.assert_called_once() + call_kwargs = mdrun_app.call_args[1] + assert call_kwargs.get("restart_from_cpt") == "/sim/prod.cpt" + + +# === Decision 6: Per-stage resource config wiring tests === + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_passes_stage_config_when_slurm(mock_run_stage): + """_execute_stage_list resolves per-stage config and forwards as stage_config to run_stage.""" + from mdfactory.orchestration.config import SlurmExecutorConfig + + cfg = SlurmExecutorConfig( + account="acct", + cpus_per_node=12, + stage_overrides={"EM": {"cpus_per_node": 4, "gres": None}}, + ) + mock_run_stage.return_value = MagicMock() + + _execute_stage_list( + Path("/tmp/test"), ["EM"], MagicMock(), MagicMock(), config=cfg, max_rescue=0 + ) + + mock_run_stage.assert_called_once() + _, call_kwargs = mock_run_stage.call_args + em_cfg = call_kwargs.get("stage_config") + assert em_cfg is not None + assert em_cfg.cpus_per_node == 4 + + +@patch("mdfactory.orchestration.simulate.run_stage") +def test_execute_stage_list_no_stage_config_for_local(mock_run_stage): + """_execute_stage_list does not inject stage_config for LocalProvider (no overrides).""" + from mdfactory.orchestration.config import ExecutorConfig + + mock_run_stage.return_value = MagicMock() + + _execute_stage_list( + Path("/tmp/test"), + ["EM"], + MagicMock(), + MagicMock(), + config=ExecutorConfig(), + max_rescue=0, + ) + + _, call_kwargs = mock_run_stage.call_args + assert "stage_config" not in call_kwargs + + +def test_mdrun_app_ntasks_override_used_when_set(): + """Explicit ntasks embeds a literal NTHR= in the script.""" + # Check via resolver (pure Python) + assert _resolve_thread_count_expr(8) == "8" + + # Confirm it flows through _build_mdrun_script to the preamble + bash_script = _build_mdrun_script(deffnm="nvt", work_dir="/tmp/test", ntasks=8) + assert "NTHR=8" in bash_script + # Should NOT fall back to SLURM auto-detection + assert "${SLURM_CPUS_PER_TASK" not in bash_script + + +def test_mdrun_app_cpu_mode_forced_when_disable_gpu(): + """disable_gpu=True produces a script with no GPU flags anywhere.""" + # Check via resolver (pure Python) + assert _resolve_gpu_flags(has_gpu=False, pme_gpu=True) == "" + + # Confirm the generated script has no GPU-related content + bash_script = _build_mdrun_script(deffnm="min", work_dir="/tmp/test", disable_gpu=True) + assert "-nb gpu" not in bash_script + assert "-gpu_id" not in bash_script + assert "GPU_ID" not in bash_script + + +def test_em_stage_passes_resource_hints_to_mdrun(): + """run_stage for EM extracts resource hints from stage_config and passes to mdrun_app.""" + from mdfactory.orchestration.config import SlurmExecutorConfig + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + cfg = SlurmExecutorConfig(account="acct", cpus_per_node=4, gres=None) + + grompp_app = MagicMock() + grompp_app.return_value = MagicMock() + mdrun_app = MagicMock() + mdrun_app.return_value = MagicMock() + + run_stage(STAGE_BY_NAME["EM"], Path("/sim"), None, grompp_app, mdrun_app, stage_config=cfg) + + mdrun_app.assert_called_once() + _, call_kwargs = mdrun_app.call_args + assert call_kwargs.get("ntasks") == 4 + assert call_kwargs.get("disable_gpu") is True # gres=None → no GPU + + +def test_mdrun_app_no_cpt_flags_by_default(): + """No restart_from_cpt produces empty restart flags.""" + # Check via resolver (pure Python) + assert _resolve_restart_flags("") == "" + + # Confirm the generated script has no checkpoint flags + bash_script = _build_mdrun_script(deffnm="nvt", work_dir="/tmp/test") + assert "-cpi" not in bash_script + assert "-append" not in bash_script + + +def test_mdrun_app_cpt_flags_added_when_restart_set(): + """restart_from_cpt adds -cpi -append to the mdrun command.""" + # Check via resolver (pure Python) + assert _resolve_restart_flags("x.cpt") == "-cpi x.cpt -append" + + # Confirm it flows into the generated script + bash_script = _build_mdrun_script( + deffnm="npt", + work_dir="/tmp/test", + restart_from_cpt="/sim/npt.cpt", + ) + assert "-cpi /sim/npt.cpt -append" in bash_script + + +def test_mdrun_app_cpt_flags_in_single_command(): + """Checkpoint restart flags appear exactly once in the single mdrun command. + + The new implementation emits one unconditional mdrun command, so -cpi + appears exactly once (not duplicated across GPU/CPU branches). + """ + bash_script = _build_mdrun_script( + deffnm="prod", + work_dir="/tmp/test", + restart_from_cpt="/sim/prod.cpt", + ) + + cpi_count = bash_script.count("-cpi /sim/prod.cpt -append") + assert cpi_count == 1, ( + f"Expected -cpi flag exactly once in the single mdrun command, found {cpi_count}" + ) + + +def test_mdrun_app_cpt_log_message_mentions_file(): + """Mdrun bash app logs the checkpoint file it is resuming from.""" + bash_script = _build_mdrun_script( + deffnm="prod", + work_dir="/tmp/test", + restart_from_cpt="/sim/prod.cpt", + ) + + assert "resuming from /sim/prod.cpt" in bash_script + + +# --------------------------------------------------------------------------- +# T1: Checkpoint restart detection from disk state (Finding 5) +# --------------------------------------------------------------------------- + + +def test_detect_stage_state_partial_restart_nvt(tmp_path): + """Partial NVT: .cpt + .tpr present, .gro absent → status=partial, restart=True.""" + from mdfactory.orchestration.simulate import _detect_stage_state + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "nvt.cpt").write_bytes(b"x") + (sim_dir / "nvt.tpr").write_bytes(b"x") + # nvt.gro intentionally absent + + state = _detect_stage_state(sim_dir, "NVT", "auto") + + assert state["status"] == "partial" + assert state["restart"] is True + assert state["cpt_file"] == sim_dir / "nvt.cpt" + + +def test_detect_stage_state_partial_restart_em(tmp_path): + """Partial EM: min.cpt + min.tpr present, min.gro absent → status=partial.""" + from mdfactory.orchestration.simulate import _detect_stage_state + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "min.cpt").write_bytes(b"x") + (sim_dir / "min.tpr").write_bytes(b"x") + + state = _detect_stage_state(sim_dir, "EM", "auto") + + assert state["status"] == "partial" + assert state["restart"] is True + assert state["cpt_file"] == sim_dir / "min.cpt" + + +def test_detect_stage_state_not_started_when_no_files(tmp_path): + """Empty directory → status=not_started, restart=False.""" + from mdfactory.orchestration.simulate import _detect_stage_state + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + + state = _detect_stage_state(sim_dir, "NVT", "auto") + + assert state["status"] == "not_started" + assert state["restart"] is False + assert state["cpt_file"] is None + + +def test_detect_needed_stages_with_restart_info_partial(tmp_path): + """Restart info propagated into the work-plan when partial progress exists.""" + from mdfactory.orchestration.simulate import _detect_needed_stages_with_restart_info + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "nvt.cpt").write_bytes(b"x") + (sim_dir / "nvt.tpr").write_bytes(b"x") + + items = _detect_needed_stages_with_restart_info(sim_dir, ["NVT"], "auto") + + assert len(items) == 1 + assert items[0]["stage"] == "NVT" + assert items[0]["restart"] is True + assert items[0]["cpt_file"] == sim_dir / "nvt.cpt" + + +@patch("mdfactory.orchestration.simulate.parsl_session") +@patch("mdfactory.orchestration.simulate._execute_stage_list") +def test_run_simulations_propagates_stage_restarts(mock_execute, mock_session, tmp_path): + """stage_restarts dict assembled from checkpoint detection is passed to _execute_stage_list.""" + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + for f in ["system.pdb", "topology.top", "em.mdp", "nvt.mdp", "npt.mdp", "md.mdp"]: + (sim_dir / f).write_text("FAKE") + # NVT partial: .cpt + .tpr present + (sim_dir / "nvt.cpt").write_bytes(b"x") + (sim_dir / "nvt.tpr").write_bytes(b"x") + # EM already complete + (sim_dir / "min.gro").write_text("FAKE") + + mock_execute.return_value = MagicMock() + mock_session.return_value.__enter__.return_value = MagicMock() + mock_session.return_value.__exit__.return_value = False + + from mdfactory.orchestration.config import ExecutorConfig + + run_simulations([sim_dir], ExecutorConfig(), wait=False) + + # _execute_stage_list must be called; assert stage_restarts contains NVT entry + assert mock_execute.called + call_kwargs = mock_execute.call_args[1] + restarts = call_kwargs.get("stage_restarts", {}) + assert "NVT" in restarts + assert str(sim_dir / "nvt.cpt") == restarts["NVT"] + + +# --------------------------------------------------------------------------- +# T3: run_stage grompp-kwarg wiring (Finding 7) +# --------------------------------------------------------------------------- + + +def test_run_stage_nvt_passes_ref_file_to_grompp(tmp_path): + """NVT: run_stage calls grompp_app with ref_file=min.gro (position restraints).""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock(return_value=MagicMock()) + mdrun_app = MagicMock(return_value=MagicMock()) + + run_stage(STAGE_BY_NAME["NVT"], tmp_path, None, grompp_app, mdrun_app) + + call_kwargs = grompp_app.call_args[1] + assert call_kwargs["ref_file"] == "min.gro" + # NVT has no prereq_cpt; cpt_file kwarg should be absent + assert "cpt_file" not in call_kwargs + + +def test_run_stage_npt_passes_ref_and_cpt_to_grompp(tmp_path): + """NPT: run_stage calls grompp_app with both ref_file=nvt.gro and cpt_file=nvt.cpt.""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock(return_value=MagicMock()) + mdrun_app = MagicMock(return_value=MagicMock()) + + run_stage(STAGE_BY_NAME["NPT"], tmp_path, None, grompp_app, mdrun_app) + + call_kwargs = grompp_app.call_args[1] + assert call_kwargs["ref_file"] == "nvt.gro" + assert call_kwargs["cpt_file"] == "nvt.cpt" + assert call_kwargs["maxwarn"] == 2 + + +def test_run_stage_em_has_no_ref_or_cpt_flags(tmp_path): + """EM: run_stage calls grompp_app without ref_file or cpt_file (none specified in spec).""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock(return_value=MagicMock()) + mdrun_app = MagicMock(return_value=MagicMock()) + + run_stage(STAGE_BY_NAME["EM"], tmp_path, None, grompp_app, mdrun_app) + + call_kwargs = grompp_app.call_args[1] + assert "ref_file" not in call_kwargs + assert "cpt_file" not in call_kwargs + + +def test_run_stage_production_has_cpt_but_no_ref(tmp_path): + """Production: run_stage calls grompp_app with cpt_file=npt.cpt but no ref_file.""" + from mdfactory.orchestration.stages import STAGE_BY_NAME, run_stage + + grompp_app = MagicMock(return_value=MagicMock()) + mdrun_app = MagicMock(return_value=MagicMock()) + + run_stage(STAGE_BY_NAME["Production"], tmp_path, None, grompp_app, mdrun_app) + + call_kwargs = grompp_app.call_args[1] + assert "ref_file" not in call_kwargs + assert call_kwargs["cpt_file"] == "npt.cpt" + + +# --------------------------------------------------------------------------- +# T5: _bash_result_to_dict (Finding 15) +# --------------------------------------------------------------------------- + + +def test_bash_result_to_dict_none_becomes_success(): + """None (bash_app exit) → success dict.""" + from mdfactory.orchestration.simulate import _bash_result_to_dict + + result = _bash_result_to_dict(None, "abc123") + assert result == {"hash": "abc123", "status": "success"} + + +def test_bash_result_to_dict_int_becomes_success(): + """Integer exit code → success dict.""" + from mdfactory.orchestration.simulate import _bash_result_to_dict + + result = _bash_result_to_dict(0, "abc123") + assert result == {"hash": "abc123", "status": "success"} + + +def test_bash_result_to_dict_dict_passthrough(): + """Dict values (python_app callers) pass through unchanged.""" + from mdfactory.orchestration.simulate import _bash_result_to_dict + + d = {"hash": "abc123", "status": "failed", "error": "mdrun crashed"} + assert _bash_result_to_dict(d, "abc123") is d + + +# --------------------------------------------------------------------------- +# T2: grompp -r/-t flag generation (Finding 6) +# --------------------------------------------------------------------------- + + +def test_build_grompp_script_generates_ref_flag(): + """_build_grompp_script emits -r flag when ref_file is provided.""" + from mdfactory.orchestration.apps import _build_grompp_script + + script = _build_grompp_script( + "nvt.mdp", "min.gro", "topology.top", "nvt.tpr", "/sim", ref_file="min.gro" + ) + assert "-r min.gro" in script + + +def test_build_grompp_script_generates_cpt_flag(): + """_build_grompp_script emits -t flag when cpt_file is provided.""" + from mdfactory.orchestration.apps import _build_grompp_script + + script = _build_grompp_script( + "npt.mdp", "nvt.gro", "topology.top", "npt.tpr", "/sim", cpt_file="nvt.cpt" + ) + assert "-t nvt.cpt" in script + + +def test_build_grompp_script_omits_flags_by_default(): + """_build_grompp_script omits -r and -t when no ref_file / cpt_file given.""" + from mdfactory.orchestration.apps import _build_grompp_script + + script = _build_grompp_script("em.mdp", "system.pdb", "topology.top", "em.tpr", "/sim") + assert "-r " not in script + assert "-t " not in script + + +def test_build_grompp_script_can_emit_both_flags(): + """_build_grompp_script emits both -r and -t when both are provided.""" + from mdfactory.orchestration.apps import _build_grompp_script + + script = _build_grompp_script( + "npt.mdp", + "nvt.gro", + "topology.top", + "npt.tpr", + "/sim", + ref_file="nvt.gro", + cpt_file="nvt.cpt", + ) + assert "-r nvt.gro" in script + assert "-t nvt.cpt" in script + + +# --------------------------------------------------------------------------- +# T6: gmx_binary selection in _build_mdrun_script (Finding 16) +# --------------------------------------------------------------------------- + + +def test_build_mdrun_script_auto_includes_binary_detection_preamble(): + """gmx_binary='auto' emits the runtime binary detection if/elif block.""" + script = _build_mdrun_script("prod", "/sim", gmx_binary="auto") + # The detection preamble checks for 'gmx' and 'gmx_mpi' at runtime + assert "command -v gmx" in script + + +def test_build_mdrun_script_explicit_gmx_omits_detection_preamble(): + """gmx_binary='gmx' uses literal 'gmx' binary, no runtime detection.""" + script = _build_mdrun_script("prod", "/sim", gmx_binary="gmx") + assert "command -v gmx" not in script + # Literal binary name appears in the mdrun command + assert "gmx mdrun" in script + + +def test_build_mdrun_script_explicit_gmx_mpi(): + """gmx_binary='gmx_mpi' uses literal 'gmx_mpi' binary, no detection preamble.""" + script = _build_mdrun_script("prod", "/sim", gmx_binary="gmx_mpi") + assert "command -v gmx" not in script + assert "gmx_mpi mdrun" in script + + +# --------------------------------------------------------------------------- +# T7: _extract_resource_hints GPU / gmx_binary branches (Finding 18) +# --------------------------------------------------------------------------- + + +def test_extract_resource_hints_gpu_gres_disables_disable_gpu(): + """GPU gres string → disable_gpu=False (GPU mode active).""" + from mdfactory.orchestration.stages import _extract_resource_hints + + cfg = MagicMock( + cpus_per_node=12, gres="gpu:l40s:1", gmx_binary="gmx_mpi", max_workers_per_node=1 + ) + hints = _extract_resource_hints(cfg) + + assert hints.disable_gpu is False + assert hints.gmx_binary == "gmx_mpi" + assert hints.ntasks == 12 + + +def test_extract_resource_hints_non_gpu_gres_sets_disable_gpu(): + """Non-GPU gres string → disable_gpu=True (no GPU).""" + from mdfactory.orchestration.stages import _extract_resource_hints + + cfg = MagicMock(cpus_per_node=4, gres="ssd:1", gmx_binary="auto", max_workers_per_node=1) + hints = _extract_resource_hints(cfg) + + assert hints.disable_gpu is True + + +def test_extract_resource_hints_none_gres_sets_disable_gpu(): + """gres=None → disable_gpu=True (no GPU).""" + from mdfactory.orchestration.stages import _extract_resource_hints + + cfg = MagicMock(cpus_per_node=8, gres=None, gmx_binary="auto", max_workers_per_node=1) + hints = _extract_resource_hints(cfg) + + assert hints.disable_gpu is True + + +def test_extract_resource_hints_none_stage_config_is_cpu_safe(): + """stage_config=None → disable_gpu=True (safe default for local runs).""" + from mdfactory.orchestration.stages import _extract_resource_hints + + hints = _extract_resource_hints(None) + + assert hints.disable_gpu is True + assert hints.ntasks == 0 + assert hints.gmx_binary == "auto" + + +def test_extract_resource_hints_divides_by_max_workers(): + """ntasks is divided by max_workers_per_node when >1 to avoid oversubscription.""" + from mdfactory.orchestration.stages import _extract_resource_hints + + cfg = MagicMock(cpus_per_node=12, gres=None, gmx_binary="auto", max_workers_per_node=2) + hints = _extract_resource_hints(cfg) + + assert hints.ntasks == 6 # 12 // 2 + + +# --- clean_simulation_outputs tests --- + + +def _populate_sim_dir(sim_dir): + """Create a fake simulation directory with build inputs and all stage outputs.""" + # Build inputs (must be preserved) + (sim_dir / "system.pdb").write_bytes(b"pdb") + (sim_dir / "topology.top").write_bytes(b"top") + (sim_dir / "em.mdp").write_bytes(b"mdp") + (sim_dir / "nvt.mdp").write_bytes(b"mdp") + (sim_dir / "npt.mdp").write_bytes(b"mdp") + (sim_dir / "md.mdp").write_bytes(b"mdp") + (sim_dir / "posres.itp").write_bytes(b"itp") + (sim_dir / "config.yaml").write_bytes(b"yaml") + + # Simulation outputs per stage + for deffnm in ("min", "nvt", "npt", "prod"): + for ext in ("tpr", "cpt", "log", "edr"): + (sim_dir / f"{deffnm}.{ext}").write_bytes(b"x") + + # Structure outputs (EM/NVT/NPT) + for deffnm in ("min", "nvt", "npt"): + (sim_dir / f"{deffnm}.gro").write_bytes(b"x") + + # Trajectory outputs (Production) + (sim_dir / "prod.xtc").write_bytes(b"x") + (sim_dir / "prod.trr").write_bytes(b"x") + + # Rescue MDPs + (sim_dir / "em_rescue_t1.mdp").write_bytes(b"x") + (sim_dir / "em_rescue_t2.mdp").write_bytes(b"x") + (sim_dir / "nvt_rescue_t1.mdp").write_bytes(b"x") + + # GROMACS backup files + (sim_dir / "#min.tpr.1#").write_bytes(b"x") + (sim_dir / "#prod.xtc.1#").write_bytes(b"x") + + # grompp output + (sim_dir / "mdout.mdp").write_bytes(b"x") + + +def test_clean_simulation_outputs_all_stages(tmp_path): + """All stage outputs removed; build inputs preserved.""" + from mdfactory.orchestration.simulate import clean_simulation_outputs + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + _populate_sim_dir(sim_dir) + + all_stages = ["EM", "NVT", "NPT", "Production"] + deleted = clean_simulation_outputs(sim_dir, all_stages) + + # Build inputs must survive + assert (sim_dir / "system.pdb").exists() + assert (sim_dir / "topology.top").exists() + assert (sim_dir / "em.mdp").exists() + assert (sim_dir / "nvt.mdp").exists() + assert (sim_dir / "npt.mdp").exists() + assert (sim_dir / "md.mdp").exists() + assert (sim_dir / "posres.itp").exists() + assert (sim_dir / "config.yaml").exists() + + # Simulation outputs must be gone + for deffnm in ("min", "nvt", "npt", "prod"): + for ext in ("tpr", "cpt", "log", "edr"): + assert not (sim_dir / f"{deffnm}.{ext}").exists() + + for deffnm in ("min", "nvt", "npt"): + assert not (sim_dir / f"{deffnm}.gro").exists() + + assert not (sim_dir / "prod.xtc").exists() + assert not (sim_dir / "prod.trr").exists() + + # Rescue MDPs and backups must be gone + assert not (sim_dir / "em_rescue_t1.mdp").exists() + assert not (sim_dir / "em_rescue_t2.mdp").exists() + assert not (sim_dir / "nvt_rescue_t1.mdp").exists() + assert not (sim_dir / "#min.tpr.1#").exists() + assert not (sim_dir / "#prod.xtc.1#").exists() + assert not (sim_dir / "mdout.mdp").exists() + + assert len(deleted) > 0 + + +def test_clean_simulation_outputs_filtered_stages(tmp_path): + """Clean only Production: prod.* removed; min/nvt/npt files preserved.""" + from mdfactory.orchestration.simulate import clean_simulation_outputs + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + _populate_sim_dir(sim_dir) + + deleted = clean_simulation_outputs(sim_dir, ["Production"]) + + # Production outputs must be gone + assert not (sim_dir / "prod.tpr").exists() + assert not (sim_dir / "prod.cpt").exists() + assert not (sim_dir / "prod.log").exists() + assert not (sim_dir / "prod.edr").exists() + assert not (sim_dir / "prod.xtc").exists() + assert not (sim_dir / "prod.trr").exists() + assert not (sim_dir / "#prod.xtc.1#").exists() + + # Other stages must survive + assert (sim_dir / "min.tpr").exists() + assert (sim_dir / "min.gro").exists() + assert (sim_dir / "nvt.cpt").exists() + assert (sim_dir / "npt.gro").exists() + + # EM rescue MDPs must survive (not in requested stages) + assert (sim_dir / "em_rescue_t1.mdp").exists() + assert (sim_dir / "em_rescue_t2.mdp").exists() + + # mdout.mdp is always deleted + assert not (sim_dir / "mdout.mdp").exists() + + deleted_names = {p.name for p in deleted} + assert "prod.tpr" in deleted_names + assert "min.tpr" not in deleted_names + + +def test_clean_simulation_outputs_missing_files(tmp_path): + """Partial outputs (some absent) — no errors, remaining files cleaned.""" + from mdfactory.orchestration.simulate import clean_simulation_outputs + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + # Only create a subset of files + (sim_dir / "system.pdb").write_bytes(b"pdb") + (sim_dir / "topology.top").write_bytes(b"top") + (sim_dir / "min.tpr").write_bytes(b"x") + # min.cpt, min.gro, etc. intentionally absent + + deleted = clean_simulation_outputs(sim_dir, ["EM"]) + + assert not (sim_dir / "min.tpr").exists() + assert (sim_dir / "system.pdb").exists() + assert len(deleted) == 1 + + +def test_clean_simulation_outputs_preserves_build_inputs(tmp_path): + """Explicitly verify all build input types survive cleaning.""" + from mdfactory.orchestration.simulate import clean_simulation_outputs + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + _populate_sim_dir(sim_dir) + + clean_simulation_outputs(sim_dir, ["EM", "NVT", "NPT", "Production"]) + + # Every build input must still exist + for name in ( + "system.pdb", + "topology.top", + "em.mdp", + "nvt.mdp", + "npt.mdp", + "md.mdp", + "posres.itp", + "config.yaml", + ): + assert (sim_dir / name).exists(), f"{name} should be preserved" + + +def test_clean_simulation_outputs_dry_run(tmp_path): + """Dry-run mode returns files that would be deleted but doesn't remove them.""" + from mdfactory.orchestration.simulate import clean_simulation_outputs + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + _populate_sim_dir(sim_dir) + + would_delete = clean_simulation_outputs( + sim_dir, ["EM", "NVT", "NPT", "Production"], dry_run=True + ) + + # Files must still exist + assert (sim_dir / "min.tpr").exists() + assert (sim_dir / "prod.xtc").exists() + assert (sim_dir / "mdout.mdp").exists() + + # But the return value should list them + assert len(would_delete) > 0 + deleted_names = {p.name for p in would_delete} + assert "min.tpr" in deleted_names + assert "prod.xtc" in deleted_names + + +# --- _detect_stage_state trajectory stale-cpt fix tests --- + + +def test_detect_stage_state_trajectory_stale_cpt(tmp_path): + """Production with cpt+tpr but no trajectory → not_started (stale-cpt fix).""" + from mdfactory.orchestration.simulate import _detect_stage_state + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "prod.cpt").write_bytes(b"x") + (sim_dir / "prod.tpr").write_bytes(b"x") + # prod.xtc / prod.trr intentionally absent + + state = _detect_stage_state(sim_dir, "Production", "auto") + + assert state["status"] == "not_started" + assert state["restart"] is False + assert state["cpt_file"] is None + + +def test_detect_stage_state_structure_partial_cpt_unchanged(tmp_path): + """EM with cpt+tpr but no gro → still partial (structure stages unchanged).""" + from mdfactory.orchestration.simulate import _detect_stage_state + + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + (sim_dir / "min.cpt").write_bytes(b"x") + (sim_dir / "min.tpr").write_bytes(b"x") + # min.gro intentionally absent + + state = _detect_stage_state(sim_dir, "EM", "auto") + + assert state["status"] == "partial" + assert state["restart"] is True + assert state["cpt_file"] == sim_dir / "min.cpt" + + +# --- run_simulations clean integration tests --- + + +@patch("mdfactory.orchestration.progress.display_stage_progress") +@patch("mdfactory.orchestration.simulate.parsl_session") +@patch("mdfactory.orchestration.simulate._execute_stage_list") +def test_run_simulations_clean_deletes_before_checkpoint_detection( + mock_execute, mock_session, mock_display, tmp_path +): + """run_simulations(clean=True) deletes outputs then detects all stages needed.""" + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + _populate_sim_dir(sim_dir) + + mock_future = MagicMock() + mock_future.done.return_value = True + mock_future.result.return_value = {"status": "success"} + mock_execute.return_value = mock_future + mock_session.return_value.__enter__.return_value = MagicMock() + + config = ExecutorConfig() + run_simulations([sim_dir], config, clean=True) + + # Verify files were actually deleted before execution + assert not (sim_dir / "min.tpr").exists() + assert not (sim_dir / "prod.xtc").exists() + assert not (sim_dir / "mdout.mdp").exists() + # Build inputs preserved + assert (sim_dir / "system.pdb").exists() + assert (sim_dir / "topology.top").exists() + + # All stages should have been submitted (clean removed all outputs) + mock_execute.assert_called_once() + stages_arg = mock_execute.call_args[0][1] + assert set(stages_arg) == {"EM", "NVT", "NPT", "Production"} + + +def test_run_simulations_clean_dry_run_preserves_files(tmp_path): + """run_simulations(clean=True, dry_run=True) previews without removing files.""" + sim_dir = tmp_path / "sim" + sim_dir.mkdir() + _populate_sim_dir(sim_dir) + + config = ExecutorConfig() + results = run_simulations([sim_dir], config, clean=True, dry_run=True) + + # dry_run=True propagates to clean_simulation_outputs — files NOT deleted + assert (sim_dir / "min.gro").exists() + assert (sim_dir / "min.tpr").exists() + assert (sim_dir / "prod.xtc").exists() + + # Still returns a work plan (dry-run shows what would run) + plan_items = [r for r in results if "sim_dir" in r] + assert len(plan_items) == 1 diff --git a/mdfactory/tests/test_orchestration_tui.py b/mdfactory/tests/test_orchestration_tui.py new file mode 100644 index 0000000..09289f4 --- /dev/null +++ b/mdfactory/tests/test_orchestration_tui.py @@ -0,0 +1,705 @@ +# ABOUTME: Tests for the interactive SLURM configuration TUI wizard +# ABOUTME: Uses mocked questionary to verify prompt flow and config generation +"""Tests for orchestration TUI wizard.""" + +from unittest.mock import MagicMock, patch + +import pytest +import yaml + +from mdfactory.orchestration.config import SlurmExecutorConfig +from mdfactory.orchestration.tui import ( + UserCancelledError, + _detect_gromacs_modules, + _prompt_environment, + _prompt_stage_overrides, + _require, + configure_and_save_environment, + configure_slurm_interactive, + save_slurm_config_yaml, +) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_cluster(): + """Create a mock ClusterInfo for TUI tests.""" + nt = MagicMock() + nt.cpus = 96 + nt.memory_mb = 512 * 1024 + nt.gpu_specs = ((4, "a100"),) + nt.features = ("avx512",) + nt.count = 10 + + partition = MagicMock() + partition.name = "gpu" + partition.state = "up" + partition.total_nodes = 10 + partition.node_types = [nt] + partition.is_default = True + partition.max_time = "24:00:00" + + cluster = MagicMock() + cluster.partitions = [partition] + cluster.accounts = ["hpc_chem", "hpc_bio"] + cluster.qos_policies = ["normal", "high"] + cluster.default_account = "hpc_chem" + return cluster + + +# --------------------------------------------------------------------------- +# _require tests +# --------------------------------------------------------------------------- + + +class TestRequire: + def test_returns_value(self): + assert _require("hello", "test") == "hello" + + def test_raises_on_none(self): + with pytest.raises(UserCancelledError): + _require(None, "test") + + +# --------------------------------------------------------------------------- +# _detect_gromacs_modules tests +# --------------------------------------------------------------------------- + + +class TestDetectGromacsModules: + """Tests for GROMACS module detection via ``module avail``.""" + + def test_parses_gromacs_modules(self, monkeypatch): + """Standard 'module avail gromacs' output is parsed correctly.""" + fake_output = ( + "---- /usr/share/modules ----\n" + "gromacs/2023.5 gromacs/2024.4(default) gromacs/2024.4-gpu\n" + ) + monkeypatch.setattr( + "subprocess.run", + lambda *a, **kw: type("R", (), {"stdout": fake_output, "stderr": ""})(), + ) + result = _detect_gromacs_modules() + assert "gromacs/2023.5" in result + assert "gromacs/2024.4" in result + assert "gromacs/2024.4-gpu" in result + + def test_no_modules_available(self, monkeypatch): + """Empty output from module avail returns empty list.""" + monkeypatch.setattr( + "subprocess.run", + lambda *a, **kw: type("R", (), {"stdout": "", "stderr": ""})(), + ) + assert _detect_gromacs_modules() == [] + + def test_subprocess_failure(self, monkeypatch): + """Subprocess error returns empty list gracefully.""" + monkeypatch.setattr( + "subprocess.run", + lambda *a, **kw: (_ for _ in ()).throw(OSError("no bash")), + ) + assert _detect_gromacs_modules() == [] + + def test_non_gromacs_modules_ignored(self, monkeypatch): + """Modules that don't start with gromacs/ are ignored.""" + fake_output = "cuda/12.2 openmpi/4.1 python/3.11\n" + monkeypatch.setattr( + "subprocess.run", + lambda *a, **kw: type("R", (), {"stdout": fake_output, "stderr": ""})(), + ) + assert _detect_gromacs_modules() == [] + + +# --------------------------------------------------------------------------- +# configure_slurm_interactive tests +# --------------------------------------------------------------------------- + + +class TestConfigureManualFallback: + """When discover_cluster returns None, wizard uses manual entry.""" + + @patch("mdfactory.orchestration.tui._prompt_environment") + @patch("mdfactory.orchestration.tui.discover_cluster", return_value=None) + @patch("mdfactory.orchestration.tui._import_questionary") + def test_configure_manual_fallback(self, mock_iq, _discover, mock_prompt_env): + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_q = mock_iq.return_value + mock_prompt_env.return_value = EnvironmentConfig() + + # confirm prompts: proceed with manual? → True, stage overrides? → False + mock_q.confirm.return_value.ask.side_effect = [True, False] + # text prompts in order: account, partition, walltime, cpus, + # gres, mem, qos, max_blocks + mock_q.text.return_value.ask.side_effect = [ + "manual_acc", + "gpu", + "4:00:00", + "24", + "gpu:a100:1", + "64G", + "", + "4", + ] + + cfg = configure_slurm_interactive() + assert cfg.account == "manual_acc" + assert cfg.partition == "gpu" + assert cfg.walltime == "4:00:00" + assert cfg.cpus_per_node == 24 + assert cfg.gres == "gpu:a100:1" + assert cfg.mem == "64G" + assert cfg.environment is not None + assert cfg.stage_overrides == {} + + +class TestConfigureWithCluster: + """When cluster is discovered, wizard uses select menus.""" + + @patch("mdfactory.orchestration.tui._prompt_environment") + @patch("mdfactory.orchestration.tui.discover_cluster") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_configure_with_cluster(self, mock_iq, mock_discover, mock_prompt_env): + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_q = mock_iq.return_value + mock_discover.return_value = _make_cluster() + mock_prompt_env.return_value = EnvironmentConfig(modules=["gromacs/2024.4"]) + + # select prompts: account, partition, walltime, cpus, mem, max_blocks + mock_q.select.return_value.ask.side_effect = [ + "hpc_chem", + "gpu", + "2h", + "16", + "50G", + "4", + ] + # text prompts: gres, constraint + mock_q.text.return_value.ask.side_effect = [ + "gpu:a100:1", + "a100", + ] + # confirm: QOS → False, stage overrides → False + mock_q.confirm.return_value.ask.side_effect = [False, False] + + cfg = configure_slurm_interactive() + assert cfg.account == "hpc_chem" + assert cfg.partition == "gpu" + assert cfg.walltime == "02:00:00" + assert cfg.cpus_per_node == 16 + assert cfg.mem == "50G" + assert cfg.max_blocks == 4 + assert cfg.constraint == "a100" + assert cfg.environment is not None + assert cfg.environment.modules == ["gromacs/2024.4"] + assert cfg.stage_overrides == {} + + +class TestUserCancellation: + """If a questionary prompt returns None, UserCancelledError is raised.""" + + @patch("mdfactory.orchestration.tui.discover_cluster", return_value=None) + @patch("mdfactory.orchestration.tui._import_questionary") + def test_user_cancellation(self, mock_iq, _discover): + mock_q = mock_iq.return_value + # confirm manual? → None (cancelled) + mock_q.confirm.return_value.ask.return_value = None + + with pytest.raises(UserCancelledError): + configure_slurm_interactive() + + +# --------------------------------------------------------------------------- +# _prompt_stage_overrides tests +# --------------------------------------------------------------------------- + + +class TestPromptStageOverrides: + """Tests for the per-stage override prompt helper.""" + + #: Default full simulation pipeline stages for test calls. + _ALL_STAGES = ("EM", "NVT", "NPT", "Production") + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_declined(self, mock_iq): + """User declines stage overrides → empty dict.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = False + + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=self._ALL_STAGES, + ) + assert result == {} + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_em_cpu_only(self, mock_iq): + """GPU config + accept + EM → gres=None and higher cpus.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = True + mock_q.checkbox.return_value.ask.return_value = ["EM"] + # text prompts for EM: cpus (default 32=16*2), gres (default ""), gmx_binary + mock_q.text.return_value.ask.side_effect = ["32", "", "auto"] + + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=self._ALL_STAGES, + ) + assert "EM" in result + assert result["EM"]["cpus_per_node"] == 32 + assert result["EM"]["gres"] is None + # gmx_binary same as common → not stored + assert "gmx_binary" not in result["EM"] + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_multiple_stages(self, mock_iq): + """Multiple stages selected → each gets correct overrides.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = True + mock_q.checkbox.return_value.ask.return_value = ["EM", "Production"] + # text prompts: EM(cpus, gres, gmx), Production(cpus, gres, gmx) + mock_q.text.return_value.ask.side_effect = [ + "8", # EM cpus + "", # EM gres (empty → None, differs from common None → no diff) + "auto", # EM gmx + "32", # Production cpus + "gpu:a100:2", # Production gres + "auto", # Production gmx + ] + + result = _prompt_stage_overrides( + common_cpus=16, + common_gres=None, + common_gmx="auto", + stages=self._ALL_STAGES, + ) + assert result["EM"] == {"cpus_per_node": 8} + assert result["Production"] == {"cpus_per_node": 32, "gres": "gpu:a100:2"} + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_no_diff_excluded(self, mock_iq): + """Override values same as common → not stored in overrides.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = True + mock_q.checkbox.return_value.ask.return_value = ["NVT"] + # text prompts: all same as common values + mock_q.text.return_value.ask.side_effect = ["16", "gpu:a100:1", "auto"] + + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=self._ALL_STAGES, + ) + # No actual deviations → NVT not in result + assert result == {} + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_empty_selection(self, mock_iq): + """User confirms but selects no stages → empty dict.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = True + mock_q.checkbox.return_value.ask.return_value = [] + + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=self._ALL_STAGES, + ) + assert result == {} + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_cancellation_at_confirm(self, mock_iq): + """User cancels at confirm prompt → raises UserCancelledError.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = None + + with pytest.raises(UserCancelledError): + _prompt_stage_overrides( + common_cpus=16, + common_gres=None, + common_gmx="auto", + stages=self._ALL_STAGES, + ) + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_cancellation_at_checkbox(self, mock_iq): + """User cancels at stage selection → raises UserCancelledError.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = True + mock_q.checkbox.return_value.ask.return_value = None + + with pytest.raises(UserCancelledError): + _prompt_stage_overrides( + common_cpus=16, + common_gres=None, + common_gmx="auto", + stages=self._ALL_STAGES, + ) + + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_gmx_binary_override(self, mock_iq): + """Override gmx_binary for a stage → stored in overrides.""" + mock_q = mock_iq.return_value + mock_q.confirm.return_value.ask.return_value = True + mock_q.checkbox.return_value.ask.return_value = ["Production"] + mock_q.text.return_value.ask.side_effect = ["16", "gpu:a100:1", "gmx_mpi"] + + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=self._ALL_STAGES, + ) + assert result["Production"] == {"gmx_binary": "gmx_mpi"} + + def test_single_stage_skips_overrides(self): + """A single-stage workflow skips overrides entirely — no prompt.""" + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=("Production",), + ) + assert result == {} + + def test_empty_stages_skips_overrides(self): + """Empty stages (build workflow) skips overrides entirely.""" + result = _prompt_stage_overrides( + common_cpus=16, + common_gres="gpu:a100:1", + common_gmx="auto", + stages=(), + ) + assert result == {} + + +class TestStageOverridesIntegration: + """Tests that stage overrides flow through the full wizard paths.""" + + @patch("mdfactory.orchestration.tui._prompt_environment") + @patch("mdfactory.orchestration.tui.discover_cluster", return_value=None) + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_manual_path(self, mock_iq, _discover, mock_prompt_env): + """Manual fallback path produces config with stage overrides.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_q = mock_iq.return_value + mock_prompt_env.return_value = EnvironmentConfig() + + # confirm: proceed with manual → True, stage overrides → True + mock_q.confirm.return_value.ask.side_effect = [True, True] + # text prompts: account, partition, walltime, cpus, gres, mem, qos, + # max_blocks, then stage override prompts for EM + mock_q.text.return_value.ask.side_effect = [ + "acc", + "gpu", + "2:00:00", + "16", + "gpu:a100:1", + "32G", + "", + "4", + # EM stage prompts: cpus, gres, gmx + "32", + "", + "auto", + ] + mock_q.checkbox.return_value.ask.return_value = ["EM"] + + cfg = configure_slurm_interactive() + assert cfg.stage_overrides == { + "EM": {"cpus_per_node": 32, "gres": None}, + } + + @patch("mdfactory.orchestration.tui._prompt_environment") + @patch("mdfactory.orchestration.tui.discover_cluster") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stage_overrides_cluster_path(self, mock_iq, mock_discover, mock_prompt_env): + """Cluster-assisted path produces config with stage overrides.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_q = mock_iq.return_value + mock_discover.return_value = _make_cluster() + mock_prompt_env.return_value = EnvironmentConfig() + + # select prompts: account, partition, walltime, cpus, mem, max_blocks + mock_q.select.return_value.ask.side_effect = [ + "hpc_chem", + "gpu", + "2h", + "16", + "50G", + "4", + ] + # text prompts: gres, constraint, then stage prompts + mock_q.text.return_value.ask.side_effect = [ + "gpu:a100:1", + "", + # EM stage prompts: cpus, gres, gmx + "32", + "", + "auto", + ] + # confirm: QOS → False, stage overrides → True + mock_q.confirm.return_value.ask.side_effect = [False, True] + mock_q.checkbox.return_value.ask.return_value = ["EM"] + + cfg = configure_slurm_interactive() + assert cfg.stage_overrides == { + "EM": {"cpus_per_node": 32, "gres": None}, + } + + +# --------------------------------------------------------------------------- +# _prompt_environment tests +# --------------------------------------------------------------------------- + + +class TestPromptEnvironment: + """Tests for the _prompt_environment helper.""" + + @patch("mdfactory.orchestration.tui.EnvironmentConfig.load_global", return_value=None) + @patch("mdfactory.orchestration.tui._prompt_gromacs_modules") + @patch("mdfactory.orchestration.tui.EnvironmentConfig.detect") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stages_empty_skips_gromacs_prompts(self, mock_iq, mock_detect, mock_gmx, _load_global): + """build workflow (stages=()) skips GROMACS module prompts.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_detect.return_value = EnvironmentConfig() + mock_q = mock_iq.return_value + # extra_init prompt + mock_q.text.return_value.ask.return_value = "" + + result = _prompt_environment(stages=()) + + mock_gmx.assert_not_called() + assert result.modules == [] + + @patch("mdfactory.orchestration.tui.EnvironmentConfig.load_global", return_value=None) + @patch("mdfactory.orchestration.tui._prompt_gromacs_modules") + @patch("mdfactory.orchestration.tui.EnvironmentConfig.detect") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_stages_none_calls_gromacs_prompts(self, mock_iq, mock_detect, mock_gmx, _load_global): + """simulate workflow (stages=None) does prompt for GROMACS modules.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_detect.return_value = EnvironmentConfig() + mock_gmx.return_value = ["gromacs/2024.4"] + mock_q = mock_iq.return_value + # extra_init prompt + mock_q.text.return_value.ask.return_value = "" + + result = _prompt_environment(stages=None) + + mock_gmx.assert_called_once() + assert result.modules == ["gromacs/2024.4"] + + @patch("mdfactory.orchestration.tui.EnvironmentConfig.load_global", return_value=None) + @patch("mdfactory.orchestration.tui._prompt_gromacs_modules") + @patch("mdfactory.orchestration.tui.EnvironmentConfig.detect") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_detected_pixi_flows_through(self, mock_iq, mock_detect, mock_gmx, _load_global): + """Auto-detected pixi_manifest is preserved in returned config.""" + from pathlib import Path + + from mdfactory.orchestration.environment import EnvironmentConfig + + mock_detect.return_value = EnvironmentConfig(pixi_manifest=Path("/project")) + mock_gmx.return_value = [] + mock_q = mock_iq.return_value + mock_q.text.return_value.ask.return_value = "" + + result = _prompt_environment(stages=None) + + assert result.pixi_manifest == Path("/project") + + @patch("mdfactory.orchestration.tui.EnvironmentConfig.load_global") + def test_global_config_skips_prompts(self, mock_load_global): + """When global environment.yaml exists, prompts are skipped entirely.""" + from pathlib import Path + + from mdfactory.orchestration.environment import EnvironmentConfig + + saved_env = EnvironmentConfig( + modules=["gromacs/2024.3-gpu"], + pixi_manifest=Path("/project"), + ) + mock_load_global.return_value = saved_env + + result = _prompt_environment(stages=None) + + assert result is saved_env + assert result.modules == ["gromacs/2024.3-gpu"] + assert result.pixi_manifest == Path("/project") + + +# --------------------------------------------------------------------------- +# save_slurm_config_yaml tests +# --------------------------------------------------------------------------- + + +class TestSaveYaml: + def test_save_slurm_config_yaml(self, tmp_path): + cfg = SlurmExecutorConfig( + account="test_acc", + partition="cpu", + walltime="1h", + cpus_per_node=16, + gres="gpu:v100:1", + max_blocks=3, + ) + out = tmp_path / "slurm.yaml" + save_slurm_config_yaml(cfg, out) + + data = yaml.safe_load(out.read_text()) + assert data["account"] == "test_acc" + assert data["partition"] == "cpu" + assert data["walltime"] == "01:00:00" + assert data["cpus_per_node"] == 16 + assert data["gres"] == "gpu:v100:1" + assert data["max_blocks"] == 3 + assert data["provider"] == "slurm" + + def test_save_yaml_excludes_empty_overrides(self, tmp_path): + """Empty stage_overrides should not appear in the YAML output.""" + cfg = SlurmExecutorConfig( + account="acc", + partition="cpu", + walltime="1h", + cpus_per_node=8, + ) + out = tmp_path / "slurm.yaml" + save_slurm_config_yaml(cfg, out) + + data = yaml.safe_load(out.read_text()) + assert "stage_overrides" not in data + + def test_save_yaml_includes_populated_overrides(self, tmp_path): + """Non-empty stage_overrides should be written to the YAML output.""" + cfg = SlurmExecutorConfig( + account="acc", + partition="gpu", + walltime="2h", + cpus_per_node=16, + gres="gpu:a100:1", + stage_overrides={ + "EM": {"cpus_per_node": 32, "gres": None}, + "Production": {"cpus_per_node": 64}, + }, + ) + out = tmp_path / "slurm.yaml" + save_slurm_config_yaml(cfg, out) + + data = yaml.safe_load(out.read_text()) + assert "stage_overrides" in data + assert data["stage_overrides"]["EM"]["cpus_per_node"] == 32 + assert data["stage_overrides"]["EM"]["gres"] is None + assert data["stage_overrides"]["Production"]["cpus_per_node"] == 64 + + def test_save_yaml_with_environment(self, tmp_path): + """Environment section is written to YAML.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + env = EnvironmentConfig( + modules=["gromacs/2024.3-gpu"], + pixi_manifest="/project/root", + ) + cfg = SlurmExecutorConfig(account="acc", environment=env) + out = tmp_path / "slurm.yaml" + save_slurm_config_yaml(cfg, out) + + data = yaml.safe_load(out.read_text()) + assert "environment" in data + assert data["environment"]["modules"] == ["gromacs/2024.3-gpu"] + assert data["environment"]["pixi_manifest"] == "/project/root" + + def test_save_yaml_empty_environment_excluded(self, tmp_path): + """Empty EnvironmentConfig (all defaults) is excluded from YAML.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + env = EnvironmentConfig() + cfg = SlurmExecutorConfig(account="acc", environment=env) + out = tmp_path / "slurm.yaml" + save_slurm_config_yaml(cfg, out) + + data = yaml.safe_load(out.read_text()) + # An empty environment section should be cleaned up + assert "environment" not in data + + +# --------------------------------------------------------------------------- +# configure_and_save_environment tests +# --------------------------------------------------------------------------- + + +class TestConfigureAndSaveEnvironment: + """Tests for configure_and_save_environment.""" + + @patch("mdfactory.orchestration.tui._prompt_gromacs_modules") + @patch("mdfactory.orchestration.tui.EnvironmentConfig.detect") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_saves_environment_to_global_path( + self, mock_iq, mock_detect, mock_gmx, tmp_path, monkeypatch + ): + """configure_and_save_environment runs wizard and saves result.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path)) + mock_detect.return_value = EnvironmentConfig() + mock_gmx.return_value = ["gromacs/2024.3-gpu"] + mock_q = mock_iq.return_value + mock_q.text.return_value.ask.return_value = "" + + env = configure_and_save_environment() + + assert env.modules == ["gromacs/2024.3-gpu"] + saved_path = tmp_path / "environment.yaml" + assert saved_path.is_file() + loaded = EnvironmentConfig.from_yaml(saved_path) + assert loaded.modules == ["gromacs/2024.3-gpu"] + + @patch("mdfactory.orchestration.tui._prompt_gromacs_modules") + @patch("mdfactory.orchestration.tui.EnvironmentConfig.detect") + @patch("mdfactory.orchestration.tui._import_questionary") + def test_ignores_existing_global_config( + self, mock_iq, mock_detect, mock_gmx, tmp_path, monkeypatch + ): + """configure_and_save_environment always runs wizard even if global exists.""" + from mdfactory.orchestration.environment import EnvironmentConfig + + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path)) + # Write an existing global config + old_env = EnvironmentConfig(modules=["old/module"]) + old_env.save_yaml(tmp_path / "environment.yaml") + + # The wizard should run (ignore_global=True) and produce new config + mock_detect.return_value = EnvironmentConfig() + mock_gmx.return_value = ["new/module"] + mock_q = mock_iq.return_value + mock_q.text.return_value.ask.return_value = "" + + env = configure_and_save_environment() + + assert env.modules == ["new/module"] + loaded = EnvironmentConfig.from_yaml(tmp_path / "environment.yaml") + assert loaded.modules == ["new/module"] + + def test_cancellation_raises(self, tmp_path, monkeypatch): + """configure_and_save_environment propagates UserCancelledError.""" + monkeypatch.setenv("MDFACTORY_CONFIG_DIR", str(tmp_path)) + with patch( + "mdfactory.orchestration.tui._prompt_environment", + side_effect=UserCancelledError("cancelled"), + ): + with pytest.raises(UserCancelledError): + configure_and_save_environment() diff --git a/mdfactory/tests/test_parametrization.py b/mdfactory/tests/test_parametrization.py index 6eceab1..47cbfd6 100644 --- a/mdfactory/tests/test_parametrization.py +++ b/mdfactory/tests/test_parametrization.py @@ -262,6 +262,28 @@ def test_smirnoff_parametrization_water(tmp_path): assert param.parametrization == "smirnoff" +@pytest.mark.skipif(not is_openff_available(), reason="OpenFF not available") +def test_smirnoff_parametrization_water_wat_resname(tmp_path): + """Test SMIRNOFF parametrization of water with non-SOL resname (regression). + + Verifies that water parametrization with resname="WAT" still produces + consistent SOL-based atom types and ITP files. Before the fix, this + caused a KeyError due to mismatched atom type prefixes (WAT_0 vs SOL_0). + """ + config.parameter_store = tmp_path / "parameters" + + spec = SingleMoleculeSpecies(smiles="O", count=1, resname="WAT") + param = parametrize_smirnoff_gromacs(spec) + + assert param.itp.is_file() + # Molecule type should always be SOL regardless of input resname + assert param.moleculetype == "SOL" + assert param.parametrization == "smirnoff" + # Verify the parameter ITP was generated correctly + assert param.parameter_itp is not None + assert param.parameter_itp.is_file() + + @pytest.mark.skipif(not is_openff_available(), reason="OpenFF not available") def test_smirnoff_parametrization_ions(tmp_path): """Test SMIRNOFF parametrization of ions.""" diff --git a/mdfactory/tests/test_prepare.py b/mdfactory/tests/test_prepare.py index b8db411..507a0b4 100644 --- a/mdfactory/tests/test_prepare.py +++ b/mdfactory/tests/test_prepare.py @@ -120,3 +120,90 @@ def test_df_to_models(): assert 1 in errors assert 3 in errors # df.to_csv("test_errors.csv") + + +def _proteinbox_row(tmp_path, **overrides): + pdb = tmp_path / "prot.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + row = { + "simulation_type": "proteinbox", + "engine": "gromacs", + "parametrization": "pdb2gmx", + "parametrization_config.type": "pdb2gmx", + "parametrization_config.forcefield": "charmm36m", + "parametrization_config.water_model": "tip3p", + "system.protein.resname": "LYZ", + "system.protein.pdb_path": str(pdb), + "system.padding": 12.0, + } + row.update(overrides) + return row + + +def test_proteinbox_row_parses_delimited_and_dotted_fields(tmp_path): + row = _proteinbox_row( + tmp_path, + **{ + "system.protein.chains": "H;L;Y", + "system.protein.disulfide_bonds": "CYS6-CYS127;CYS30-CYS115", + "system.protein.protonation_states.HIS15": "HIE", + }, + ) + nested = dict_to_nested_dict_with_species_prefix(row) + protein = nested["system"]["protein"] + assert protein["chains"] == ["H", "L", "Y"] + assert protein["disulfide_bonds"] == [("CYS6", "CYS127"), ("CYS30", "CYS115")] + assert protein["protonation_states"] == {"HIS15": "HIE"} + + inp = BuildInput(**nested) + assert inp.system.protein.chains == ["H", "L", "Y"] + assert inp.system.protein.disulfide_bonds == [("CYS6", "CYS127"), ("CYS30", "CYS115")] + assert inp.system.protein.protonation_states == {"HIS15": "HIE"} + + +def test_proteinbox_single_chain_no_optional_fields(tmp_path): + row = _proteinbox_row(tmp_path, **{"system.protein.chains": "A"}) + inp = BuildInput(**dict_to_nested_dict_with_species_prefix(row)) + assert inp.system.protein.chains == ["A"] + assert inp.system.protein.disulfide_bonds == [] + assert inp.system.protein.protonation_states == {} + + +def test_proteinbox_invalid_disulfide_cell_raises(tmp_path): + row = _proteinbox_row(tmp_path, **{"system.protein.disulfide_bonds": "CYS6_CYS127"}) + with pytest.raises(ValueError, match="Invalid disulfide bond"): + dict_to_nested_dict_with_species_prefix(row) + + +def test_df_to_models_allows_blank_optional_protein_fields(tmp_path): + # Two proteins in one CSV: one with disulfides+protonation, one multi-chain with + # neither. The blank optional cells become NaN but must not fail the NaN guard. + with_extras = _proteinbox_row( + tmp_path, + **{ + "system.protein.chains": "A", + "system.protein.disulfide_bonds": "CYS6-CYS127", + "system.protein.protonation_states.HIS15": "HIE", + }, + ) + multichain = _proteinbox_row( + tmp_path, + **{"system.protein.resname": "INS", "system.protein.chains": "A;B"}, + ) + df = pd.DataFrame(data=[with_extras, multichain]) + models, errors = df_to_build_input_models(df) + assert errors == {} + assert len(models) == 2 + assert models[0].system.protein.disulfide_bonds == [("CYS6", "CYS127")] + assert models[1].system.protein.chains == ["A", "B"] + assert models[1].system.protein.disulfide_bonds == [] + assert models[1].system.protein.protonation_states == {} + + +def test_df_to_models_still_flags_missing_required_field(tmp_path): + good = _proteinbox_row(tmp_path, **{"system.protein.chains": "A"}) + bad = _proteinbox_row(tmp_path, **{"system.protein.chains": "A"}) + del bad["simulation_type"] + df = pd.DataFrame(data=[good, bad]) + with pytest.raises(ValueError, match="NaN"): + df_to_build_input_models(df) diff --git a/mdfactory/tests/test_proteinbox.py b/mdfactory/tests/test_proteinbox.py new file mode 100644 index 0000000..0e239b7 --- /dev/null +++ b/mdfactory/tests/test_proteinbox.py @@ -0,0 +1,974 @@ +# ABOUTME: Tests for proteinbox models, topology parsing, and YAML validation +# ABOUTME: Covers ProteinSpecies, ProteinBoxComposition, Pdb2gmxConfig, and BuildInput +"""Tests for proteinbox models, topology parsing, and YAML validation.""" + +import hashlib +import io +import tarfile +import textwrap +from pathlib import Path + +import MDAnalysis as mda +import numpy as np +import pytest +import yaml + +from mdfactory import workflows +from mdfactory.build import _center_in_cubic_box +from mdfactory.models.composition import ProteinBoxComposition +from mdfactory.models.input import BuildInput +from mdfactory.models.parametrization import Pdb2gmxConfig +from mdfactory.models.species import ProteinSpecies +from mdfactory.setup import protein +from mdfactory.setup.protein import ( + _apply_protonation_states, + _build_disulfide_prompt_input, + _sum_charges_from_itp, + bundle_forcefield_into_topology, + check_forcefield_available, + clean_pdb, + download_forcefield, + run_pdb2gmx, + update_topology_molecules, +) +from mdfactory.workflows import _resolve_proteinbox_pdb_path + + +def _pdb_atom(serial, atom_name, resname, resid, x, y, z, chain="A", insertion_code=""): + return ( + f"ATOM {serial:5d} {atom_name:<4s} {resname:>3s} " + f"{chain:1s}{resid:4d}{insertion_code:1s} " + f"{x:8.3f}{y:8.3f}{z:8.3f} 1.00 0.00\n" + ) + + +def _patch_pdb2gmx_env(monkeypatch): + """Stub the gmx binary, force-field, and environment lookups for run_pdb2gmx.""" + monkeypatch.setattr("mdfactory.setup.protein.check_gmx_available", lambda: Path("/mock/gmx")) + monkeypatch.setattr("mdfactory.setup.protein.check_forcefield_available", lambda ff: None) + monkeypatch.setattr( + "mdfactory.setup.protein.get_gromacs_env", lambda: {"GMXLIB": "/mock/forcefields"} + ) + + +def _make_pdb2gmx_run(molecule_names, extra_files=()): + """Build a fake subprocess.run writing a topology listing the given molecules.""" + + def fake_run(cmd, cwd, text, input, capture_output, timeout, env=None, check=None): + output_dir = Path(cwd) + (output_dir / "processed.gro").write_text("mock gro\n") + molecules = "".join(f"{name} 1\n" for name in molecule_names) + (output_dir / "topol.top").write_text(f"[ molecules ]\n{molecules}") + for name in extra_files: + (output_dir / name).write_text(f"; {name}\n") + + class Result: + returncode = 0 + stdout = "" + stderr = "" + + return Result() + + return fake_run + + +def _make_ff_tarball(dirname): + """Build an in-memory gzip tarball containing a minimal .ff directory.""" + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w:gz") as tar: + content = b"; forcefield\n" + info = tarfile.TarInfo(name=f"{dirname}/forcefield.itp") + info.size = len(content) + tar.addfile(info, io.BytesIO(content)) + return buf.getvalue() + + +class _FakeResponse: + def __init__(self, payload): + self._payload = payload + + def read(self): + return self._payload + + +class TestProteinSpecies: + def test_basic_creation(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + spec = ProteinSpecies(resname="LYZ", pdb_path=pdb) + assert spec.resname == "LYZ" + assert spec.count == 1 + assert spec.fraction == 1.0 + + def test_with_disulfides_and_protonation(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + spec = ProteinSpecies( + resname="LYZ", + pdb_path=pdb, + disulfide_bonds=[("CYS6", "CYS127"), ("CYS30", "CYS115")], + protonation_states={"HIS15": "HIE", "GLU35": "GLH"}, + ) + assert len(spec.disulfide_bonds) == 2 + assert spec.disulfide_bonds[0] == ("CYS6", "CYS127") + assert spec.protonation_states["HIS15"] == "HIE" + + def test_charge_is_none(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + spec = ProteinSpecies(resname="LYZ", pdb_path=pdb) + assert spec.charge is None + + def test_chains_default_empty_and_declared(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + assert ProteinSpecies(resname="LYZ", pdb_path=pdb).chains == [] + spec = ProteinSpecies(resname="LYZ", pdb_path=pdb, chains=["H", "L", "Y"]) + assert spec.chains == ["H", "L", "Y"] + + def test_resname_validation(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + with pytest.raises(ValueError, match="Residue name must be less"): + ProteinSpecies(resname="TOOLONG", pdb_path=pdb) + + def test_rejects_multiple_copies(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + with pytest.raises(ValueError, match="count must be 1"): + ProteinSpecies(resname="LYZ", pdb_path=pdb, count=7) + with pytest.raises(ValueError, match="fraction must be 1.0"): + ProteinSpecies(resname="LYZ", pdb_path=pdb, fraction=0.25) + + def test_rejects_null_count_or_fraction(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + with pytest.raises(ValueError, match="count must be 1"): + ProteinSpecies(resname="LYZ", pdb_path=pdb, count=None) + with pytest.raises(ValueError, match="fraction must be 1.0"): + ProteinSpecies(resname="LYZ", pdb_path=pdb, fraction=None) + + +class TestProteinBoxComposition: + def test_basic_creation(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + comp = ProteinBoxComposition( + protein=ProteinSpecies(resname="LYZ", pdb_path=pdb), + padding=12.0, + ) + assert comp.padding == 12.0 + assert comp.ionization.neutralize is True + assert comp.ionization.concentration == 0.15 + + def test_custom_ionization(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + comp = ProteinBoxComposition( + protein=ProteinSpecies(resname="LYZ", pdb_path=pdb), + ionization={"neutralize": True, "concentration": 0.10}, + ) + assert comp.ionization.concentration == 0.10 + + +class TestPdb2gmxConfig: + def test_defaults(self): + config = Pdb2gmxConfig() + assert config.forcefield == "charmm36m" + assert config.water_model == "tip3p" + assert config.ignore_hydrogens is True + assert config.merge_all is False + + def test_custom(self): + config = Pdb2gmxConfig(forcefield="charmm36", water_model="spce") + assert config.forcefield == "charmm36" + assert config.water_model == "spce" + + def test_frozen(self): + config = Pdb2gmxConfig() + with pytest.raises(Exception): + config.forcefield = "other" + + def test_rejects_non_charmm_forcefield(self): + with pytest.raises(ValueError, match="CHARMM"): + Pdb2gmxConfig(forcefield="amber99sb-ildn") + + def test_rejects_non_three_site_water(self): + with pytest.raises(ValueError, match="3-site"): + Pdb2gmxConfig(water_model="tip4p") + + def test_rejects_ljpme_forcefield(self): + with pytest.raises(ValueError, match="LJ-PME"): + Pdb2gmxConfig(forcefield="charmm36m-ljpme") + + +class TestForceFieldCheck: + def test_available_forcefield_passes(self, tmp_path, monkeypatch): + (tmp_path / "charmm27.ff").mkdir() + monkeypatch.setattr( + "mdfactory.setup.protein._get_gmx_search_paths", + lambda: [tmp_path], + ) + check_forcefield_available("charmm27") + + def test_unavailable_forcefield_raises(self, tmp_path, monkeypatch): + monkeypatch.setattr( + "mdfactory.setup.protein._get_gmx_search_paths", + lambda: [tmp_path], + ) + with pytest.raises(ValueError, match="not found"): + check_forcefield_available("nonexistent_ff_xyz") + + def test_error_lists_available_forcefields(self, tmp_path, monkeypatch): + (tmp_path / "charmm27.ff").mkdir() + monkeypatch.setattr( + "mdfactory.setup.protein._get_gmx_search_paths", + lambda: [tmp_path], + ) + with pytest.raises(ValueError, match="charmm27") as exc_info: + check_forcefield_available("nonexistent_ff_xyz") + assert "Available force fields" in str(exc_info.value) + + def test_registered_forcefield_check_does_not_download(self, tmp_path, monkeypatch): + monkeypatch.setattr( + "mdfactory.setup.protein._get_gmx_search_paths", + lambda: [tmp_path], + ) + monkeypatch.setattr( + "mdfactory.setup.protein.download_forcefield", + lambda name: pytest.fail("check_forcefield_available should not download"), + ) + with pytest.raises(ValueError, match="Downloadable"): + check_forcefield_available("charmm36m") + + +class TestForceFieldDownload: + def _install(self, monkeypatch, tmp_path, payload, sha256): + """Register a stub force field served from an in-memory tarball.""" + monkeypatch.setattr( + "mdfactory.setup.protein.urlopen", + lambda url, timeout=0: _FakeResponse(payload), + ) + monkeypatch.setattr("mdfactory.setup.protein.get_forcefield_dir", lambda: tmp_path) + monkeypatch.setitem( + protein.FORCEFIELD_REGISTRY, + "test-ff", + { + "url": "https://example.invalid/test.ff.tgz", + "dirname": "test.ff", + "sha256": sha256, + "description": "stub force field", + }, + ) + + def test_verifies_matching_sha256_and_extracts(self, tmp_path, monkeypatch): + payload = _make_ff_tarball("test.ff") + self._install(monkeypatch, tmp_path, payload, hashlib.sha256(payload).hexdigest()) + result = download_forcefield("test-ff") + assert result == tmp_path / "test.ff" + assert (result / "forcefield.itp").is_file() + + def test_rejects_mismatched_sha256(self, tmp_path, monkeypatch): + payload = _make_ff_tarball("test.ff") + self._install(monkeypatch, tmp_path, payload, "0" * 64) + with pytest.raises(RuntimeError, match="failed integrity check"): + download_forcefield("test-ff") + assert not (tmp_path / "test.ff").exists() + + +class TestBuildInputProteinbox: + def test_yaml_roundtrip(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + + yaml_str = f""" +simulation_type: proteinbox +engine: gromacs +parametrization: pdb2gmx +parametrization_config: + type: pdb2gmx + forcefield: charmm36m + water_model: tip3p + ignore_hydrogens: true +system: + protein: + resname: LYZ + count: 1 + pdb_path: {pdb} + disulfide_bonds: + - [CYS6, CYS127] + - [CYS30, CYS115] + protonation_states: + HIS15: HIE + padding: 12.0 + ionization: + neutralize: true + concentration: 0.15 +""" + data = yaml.safe_load(yaml_str) + inp = BuildInput(**data) + assert inp.simulation_type == "proteinbox" + assert isinstance(inp.system, ProteinBoxComposition) + assert isinstance(inp.parametrization_config, Pdb2gmxConfig) + assert inp.parametrization == "pdb2gmx" + + def test_default_parametrization_config(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + + data = { + "simulation_type": "proteinbox", + "parametrization": "pdb2gmx", + "system": { + "protein": {"resname": "LYZ", "count": 1, "pdb_path": str(pdb)}, + }, + } + inp = BuildInput(**data) + assert isinstance(inp.parametrization_config, Pdb2gmxConfig) + assert inp.parametrization_config.forcefield == "charmm36m" + + def test_rejects_cgenff_parametrization(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + data = { + "simulation_type": "proteinbox", + "parametrization": "cgenff", + "system": {"protein": {"resname": "LYZ", "count": 1, "pdb_path": str(pdb)}}, + } + with pytest.raises(ValueError, match="not valid for simulation type"): + BuildInput(**data) + + def test_rejects_mismatched_config_type(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + data = { + "simulation_type": "proteinbox", + "parametrization": "pdb2gmx", + "parametrization_config": {"type": "cgenff"}, + "system": {"protein": {"resname": "LYZ", "count": 1, "pdb_path": str(pdb)}}, + } + with pytest.raises(ValueError, match="does not match"): + BuildInput(**data) + + def test_rejects_merge_all_with_declared_chains(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + data = { + "simulation_type": "proteinbox", + "parametrization": "pdb2gmx", + "parametrization_config": {"type": "pdb2gmx", "merge_all": True}, + "system": { + "protein": {"resname": "INS", "pdb_path": str(pdb), "chains": ["A", "B"]}, + }, + } + with pytest.raises(ValueError, match="merge_all"): + BuildInput(**data) + + def test_merge_all_without_chains_allowed(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + data = { + "simulation_type": "proteinbox", + "parametrization": "pdb2gmx", + "parametrization_config": {"type": "pdb2gmx", "merge_all": True}, + "system": {"protein": {"resname": "INS", "pdb_path": str(pdb)}}, + } + inp = BuildInput(**data) + assert inp.parametrization_config.merge_all is True + assert inp.system.protein.chains == [] + + +class TestTopologyParsing: + def test_sum_charges_from_itp(self, tmp_path): + itp = tmp_path / "protein.itp" + itp.write_text( + textwrap.dedent("""\ + [ moleculetype ] + Protein 3 + + [ atoms ] + ; nr type resnr residue atom cgnr charge mass + 1 NH3 1 LYS N 1 -0.300 14.007 + 2 CT 1 LYS CA 2 0.210 12.011 + 3 C 1 LYS C 3 0.510 12.011 + 4 O 1 LYS O 4 -0.510 15.999 + + [ bonds ] + 1 2 + """) + ) + charge = _sum_charges_from_itp(itp) + assert abs(charge - (-0.090)) < 1e-6 + + def test_update_topology_molecules(self, tmp_path): + top = tmp_path / "topol.top" + top.write_text( + textwrap.dedent("""\ + #include "charmm36m.ff/forcefield.itp" + #include "protein.itp" + + [ system ] + Protein in water + + [ molecules ] + Protein_chain_A 1 + """) + ) + update_topology_molecules(top, n_water=5000, num_na=10, num_cl=18) + content = top.read_text() + assert "SOL" in content + assert "5000" in content + assert "NA" in content + assert "10" in content + assert "CL" in content + assert "18" in content + + def test_apply_protonation_states(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text( + textwrap.dedent("""\ + ATOM 1 N HIS A 15 1.000 2.000 3.000 1.00 0.00 + ATOM 2 CA HIS A 15 1.500 2.500 3.500 1.00 0.00 + ATOM 3 N ALA A 16 2.000 3.000 4.000 1.00 0.00 + ATOM 4 N GLU A 35 3.000 4.000 5.000 1.00 0.00 + """) + ) + result = _apply_protonation_states(pdb, {"HIS15": "HIE", "GLU35": "GLH"}, tmp_path) + content = result.read_text() + # HIS at resid 15 should be renamed to HIE + assert "HIE" in content + # GLU at resid 35 should be renamed to GLH + assert "GLH" in content + # ALA at resid 16 should be unchanged + assert "ALA" in content + + def test_apply_protonation_states_translates_charmm_histidine_alias(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "HIS", 15, 1.0, 2.0, 3.0)) + result = _apply_protonation_states(pdb, {"HIS15": "HIE"}, tmp_path, forcefield="charmm36m") + assert "HSE" in result.read_text() + + def test_apply_protonation_states_rejects_four_character_names(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "GLU", 35, 1.0, 2.0, 3.0)) + with pytest.raises(ValueError, match="cannot be written to a PDB"): + _apply_protonation_states(pdb, {"GLU35": "GLUP"}, tmp_path) + + def test_apply_protonation_states_rejects_charmm_acid_protonation(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "GLU", 35, 1.0, 2.0, 3.0)) + # GLH aliases to CHARMM GLUP (4 chars), which cannot fit a PDB column. + with pytest.raises(ValueError, match="cannot be written to a PDB"): + _apply_protonation_states(pdb, {"GLU35": "GLH"}, tmp_path, forcefield="charmm36m") + + def test_apply_protonation_states_matches_existing_histidine_tautomer(self, tmp_path): + # Residue 15 is already an HID tautomer; a "HIS15" key must still match it + # instead of silently doing nothing. + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "HID", 15, 1.0, 2.0, 3.0)) + result = _apply_protonation_states(pdb, {"HIS15": "HIE"}, tmp_path, forcefield="charmm36m") + content = result.read_text() + assert "HSE" in content + assert "HID" not in content + + def test_apply_protonation_states_raises_on_unmatched_override(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "HIS", 15, 1.0, 2.0, 3.0)) + with pytest.raises(ValueError, match="matched no residue"): + _apply_protonation_states(pdb, {"HIS99": "HIE"}, tmp_path) + + def test_apply_protonation_states_rejects_ambiguous_unqualified_key(self, tmp_path): + # HIS15 exists in both chain A and chain B; an unqualified key cannot say which. + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "N", "HIS", 15, 1.0, 2.0, 3.0, chain="A"), + _pdb_atom(2, "N", "HIS", 15, 4.0, 5.0, 6.0, chain="B"), + ] + ) + ) + with pytest.raises(ValueError, match="ambiguous across chains"): + _apply_protonation_states(pdb, {"HIS15": "HIE"}, tmp_path) + + def test_apply_protonation_states_chain_qualified_key_targets_one_chain(self, tmp_path): + # A chain-qualified key renames only the residue in that chain. + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "N", "HIS", 15, 1.0, 2.0, 3.0, chain="A"), + _pdb_atom(2, "N", "HIS", 15, 4.0, 5.0, 6.0, chain="B"), + ] + ) + ) + result = _apply_protonation_states(pdb, {"A:HIS15": "HIE"}, tmp_path) + lines = [ln for ln in result.read_text().splitlines() if ln.startswith("ATOM")] + by_chain = {ln[21]: ln[17:20].strip() for ln in lines} + assert by_chain["A"] == "HIE" + assert by_chain["B"] == "HIS" + + def test_apply_protonation_states_resolves_insertion_code(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "N", "HIS", 100, 1.0, 2.0, 3.0, chain="H"), + _pdb_atom( + 2, + "N", + "HIS", + 100, + 4.0, + 5.0, + 6.0, + chain="H", + insertion_code="A", + ), + ] + ) + ) + result = _apply_protonation_states(pdb, {"H:HIS100A": "HIE"}, tmp_path) + lines = [ln for ln in result.read_text().splitlines() if ln.startswith("ATOM")] + by_insertion_code = {ln[26]: ln[17:20].strip() for ln in lines} + assert by_insertion_code[" "] == "HIS" + assert by_insertion_code["A"] == "HIE" + + result = _apply_protonation_states(pdb, {"H:HIS100": "HID"}, tmp_path) + lines = [ln for ln in result.read_text().splitlines() if ln.startswith("ATOM")] + by_insertion_code = {ln[26]: ln[17:20].strip() for ln in lines} + assert by_insertion_code[" "] == "HID" + assert by_insertion_code["A"] == "HIS" + + def test_build_disulfide_prompt_input_answers_exact_requested_pairs(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 1, 0.0, 0.0, 0.0), + _pdb_atom(2, "SG", "CYS", 4, 2.0, 0.0, 0.0), + _pdb_atom(3, "SG", "CYS", 2, 10.0, 0.0, 0.0), + _pdb_atom(4, "SG", "CYS", 3, 12.0, 0.0, 0.0), + ] + ) + ) + assert _build_disulfide_prompt_input(pdb, [("CYS2", "CYS3")]) == "n\ny\n" + + def test_build_disulfide_prompt_input_rejects_missing_pair(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 6, 0.0, 0.0, 0.0), + _pdb_atom(2, "SG", "CYS", 127, 8.0, 0.0, 0.0), + ] + ) + ) + with pytest.raises(ValueError, match="not detected as close CYS SG pairs"): + _build_disulfide_prompt_input(pdb, [("CYS6", "CYS127")]) + + def test_build_disulfide_prompt_input_rejects_ambiguous_reference(self, tmp_path): + # CYS7 exists in both chains; an unqualified reference is ambiguous. + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 7, 0.0, 0.0, 0.0, chain="A"), + _pdb_atom(2, "SG", "CYS", 7, 2.0, 0.0, 0.0, chain="B"), + ] + ) + ) + with pytest.raises(ValueError, match="ambiguous across chains"): + _build_disulfide_prompt_input(pdb, [("CYS7", "CYS7")]) + + def test_build_disulfide_prompt_input_chain_qualified_inter_chain_bond(self, tmp_path): + # An inter-chain disulfide (e.g. insulin A7-B7) is expressed with chain-qualified + # references and matched as a close SG pair across chains. + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 7, 0.0, 0.0, 0.0, chain="A"), + _pdb_atom(2, "SG", "CYS", 7, 2.0, 0.0, 0.0, chain="B"), + _pdb_atom(3, "SG", "CYS", 20, 20.0, 0.0, 0.0, chain="A"), + ] + ) + ) + assert _build_disulfide_prompt_input(pdb, [("A:CYS7", "B:CYS7")]) == "y\n" + + def test_build_disulfide_prompt_input_preserves_pdb_chain_order(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 6, 0.0, 0.0, 0.0, chain="B"), + _pdb_atom(2, "SG", "CYS", 127, 2.0, 0.0, 0.0, chain="B"), + _pdb_atom(3, "SG", "CYS", 6, 10.0, 0.0, 0.0, chain="A"), + _pdb_atom(4, "SG", "CYS", 127, 12.0, 0.0, 0.0, chain="A"), + ] + ) + ) + assert _build_disulfide_prompt_input(pdb, [("B:CYS6", "B:CYS127")]) == "y\nn\n" + + def test_build_disulfide_prompt_input_resolves_insertion_code(self, tmp_path): + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 100, 0.0, 0.0, 0.0, chain="H"), + _pdb_atom( + 2, + "SG", + "CYS", + 100, + 2.0, + 0.0, + 0.0, + chain="H", + insertion_code="A", + ), + ] + ) + ) + assert _build_disulfide_prompt_input(pdb, [("H:CYS100A", "H:CYS100")]) == "y\n" + + def test_run_pdb2gmx_passes_disulfide_answers_to_subprocess(self, tmp_path, monkeypatch): + pdb = tmp_path / "input.pdb" + pdb.write_text( + "".join( + [ + _pdb_atom(1, "SG", "CYS", 1, 0.0, 0.0, 0.0), + _pdb_atom(2, "SG", "CYS", 4, 2.0, 0.0, 0.0), + _pdb_atom(3, "SG", "CYS", 2, 10.0, 0.0, 0.0), + _pdb_atom(4, "SG", "CYS", 3, 12.0, 0.0, 0.0), + ] + ) + ) + calls = {} + + def fake_run(cmd, cwd, text, input, capture_output, timeout, env=None, check=None): + calls["cmd"] = cmd + calls["cwd"] = cwd + calls["input"] = input + calls["env"] = env + calls["check"] = check + output_dir = Path(cwd) + (output_dir / "processed.gro").write_text("mock gro\n") + (output_dir / "topol.top").write_text( + "[ moleculetype ]\nProtein_chain_A 3\n[ atoms ]\n[ molecules ]\nProtein_chain_A 1\n" + ) + (output_dir / "posre.itp").write_text("mock posre\n") + + class Result: + returncode = 0 + stdout = "" + stderr = "" + + return Result() + + monkeypatch.setattr("mdfactory.setup.protein.subprocess.run", fake_run) + _patch_pdb2gmx_env(monkeypatch) + + run_pdb2gmx( + pdb_path=pdb, + config=Pdb2gmxConfig(forcefield="charmm36m", water_model="tip3p"), + disulfide_bonds=[("CYS2", "CYS3")], + protonation_states={}, + output_dir=tmp_path / "pdb2gmx_output", + ) + + assert "-ss" in calls["cmd"] + assert calls["cmd"][:2] == ["/mock/gmx", "pdb2gmx"] + assert calls["input"] == "n\ny\n" + assert calls["cwd"] == str((tmp_path / "pdb2gmx_output").resolve()) + assert calls["env"] == {"GMXLIB": "/mock/forcefields"} + assert calls["check"] is False + + def test_run_pdb2gmx_multichain_collects_chain_includes(self, tmp_path, monkeypatch): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "ALA", 1, 0.0, 0.0, 0.0)) + chains = ["H", "L", "Y"] + extra = [f"topol_Protein_chain_{c}.itp" for c in chains] + [ + f"posre_Protein_chain_{c}.itp" for c in chains + ] + monkeypatch.setattr( + "mdfactory.setup.protein.subprocess.run", + _make_pdb2gmx_run([f"Protein_chain_{c}" for c in chains], extra_files=extra), + ) + _patch_pdb2gmx_env(monkeypatch) + + params = run_pdb2gmx( + pdb_path=pdb, + config=Pdb2gmxConfig(forcefield="charmm36m", water_model="tip3p"), + disulfide_bonds=[], + protonation_states={}, + output_dir=tmp_path / "out", + chains=chains, + ) + + assert params.chains == chains + assert sorted(p.name for p in params.topology_include_files) == sorted(extra) + + def test_run_pdb2gmx_multichain_without_declaration_raises(self, tmp_path, monkeypatch): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "ALA", 1, 0.0, 0.0, 0.0)) + monkeypatch.setattr( + "mdfactory.setup.protein.subprocess.run", + _make_pdb2gmx_run(["Protein_chain_H", "Protein_chain_L"]), + ) + _patch_pdb2gmx_env(monkeypatch) + + with pytest.raises(ValueError, match="none were declared"): + run_pdb2gmx( + pdb_path=pdb, + config=Pdb2gmxConfig(forcefield="charmm36m", water_model="tip3p"), + disulfide_bonds=[], + protonation_states={}, + output_dir=tmp_path / "out", + ) + + def test_run_pdb2gmx_chain_mismatch_raises(self, tmp_path, monkeypatch): + pdb = tmp_path / "input.pdb" + pdb.write_text(_pdb_atom(1, "N", "ALA", 1, 0.0, 0.0, 0.0)) + monkeypatch.setattr( + "mdfactory.setup.protein.subprocess.run", + _make_pdb2gmx_run(["Protein_chain_H", "Protein_chain_L", "Protein_chain_Y"]), + ) + _patch_pdb2gmx_env(monkeypatch) + + with pytest.raises(ValueError, match="do not match"): + run_pdb2gmx( + pdb_path=pdb, + config=Pdb2gmxConfig(forcefield="charmm36m", water_model="tip3p"), + disulfide_bonds=[], + protonation_states={}, + output_dir=tmp_path / "out", + chains=["H", "L"], + ) + + +class TestBundleForcefield: + def test_copies_ff_dir_and_rewrites_absolute_includes(self, tmp_path): + ff_source = tmp_path / "ffsource" / "charmm36-feb2026_cgenff-5.0.ff" + ff_source.mkdir(parents=True) + (ff_source / "forcefield.itp").write_text("; forcefield\n") + (ff_source / "tip3p.itp").write_text("; water\n") + + build_dir = tmp_path / "build" + build_dir.mkdir() + top = build_dir / "topology.top" + top.write_text( + textwrap.dedent(f"""\ + #include "{ff_source / "forcefield.itp"}" + #include "protein.itp" + #include "{ff_source / "tip3p.itp"}" + #include "posre.itp" + """) + ) + + ff_name = bundle_forcefield_into_topology(top) + + assert ff_name == "charmm36-feb2026_cgenff-5.0.ff" + assert (build_dir / ff_name / "forcefield.itp").is_file() + assert (build_dir / ff_name / "tip3p.itp").is_file() + + content = top.read_text() + assert '#include "charmm36-feb2026_cgenff-5.0.ff/forcefield.itp"' in content + assert '#include "charmm36-feb2026_cgenff-5.0.ff/tip3p.itp"' in content + # Relative includes are left untouched. + assert '#include "protein.itp"' in content + assert '#include "posre.itp"' in content + # No absolute paths remain. + assert str(ff_source) not in content + + def test_copies_ff_dir_once_for_multiple_includes(self, tmp_path, monkeypatch): + ff_source = tmp_path / "ffsource" / "charmm36-feb2026_cgenff-5.0.ff" + ff_source.mkdir(parents=True) + for name in ("forcefield.itp", "tip3p.itp", "ions.itp"): + (ff_source / name).write_text(f"; {name}\n") + + build_dir = tmp_path / "build" + build_dir.mkdir() + top = build_dir / "topology.top" + top.write_text( + textwrap.dedent(f"""\ + #include "{ff_source / "forcefield.itp"}" + #include "{ff_source / "tip3p.itp"}" + #include "{ff_source / "ions.itp"}" + """) + ) + + calls = [] + real_copytree = protein.shutil.copytree + + def counting_copytree(src, dst, *args, **kwargs): + calls.append((src, dst)) + return real_copytree(src, dst, *args, **kwargs) + + monkeypatch.setattr(protein.shutil, "copytree", counting_copytree) + + bundle_forcefield_into_topology(top) + + # Three includes into the same .ff directory, copied exactly once. + assert len(calls) == 1 + content = top.read_text() + assert '#include "charmm36-feb2026_cgenff-5.0.ff/ions.itp"' in content + + def test_no_absolute_ff_include_returns_none(self, tmp_path): + top = tmp_path / "topology.top" + top.write_text('#include "protein.itp"\n#include "posre.itp"\n') + original = top.read_text() + + assert bundle_forcefield_into_topology(top) is None + assert top.read_text() == original + + def test_bundles_relative_ff_include(self, tmp_path, monkeypatch): + # Built-in force fields emit relative includes like charmm27.ff/forcefield.itp; + # the directory must be resolved from a search path and copied next to the topology. + ff_source = tmp_path / "gmxtop" / "charmm27.ff" + ff_source.mkdir(parents=True) + (ff_source / "forcefield.itp").write_text("; forcefield\n") + monkeypatch.setattr( + "mdfactory.setup.protein._get_gmx_search_paths", + lambda: [tmp_path / "gmxtop"], + ) + + build_dir = tmp_path / "build" + build_dir.mkdir() + top = build_dir / "topology.top" + top.write_text('#include "charmm27.ff/forcefield.itp"\n#include "posre.itp"\n') + + ff_name = bundle_forcefield_into_topology(top) + + assert ff_name == "charmm27.ff" + assert (build_dir / "charmm27.ff" / "forcefield.itp").is_file() + # A relative include already resolves from the topology directory once the + # force field sits alongside it, so the line is left untouched. + assert '#include "charmm27.ff/forcefield.itp"' in top.read_text() + + def test_relative_ff_include_not_found_raises(self, tmp_path, monkeypatch): + monkeypatch.setattr( + "mdfactory.setup.protein._get_gmx_search_paths", + lambda: [tmp_path / "empty"], + ) + top = tmp_path / "topology.top" + top.write_text('#include "charmm27.ff/forcefield.itp"\n') + with pytest.raises(RuntimeError, match="not found in any GROMACS search path"): + bundle_forcefield_into_topology(top) + + +class TestCleanPdb: + def test_preserves_chain_and_residue_numbers(self, tmp_path): + # A residue in chain H numbered 15: cleaning must not renumber it to A/1, + # or later chain/disulfide/protonation references stop resolving. + pdb = tmp_path / "in.pdb" + pdb.write_text( + "ATOM 1 N ALA H 15A 0.000 0.000 0.000 1.00 0.00 N\n" + "ATOM 2 CA ALA H 15A 1.458 0.000 0.000 1.00 0.00 C\n" + "ATOM 3 C ALA H 15A 2.009 1.420 0.000 1.00 0.00 C\n" + "ATOM 4 O ALA H 15A 1.251 2.390 0.000 1.00 0.00 O\n" + "ATOM 5 CB ALA H 15A 1.988 -0.773 1.199 1.00 0.00 C\n" + "TER\n" + "END\n" + ) + out = clean_pdb(pdb, tmp_path / "clean.pdb") + atom_lines = [ln for ln in out.read_text().splitlines() if ln.startswith("ATOM")] + assert atom_lines + assert all(ln[21] == "H" for ln in atom_lines) + assert all(ln[22:26].strip() == "15" for ln in atom_lines) + assert all(ln[26] == "A" for ln in atom_lines) + + +class TestMetadata: + def test_proteinbox_metadata(self, tmp_path): + pdb = tmp_path / "test.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + data = { + "simulation_type": "proteinbox", + "parametrization": "pdb2gmx", + "system": { + "protein": {"resname": "LYZ", "count": 1, "pdb_path": str(pdb)}, + "padding": 12.0, + }, + } + inp = BuildInput(**data) + meta = inp.metadata + assert meta["simulation_type"] == "proteinbox" + assert meta["total_count"] == 1 + assert "padding" in meta["system_specific"] + assert meta["system_specific"]["padding"] == 12.0 + + +class TestPdbPathResolution: + def test_relative_pdb_path_resolved_against_yaml_dir(self, tmp_path): + base = tmp_path / "specs" + base.mkdir() + dct = { + "simulation_type": "proteinbox", + "system": {"protein": {"pdb_path": "1aki.pdb"}}, + } + _resolve_proteinbox_pdb_path(dct, base) + assert dct["system"]["protein"]["pdb_path"] == str((base / "1aki.pdb").resolve()) + + def test_absolute_pdb_path_unchanged(self, tmp_path): + abs_path = str((tmp_path / "abs.pdb").resolve()) + dct = { + "simulation_type": "proteinbox", + "system": {"protein": {"pdb_path": abs_path}}, + } + _resolve_proteinbox_pdb_path(dct, tmp_path / "other") + assert dct["system"]["protein"]["pdb_path"] == abs_path + + def test_non_proteinbox_untouched(self, tmp_path): + dct = {"simulation_type": "mixedbox", "system": {"foo": "bar"}} + _resolve_proteinbox_pdb_path(dct, tmp_path) + assert dct == {"simulation_type": "mixedbox", "system": {"foo": "bar"}} + + def test_run_build_from_file_resolves_relative_path(self, tmp_path, monkeypatch): + pdb = tmp_path / "1aki.pdb" + pdb.write_text("ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00\n") + yaml_file = tmp_path / "build.yaml" + yaml_file.write_text( + textwrap.dedent("""\ + simulation_type: proteinbox + parametrization: pdb2gmx + system: + protein: + resname: LYZ + count: 1 + pdb_path: 1aki.pdb + padding: 12.0 + """) + ) + captured = {} + monkeypatch.setattr(workflows, "run_build_from_dict", captured.update) + workflows.run_build_from_file(yaml_file) + assert captured["system"]["protein"]["pdb_path"] == str(pdb.resolve()) + + +class TestCenterInCubicBox: + def test_padding_guaranteed_on_every_face(self): + # Asymmetric molecule: long in x, short in y/z, offset from the origin. + positions = np.array( + [ + [0.0, 0.0, 0.0], + [40.0, 5.0, 2.0], + [10.0, -3.0, 8.0], + ], + dtype=float, + ) + u = mda.Universe.empty(len(positions), trajectory=True) + u.atoms.positions = positions + padding = 12.0 + box_size = _center_in_cubic_box(u, padding) + + pos = u.atoms.positions + bbox_min = pos.min(axis=0) + bbox_max = pos.max(axis=0) + # Every face is at least `padding` from the protein. + assert bbox_min.min() >= padding - 1e-3 + assert (box_size - bbox_max.max()) >= padding - 1e-3 + # The longest-extent axis sits exactly `padding` from both faces. + long_axis = int(np.argmax(bbox_max - bbox_min)) + assert abs(bbox_min[long_axis] - padding) < 1e-3 + assert abs((box_size - bbox_max[long_axis]) - padding) < 1e-3 diff --git a/mdfactory/tests/test_rescue_integration.py b/mdfactory/tests/test_rescue_integration.py new file mode 100644 index 0000000..da2cc8e --- /dev/null +++ b/mdfactory/tests/test_rescue_integration.py @@ -0,0 +1,330 @@ +# ABOUTME: Integration tests for rescue retry with real GROMACS execution. +# ABOUTME: Validates that oversized dt crashes NVT at tier 0 and succeeds after rescue. +"""Integration tests: rescue retry with real GROMACS. + +These tests require ``gmx`` on PATH and are marked ``slow`` — they are +excluded from the default test suite and must be run explicitly:: + + pixi run pytest -m slow mdfactory/tests/test_rescue_integration.py -v +""" + +from __future__ import annotations + +import shutil +import subprocess +import textwrap +from pathlib import Path + +import pytest + +# --------------------------------------------------------------------------- +# Skip entire module if GROMACS is not available +# --------------------------------------------------------------------------- + +GMX = shutil.which("gmx") or shutil.which("gmx_mpi") + +pytestmark = [ + pytest.mark.slow, + pytest.mark.skipif(GMX is None, reason="GROMACS (gmx) not found on PATH"), +] + +FIXTURE_DIR = Path(__file__).parent / "fixtures" / "rescue_test" + +# Force fields to try, in preference order. Each entry is +# (forcefield_dir, water_include) — we pick the first one that exists +# in the GROMACS data directory. +_FF_CANDIDATES = [ + ("oplsaa.ff", "oplsaa.ff/spce.itp"), + ("amber99sb-ildn.ff", "amber99sb-ildn.ff/spce.itp"), + ("gromos54a7.ff", "gromos54a7.ff/spc.itp"), + ("charmm27.ff", "charmm27.ff/spc.itp"), +] + + +def _find_gmx_topdir() -> Path | None: + """Discover the GROMACS topology data directory. + + Returns + ------- + Path or None + Path to the ``top`` directory (e.g. ``/usr/share/gromacs/top``), + or None if it cannot be determined. + """ + # GMXLIB takes precedence + import os + + gmxlib = os.environ.get("GMXLIB") + if gmxlib: + p = Path(gmxlib) + if p.is_dir(): + return p + + # Parse 'gmx --version' for the data prefix + try: + result = subprocess.run( + [GMX, "--version"], + capture_output=True, + text=True, + timeout=10, + check=False, + ) + for line in result.stdout.splitlines(): + if "Data prefix" in line: + prefix = line.split(":", 1)[1].strip() + topdir = Path(prefix) / "share" / "gromacs" / "top" + if topdir.is_dir(): + return topdir + except Exception: + pass + + return None + + +def _detect_forcefield() -> tuple[str, str]: + """Return (forcefield_itp, water_itp) for the first available FF. + + Raises + ------ + pytest.skip + If no supported force field is found. + """ + topdir = _find_gmx_topdir() + if topdir is None: + pytest.skip("Cannot locate GROMACS topology data directory") + + for ff_dir, water_itp in _FF_CANDIDATES: + if (topdir / ff_dir).is_dir(): + ff_itp = f"{ff_dir}/forcefield.itp" + return ff_itp, water_itp + + available = [d.name for d in topdir.iterdir() if d.suffix == ".ff"] + pytest.skip(f"No supported force field found in {topdir}. Available: {available}") + + +def _write_topology(dest: Path, ff_itp: str, water_itp: str) -> None: + """Write a minimal SPC water topology file.""" + dest.write_text( + textwrap.dedent(f"""\ + ; Auto-generated topology for rescue integration test + #include "{ff_itp}" + #include "{water_itp}" + + [ system ] + Rescue integration test + + [ molecules ] + SOL 3 + """) + ) + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def sim_dir(tmp_path: Path) -> Path: + """Copy fixture files to a temp directory with a working topology. + + Copies ``system.gro``, ``em.mdp``, and ``nvt.mdp`` from the static + fixtures, then generates ``topology.top`` using whichever force field + is available in the local GROMACS installation. + """ + # Copy static fixtures + for name in ("system.gro", "em.mdp", "nvt.mdp"): + shutil.copy(FIXTURE_DIR / name, tmp_path / name) + + # Generate topology with detected force field + ff_itp, water_itp = _detect_forcefield() + _write_topology(tmp_path / "topology.top", ff_itp, water_itp) + + return tmp_path + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +class TestRescueIntegrationNVT: + """Verify NVT rescue with real GROMACS execution.""" + + def _run_gmx( + self, + args: list[str], + cwd: Path, + ) -> subprocess.CompletedProcess: + """Run a gmx command and return the result.""" + return subprocess.run( + [GMX, *args], + cwd=cwd, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + + def _run_grompp( + self, sim_dir: Path, mdp: str = "nvt.mdp", tpr: str = "nvt.tpr" + ) -> subprocess.CompletedProcess: + """Run grompp and return the result.""" + return self._run_gmx( + [ + "grompp", + "-f", + mdp, + "-c", + "system.gro", + "-p", + "topology.top", + "-o", + tpr, + "-maxwarn", + "10", + ], + cwd=sim_dir, + ) + + def _run_mdrun(self, sim_dir: Path, deffnm: str = "nvt") -> subprocess.CompletedProcess: + """Run mdrun and return the result.""" + return self._run_gmx( + ["mdrun", "-deffnm", deffnm, "-ntomp", "1"], + cwd=sim_dir, + ) + + def _run_em(self, sim_dir: Path) -> subprocess.CompletedProcess: + """Run energy minimisation to prepare a valid starting structure.""" + grompp = self._run_grompp(sim_dir, mdp="em.mdp", tpr="em.tpr") + assert grompp.returncode == 0, f"EM grompp failed: {grompp.stderr}" + mdrun = self._run_gmx( + ["mdrun", "-deffnm", "em", "-ntomp", "1"], + cwd=sim_dir, + ) + assert mdrun.returncode == 0, f"EM mdrun failed: {mdrun.stderr}" + # Use EM output as starting structure for NVT + shutil.copy(sim_dir / "em.gro", sim_dir / "system.gro") + return mdrun + + def test_grompp_succeeds(self, sim_dir: Path): + """Sanity check: grompp accepts the fixture files for NVT.""" + self._run_em(sim_dir) + result = self._run_grompp(sim_dir) + assert result.returncode == 0, ( + f"grompp failed — the fixture files may be invalid.\nstderr: {result.stderr}" + ) + assert (sim_dir / "nvt.tpr").exists() + + def test_nvt_crashes_at_tier_0(self, sim_dir: Path): + """The oversized dt (0.1 ps) causes a LINCS/constraint failure.""" + self._run_em(sim_dir) + grompp = self._run_grompp(sim_dir) + assert grompp.returncode == 0, f"grompp failed: {grompp.stderr}" + + mdrun = self._run_mdrun(sim_dir) + assert mdrun.returncode != 0, ( + "mdrun succeeded with dt=0.1 — the fixture is not " + "triggering a crash. Increase dt or adjust the system." + ) + + # Confirm it's a physics failure, not a missing-file error + combined = mdrun.stdout + mdrun.stderr + log_file = sim_dir / "nvt.log" + if log_file.exists(): + combined += log_file.read_text() + + physics_markers = [ + "LINCS", + "SETTLE", + "moved more than", + "blowing up", + "coordinate constraints", + "Abnorm", + "not finite", + "can not continue", + "too large", + "Bond/angle/dihedral", + ] + assert any(m in combined for m in physics_markers), ( + f"mdrun failed but not with a recognisable physics error.\n" + f"Output (last 500 chars): ...{combined[-500:]}" + ) + + def test_apply_rescue_tier_produces_valid_mdp(self, sim_dir: Path): + """apply_rescue_tier writes a rescue MDP with halved dt.""" + from mdfactory.orchestration.mdp import ( + apply_rescue_tier, + get_mdp_value, + parse_mdp, + ) + + rescue_path = apply_rescue_tier(sim_dir, "NVT", tier=1) + + assert rescue_path.exists() + assert rescue_path.name == "nvt_rescue_t1.mdp" + + parsed = parse_mdp(rescue_path) + dt = float(get_mdp_value(parsed, "dt")) + nsteps = int(get_mdp_value(parsed, "nsteps")) + + assert dt == pytest.approx(0.05) # 0.1 / 2^1 + assert nsteps == 200 # 100 * 2^1 + + def test_nvt_succeeds_with_rescue_tier(self, sim_dir: Path): + """NVT runs when rescue reduces dt sufficiently. + + Tries rescue tiers 1 through 6 until one succeeds. At least one + tier must complete without error for the test to pass — this + validates the core premise that halving the timestep eventually + stabilises the dynamics. + """ + from mdfactory.orchestration.mdp import apply_rescue_tier + + self._run_em(sim_dir) + + max_tier = 6 + for tier in range(1, max_tier + 1): + rescue_mdp = apply_rescue_tier(sim_dir, "NVT", tier=tier) + + # Clean previous run outputs + for f in sim_dir.glob("nvt.*"): + if f.suffix != ".mdp": + f.unlink() + + grompp = self._run_grompp(sim_dir, mdp=rescue_mdp.name) + assert grompp.returncode == 0, f"grompp failed at tier {tier}: {grompp.stderr}" + + mdrun = self._run_mdrun(sim_dir) + if mdrun.returncode == 0: + return + + pytest.fail( + f"NVT did not succeed at any rescue tier (1–{max_tier}). " + f"Last dt = {0.1 / 2**max_tier}. " + f"Consider adjusting the fixture." + ) + + def test_classify_failure_detects_physics(self, sim_dir: Path): + """classify_failure correctly identifies the crash as PHYSICS.""" + from mdfactory.orchestration.errors import FailureType, classify_failure + + self._run_em(sim_dir) + grompp = self._run_grompp(sim_dir) + if grompp.returncode != 0: + pytest.skip(f"grompp failed: {grompp.stderr}") + + mdrun = self._run_mdrun(sim_dir) + if mdrun.returncode == 0: + pytest.skip("mdrun did not crash — cannot test classification") + + error_text = mdrun.stderr + mdrun.stdout + log_file = sim_dir / "nvt.log" + if log_file.exists(): + error_text += log_file.read_text() + + exc = RuntimeError(error_text) + + result = classify_failure(exc, sim_dir=sim_dir, stage="NVT") + assert result == FailureType.PHYSICS, ( + f"Expected PHYSICS, got {result}. Error text (last 300 chars): ...{error_text[-300:]}" + ) diff --git a/mdfactory/tests/test_settings.py b/mdfactory/tests/test_settings.py index 0eb0159..b918aac 100644 --- a/mdfactory/tests/test_settings.py +++ b/mdfactory/tests/test_settings.py @@ -25,6 +25,7 @@ def test_defaults_loaded(): assert "csv" in s.config assert "foundry" in s.config assert "cgenff" in s.config + assert "gromacs" in s.config assert "storage" in s.config assert s.config["database"]["TYPE"] == "sqlite" @@ -53,6 +54,25 @@ def test_cgenff_dir_default(): assert s.cgenff_dir is not None +def test_gromacs_env_prepends_forcefield_dir(tmp_path, monkeypatch): + """gromacs_env should add the configured force field directory to GMXLIB.""" + monkeypatch.setenv("MDFACTORY_DATA_DIR", str(tmp_path / "data")) + s = Settings() + env = s.gromacs_env({"GMXLIB": "/existing/path"}) + assert env["GMXLIB"].split(":") == [ + str(tmp_path / "data" / "forcefields"), + "/existing/path", + ] + + +def test_config_template_documents_gromacs_section(): + """The sample config should expose all user-facing GROMACS settings.""" + template = Path("config_templates/config.ini").read_text() + assert "[gromacs]" in template + assert "GMX_PATH =" in template + assert "FORCEFIELD_DIR =" in template + + def test_reload(): """reload() should re-read config from disk.""" s = Settings() diff --git a/mdfactory/tests/test_slurm_config.py b/mdfactory/tests/test_slurm_config.py new file mode 100644 index 0000000..2c0d33b --- /dev/null +++ b/mdfactory/tests/test_slurm_config.py @@ -0,0 +1,386 @@ +# ABOUTME: Unit tests for BaseSlurmConfig, SlurmConfig, and normalize_slurm_time. +# ABOUTME: Uses mocked discover_cluster() so tests run on non-SLURM machines. +"""Unit tests for mdfactory.performance.slurm_config.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest +import yaml + +from mdfactory.performance import cluster as cluster_mod +from mdfactory.performance.slurm_config import ( + BaseSlurmConfig, + SlurmConfig, + normalize_slurm_time, +) + +# --------------------------------------------------------------------------- +# normalize_slurm_time +# --------------------------------------------------------------------------- + + +class TestNormalizeSlurmTime: + """Edge-case coverage for the time-string normaliser.""" + + def test_hours_shorthand(self): + assert normalize_slurm_time("2h") == "02:00:00" + + def test_hours_single_digit(self): + assert normalize_slurm_time("1h") == "01:00:00" + + def test_hours_large(self): + assert normalize_slurm_time("24h") == "24:00:00" + + def test_minutes_shorthand(self): + assert normalize_slurm_time("30m") == "00:30:00" + + def test_minutes_overflow(self): + assert normalize_slurm_time("90m") == "01:30:00" + + def test_minutes_as_integer(self): + assert normalize_slurm_time("90") == "01:30:00" + assert normalize_slurm_time("120") == "02:00:00" + + def test_days_shorthand(self): + assert normalize_slurm_time("1d") == "1-00:00:00" + assert normalize_slurm_time("3d") == "3-00:00:00" + + def test_hms_passthrough(self): + assert normalize_slurm_time("01:00:00") == "01:00:00" + assert normalize_slurm_time("3-00:00:00") == "3-00:00:00" + + def test_strips_whitespace(self): + assert normalize_slurm_time(" 2h ") == "02:00:00" + + +# --------------------------------------------------------------------------- +# SlurmConfig — Pydantic model behaviour +# --------------------------------------------------------------------------- + + +class TestSlurmConfigModel: + """Tests for the SlurmConfig Pydantic model (no SLURM required).""" + + def test_minimal_construction(self): + cfg = SlurmConfig(account="mygroup") + assert cfg.account == "mygroup" + assert cfg.partition == "cpu" + assert cfg.time == "02:00:00" # "2h" normalised on construction + assert cfg.cpus_per_task == 4 + assert cfg.mem_gb == 8 + assert cfg.job_name_prefix == "mdfactory-analysis" + assert cfg.qos is None + assert cfg.constraint is None + + def test_time_normalised_on_construction(self): + cfg = SlurmConfig(account="grp", time="4h") + assert cfg.time == "04:00:00" + + def test_time_hms_passthrough(self): + cfg = SlurmConfig(account="grp", time="02:00:00") + assert cfg.time == "02:00:00" + + def test_time_minutes_shorthand(self): + cfg = SlurmConfig(account="grp", time="30m") + assert cfg.time == "00:30:00" + + def test_time_integer_minutes(self): + cfg = SlurmConfig(account="grp", time="120") + assert cfg.time == "02:00:00" + + def test_frozen(self): + cfg = SlurmConfig(account="grp") + with pytest.raises(Exception): # ValidationError or AttributeError + cfg.account = "other" # type: ignore[misc] + + def test_inherits_base_slurm_config(self): + assert issubclass(SlurmConfig, BaseSlurmConfig) + + def test_optional_fields(self): + cfg = SlurmConfig( + account="grp", + partition="gpu", + qos="high", + constraint="a100", + time="1d", + cpus_per_task=16, + mem_gb=64, + job_name_prefix="my-job", + ) + assert cfg.partition == "gpu" + assert cfg.qos == "high" + assert cfg.constraint == "a100" + assert cfg.time == "1-00:00:00" + assert cfg.cpus_per_task == 16 + assert cfg.mem_gb == 64 + assert cfg.job_name_prefix == "my-job" + + +# --------------------------------------------------------------------------- +# SlurmConfig.from_yaml +# --------------------------------------------------------------------------- + + +class TestSlurmConfigFromYaml: + def test_round_trip(self, tmp_path: Path): + data = { + "account": "mygroup", + "partition": "compute", + "time": "4h", + "cpus_per_task": 8, + "mem_gb": 32, + } + yaml_file = tmp_path / "slurm.yaml" + yaml_file.write_text(yaml.dump(data)) + + cfg = SlurmConfig.from_yaml(yaml_file) + + assert cfg.account == "mygroup" + assert cfg.partition == "compute" + assert cfg.time == "04:00:00" # normalised + assert cfg.cpus_per_task == 8 + assert cfg.mem_gb == 32 + + def test_missing_file_raises(self, tmp_path: Path): + with pytest.raises(FileNotFoundError): + SlurmConfig.from_yaml(tmp_path / "nonexistent.yaml") + + def test_defaults_applied(self, tmp_path: Path): + yaml_file = tmp_path / "minimal.yaml" + yaml_file.write_text("account: grp\n") + + cfg = SlurmConfig.from_yaml(yaml_file) + assert cfg.time == "02:00:00" + assert cfg.cpus_per_task == 4 + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture +def no_slurm_settings(monkeypatch): + """Return None for all [slurm] settings — simulates an unconfigured machine. + + Without this fixture, tests that expect autodiscovery to be the sole source + of truth would behave differently on machines that have ACCOUNT / PARTITION_CPU + set in their config.ini. + """ + from mdfactory.settings import Settings + + monkeypatch.setattr(Settings, "slurm_account", property(lambda self: None)) + monkeypatch.setattr(Settings, "slurm_partition_cpu", property(lambda self: None)) + monkeypatch.setattr(Settings, "slurm_partition_gpu", property(lambda self: None)) + monkeypatch.setattr(Settings, "slurm_qos", property(lambda self: None)) + + +# --------------------------------------------------------------------------- +# Helpers to build mock cluster objects +# --------------------------------------------------------------------------- + + +def _make_cpu_partition(name: str = "compute", cpus: int = 32) -> cluster_mod.Partition: + return cluster_mod.Partition( + name=name, + state="up", + max_time="3-00:00:00", + default_time="1:00:00", + node_types=[cluster_mod.NodeType(cpus=cpus, memory_mb=128 * 1024, gpu_specs=(), count=10)], + total_nodes=10, + is_default=True, + ) + + +def _make_gpu_partition(name: str = "gpu") -> cluster_mod.Partition: + return cluster_mod.Partition( + name=name, + state="up", + max_time="2-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType( + cpus=32, + memory_mb=128 * 1024, + gpu_specs=((4, "a100"),), + count=20, + ) + ], + total_nodes=20, + is_default=False, + ) + + +def _make_cluster( + partitions: list[cluster_mod.Partition] | None = None, + default_account: str | None = "myaccount", +) -> cluster_mod.ClusterInfo: + if partitions is None: + partitions = [_make_cpu_partition()] + return cluster_mod.ClusterInfo( + partitions=partitions, + accounts=[default_account] if default_account else [], + qos_policies=["normal"], + default_account=default_account, + ) + + +# --------------------------------------------------------------------------- +# BaseSlurmConfig.from_cluster — 3-tier precedence +# --------------------------------------------------------------------------- + + +class TestBaseSlurmConfigFromCluster: + """Tests run without a real SLURM cluster (discover_cluster mocked).""" + + def test_autodiscovery_populates_account_and_partition(self, monkeypatch, no_slurm_settings): + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + cfg = SlurmConfig.from_cluster() + assert cfg.account == "myaccount" + assert cfg.partition == "compute" + + def test_no_slurm_raises(self, monkeypatch, no_slurm_settings): + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: None) + with pytest.raises(RuntimeError, match="SLURM autodiscovery failed and no account"): + SlurmConfig.from_cluster() + + def test_no_default_account_raises(self, monkeypatch, no_slurm_settings): + cluster = _make_cluster(default_account=None) + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: cluster) + with pytest.raises(RuntimeError, match="No SLURM account available"): + SlurmConfig.from_cluster() + + def test_no_suitable_partition_raises(self, monkeypatch, no_slurm_settings): + # Tiny partition: 4 CPUs — won't satisfy min_cpus=32 + cluster = _make_cluster(partitions=[_make_cpu_partition(cpus=4)]) + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: cluster) + with pytest.raises(RuntimeError, match="No suitable partition found"): + SlurmConfig.from_cluster(min_cpus=32) + + def test_autodiscovery_fails_no_partition_in_config_raises( + self, monkeypatch, no_slurm_settings + ): + """cluster=None with account supplied but no partition in config raises RuntimeError.""" + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: None) + # Provide account so resolution reaches the partition step + monkeypatch.setattr(Settings, "slurm_account", property(lambda self: "myaccount")) + with pytest.raises(RuntimeError, match="no partition configured"): + SlurmConfig.from_cluster() + + def test_needs_gpu_selects_gpu_partition(self, monkeypatch, no_slurm_settings): + cluster = _make_cluster(partitions=[_make_cpu_partition(), _make_gpu_partition()]) + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: cluster) + cfg = SlurmConfig.from_cluster(needs_gpu=True) + assert cfg.partition == "gpu" + + # --- tier 2: config.ini overrides autodiscovery --- + + def test_config_account_overrides_autodiscovery(self, monkeypatch): + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + monkeypatch.setattr(Settings, "slurm_account", property(lambda self: "config-account")) + cfg = SlurmConfig.from_cluster() + assert cfg.account == "config-account" + + def test_config_cpu_partition_overrides_autodiscovery(self, monkeypatch): + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + monkeypatch.setattr(Settings, "slurm_partition_cpu", property(lambda self: "config-cpu")) + cfg = SlurmConfig.from_cluster(needs_gpu=False) + assert cfg.partition == "config-cpu" + + def test_config_gpu_partition_overrides_autodiscovery(self, monkeypatch): + from mdfactory.settings import Settings + + cluster = _make_cluster(partitions=[_make_cpu_partition(), _make_gpu_partition()]) + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: cluster) + monkeypatch.setattr(Settings, "slurm_partition_gpu", property(lambda self: "config-gpu")) + cfg = SlurmConfig.from_cluster(needs_gpu=True) + assert cfg.partition == "config-gpu" + + def test_config_qos_propagated(self, monkeypatch): + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + monkeypatch.setattr(Settings, "slurm_qos", property(lambda self: "high")) + cfg = SlurmConfig.from_cluster() + assert cfg.qos == "high" + + # --- tier 1: explicit kwargs override everything --- + + def test_explicit_account_overrides_config_and_autodiscovery(self, monkeypatch): + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + monkeypatch.setattr(Settings, "slurm_account", property(lambda self: "config-account")) + cfg = SlurmConfig.from_cluster(account="explicit-account") + assert cfg.account == "explicit-account" + + def test_explicit_partition_overrides_config_and_autodiscovery(self, monkeypatch): + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + monkeypatch.setattr( + Settings, "slurm_partition_cpu", property(lambda self: "config-partition") + ) + cfg = SlurmConfig.from_cluster(partition="explicit-partition") + assert cfg.partition == "explicit-partition" + + def test_explicit_qos_overrides_config(self, monkeypatch): + from mdfactory.settings import Settings + + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + monkeypatch.setattr(Settings, "slurm_qos", property(lambda self: "high")) + cfg = SlurmConfig.from_cluster(qos="debug") + assert cfg.qos == "debug" + + def test_explicit_constraint_forwarded(self, monkeypatch): + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + cfg = SlurmConfig.from_cluster(constraint="epyc") + assert cfg.constraint == "epyc" + + # --- submitit-specific extra fields forwarded --- + + def test_submitit_fields_forwarded(self, monkeypatch): + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + cfg = SlurmConfig.from_cluster( + time="4h", + cpus_per_task=8, + mem_gb=32, + job_name_prefix="my-prefix", + ) + assert cfg.time == "04:00:00" + assert cfg.cpus_per_task == 8 + assert cfg.mem_gb == 32 + assert cfg.job_name_prefix == "my-prefix" + + def test_returns_slurm_config_type(self, monkeypatch): + monkeypatch.setattr(cluster_mod, "discover_cluster", _make_cluster) + result = SlurmConfig.from_cluster() + assert isinstance(result, SlurmConfig) + + +# --------------------------------------------------------------------------- +# Backward-compat import from mdfactory.analysis.submit +# --------------------------------------------------------------------------- + + +def test_backward_compat_import_slurm_config(): + """from mdfactory.analysis.submit import SlurmConfig must still work.""" + from mdfactory.analysis.submit import SlurmConfig as SubmitSlurmConfig + + cfg = SubmitSlurmConfig(account="grp") + assert cfg.account == "grp" + + +def test_backward_compat_import_normalize_slurm_time(): + """from mdfactory.analysis.submit import normalize_slurm_time must still work.""" + from mdfactory.analysis.submit import normalize_slurm_time as nslt + + assert nslt("2h") == "02:00:00" diff --git a/mdfactory/tests/test_submit.py b/mdfactory/tests/test_submit.py index 3d6524a..92017e7 100644 --- a/mdfactory/tests/test_submit.py +++ b/mdfactory/tests/test_submit.py @@ -23,6 +23,8 @@ def test_resolve_simulation_paths_from_yaml(tmp_path): def test_normalize_slurm_time(): + # normalize_slurm_time is re-exported from mdfactory.analysis.submit for + # backward compatibility — the canonical version lives in performance.slurm_config. assert submit_mod.normalize_slurm_time("2h") == "02:00:00" assert submit_mod.normalize_slurm_time("30m") == "00:30:00" assert submit_mod.normalize_slurm_time("90") == "01:30:00" @@ -385,3 +387,258 @@ def submit(self, func, sim_path, analysis_names, **kwargs): assert len(submitted_kwargs) == 1 assert submitted_kwargs[0]["analysis_kwargs"] == {"start_ns": 50.0, "stride": 2} + + +# --- SlurmConfig.from_cluster tests --- + + +class TestSlurmConfigFromCluster: + """Tests for SlurmConfig.from_cluster() autodiscovery method.""" + + def test_from_cluster_success(self, monkeypatch): + """Test successful autodiscovery creates correct config.""" + from mdfactory.performance import cluster as cluster_mod + + # Create mock cluster info + mock_partition = cluster_mod.Partition( + name="compute", + state="up", + max_time="3-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType(cpus=32, memory_mb=128 * 1024, gpu_specs=(), count=10), + ], + total_nodes=10, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["myaccount", "otheraccount"], + qos_policies=["normal", "high"], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + config = submit_mod.SlurmConfig.from_cluster( + time="4h", + cpus_per_task=8, + mem_gb=16, + ) + + assert config.account == "myaccount" + assert config.partition == "compute" + assert config.time == "04:00:00" + assert config.cpus_per_task == 8 + assert config.mem_gb == 16 + + def test_from_cluster_no_slurm_raises(self, monkeypatch): + """Test that missing SLURM raises RuntimeError.""" + from mdfactory.performance import cluster as cluster_mod + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: None) + + import pytest + + with pytest.raises( + RuntimeError, match="SLURM autodiscovery failed and no account configured" + ): + submit_mod.SlurmConfig.from_cluster() + + def test_from_cluster_no_account_raises(self, monkeypatch): + """Test that missing default account raises RuntimeError.""" + from mdfactory.performance import cluster as cluster_mod + + mock_partition = cluster_mod.Partition( + name="compute", + state="up", + max_time="1-00:00:00", + default_time="1:00:00", + node_types=[cluster_mod.NodeType(cpus=16, memory_mb=64 * 1024, count=5)], + total_nodes=5, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=[], + qos_policies=[], + default_account=None, + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + import pytest + + with pytest.raises(RuntimeError, match="No SLURM account available"): + submit_mod.SlurmConfig.from_cluster() + + def test_from_cluster_no_suitable_partition_raises(self, monkeypatch): + """Test that no matching partition raises RuntimeError.""" + from mdfactory.performance import cluster as cluster_mod + + # Partition with only 4 CPUs - won't match request for 32 CPUs + mock_partition = cluster_mod.Partition( + name="tiny", + state="up", + max_time="1:00:00", + default_time="0:30:00", + node_types=[cluster_mod.NodeType(cpus=4, memory_mb=8 * 1024, count=2)], + total_nodes=2, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["myaccount"], + qos_policies=[], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + import pytest + + with pytest.raises(RuntimeError, match="No suitable partition found"): + submit_mod.SlurmConfig.from_cluster(min_cpus=32) + + def test_from_cluster_selects_gpu_partition(self, monkeypatch): + """Test that needs_gpu=True selects GPU partition.""" + from mdfactory.performance import cluster as cluster_mod + + cpu_partition = cluster_mod.Partition( + name="cpu", + state="up", + max_time="7-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType(cpus=64, memory_mb=256 * 1024, gpu_specs=(), count=100) + ], + total_nodes=100, + is_default=True, + ) + gpu_partition = cluster_mod.Partition( + name="gpu", + state="up", + max_time="2-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType( + cpus=32, memory_mb=128 * 1024, gpu_specs=((4, "a100"),), count=20 + ) + ], + total_nodes=20, + is_default=False, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[cpu_partition, gpu_partition], + accounts=["myaccount"], + qos_policies=[], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + + config = submit_mod.SlurmConfig.from_cluster(needs_gpu=True) + + assert config.partition == "gpu" + assert config.account == "myaccount" + + def test_from_cluster_uses_config_account(self, monkeypatch): + """Test that config account takes precedence over autodiscovery.""" + from mdfactory.performance import cluster as cluster_mod + from mdfactory.settings import Settings + + mock_partition = cluster_mod.Partition( + name="compute", + state="up", + max_time="3-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType(cpus=32, memory_mb=128 * 1024, gpu_specs=(), count=10), + ], + total_nodes=10, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["autodiscovered-account"], + qos_policies=[], + default_account="autodiscovered-account", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + monkeypatch.setattr(Settings, "slurm_account", property(lambda self: "config-account")) + + config = submit_mod.SlurmConfig.from_cluster() + + # Should use config account, not autodiscovered + assert config.account == "config-account" + + def test_from_cluster_uses_config_partition(self, monkeypatch): + """Test that config partition takes precedence over autodiscovery.""" + from mdfactory.performance import cluster as cluster_mod + from mdfactory.settings import Settings + + mock_partition = cluster_mod.Partition( + name="autodiscovered-partition", + state="up", + max_time="3-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType(cpus=32, memory_mb=128 * 1024, gpu_specs=(), count=10), + ], + total_nodes=10, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["myaccount"], + qos_policies=[], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + monkeypatch.setattr( + Settings, "slurm_partition_cpu", property(lambda self: "config-partition") + ) + + config = submit_mod.SlurmConfig.from_cluster(needs_gpu=False) + + # Should use config partition, not autodiscovered + assert config.partition == "config-partition" + + def test_from_cluster_explicit_overrides_config(self, monkeypatch): + """Test that explicit parameters override config values.""" + from mdfactory.performance import cluster as cluster_mod + from mdfactory.settings import Settings + + mock_partition = cluster_mod.Partition( + name="any-partition", + state="up", + max_time="3-00:00:00", + default_time="1:00:00", + node_types=[ + cluster_mod.NodeType(cpus=32, memory_mb=128 * 1024, gpu_specs=(), count=10), + ], + total_nodes=10, + is_default=True, + ) + mock_cluster = cluster_mod.ClusterInfo( + partitions=[mock_partition], + accounts=["myaccount"], + qos_policies=[], + default_account="myaccount", + ) + + monkeypatch.setattr(cluster_mod, "discover_cluster", lambda: mock_cluster) + monkeypatch.setattr(Settings, "slurm_account", property(lambda self: "config-account")) + monkeypatch.setattr( + Settings, "slurm_partition_cpu", property(lambda self: "config-partition") + ) + + config = submit_mod.SlurmConfig.from_cluster( + account="explicit-account", partition="explicit-partition" + ) + + # Should use explicit values + assert config.account == "explicit-account" + assert config.partition == "explicit-partition" diff --git a/mdfactory/tests/test_sync_config_local_paths.py b/mdfactory/tests/test_sync_config_local_paths.py index b09bef8..e4a3127 100644 --- a/mdfactory/tests/test_sync_config_local_paths.py +++ b/mdfactory/tests/test_sync_config_local_paths.py @@ -89,12 +89,16 @@ def fake_initialize(): ) # Mock questionary prompts in order: - # 1. confirm CGenFF -> False - # 2. text parameter store -> default - # 3. select backend -> "sqlite" - # 4. text RUN_DB_PATH -> "runs.db" - # 5. text ANALYSIS_DB_PATH -> "analysis.db" - # 6. confirm initialize -> True + # 1. confirm GROMACS -> True + # 2. text GMX_PATH -> "" + # 3. text FORCEFIELD_DIR -> default + # 4. confirm force field download -> False + # 5. confirm CGenFF -> False + # 6. text parameter store -> default + # 7. select backend -> "sqlite" + # 8. text RUN_DB_PATH -> "runs.db" + # 9. text ANALYSIS_DB_PATH -> "analysis.db" + # 10. confirm initialize -> True class FakeQuestion: def __init__(self, value): self._value = value @@ -102,8 +106,16 @@ def __init__(self, value): def ask(self): return self._value - answers_confirm = iter([False, True]) - answers_text = iter([str(tmp_path / "data" / "parameters"), "runs.db", "analysis.db"]) + answers_confirm = iter([True, False, False, True]) + answers_text = iter( + [ + "", + str(tmp_path / "data" / "forcefields"), + str(tmp_path / "data" / "parameters"), + "runs.db", + "analysis.db", + ] + ) answers_select = iter(["sqlite"]) monkeypatch.setattr( @@ -122,4 +134,10 @@ def ask(self): run_config_wizard() assert called["value"] is True - assert (tmp_path / "config.ini").exists() + config_path = tmp_path / "config.ini" + assert config_path.exists() + + config = configparser.ConfigParser() + config.read(config_path) + assert config["gromacs"]["GMX_PATH"] == "" + assert config["gromacs"]["FORCEFIELD_DIR"] == str(tmp_path / "data" / "forcefields") diff --git a/mdfactory/tests/test_utilities.py b/mdfactory/tests/test_utilities.py index 41e3fb0..5665f0f 100644 --- a/mdfactory/tests/test_utilities.py +++ b/mdfactory/tests/test_utilities.py @@ -2,14 +2,17 @@ # ABOUTME: and file locking behavior under concurrent access. """Tests for general utility functions including YAML file loading.""" +import subprocess import time from pathlib import Path +from unittest.mock import patch import pytest import yaml from mdfactory.utils.utilities import ( load_yaml_file, + run_command, ) from .utils import ProcessWithException @@ -131,3 +134,76 @@ def lock_stuff(i): if proc.exception: raise Exception(f"{proc.exception[1]}") from proc.exception[0] assert not lockfile.is_file() + + +# --------------------------------------------------------------------------- +# Tests: run_command +# --------------------------------------------------------------------------- + + +class TestRunCommand: + """Test run_command with various failure modes and options.""" + + def test_graceful_returns_none_on_timeout(self): + """Test that graceful=True returns None on timeout.""" + with patch( + "mdfactory.utils.utilities.subprocess.run", + side_effect=subprocess.TimeoutExpired(cmd=["test_cmd"], timeout=30), + ): + result = run_command(["test_cmd"], timeout=30, graceful=True) + assert result is None + + def test_graceful_returns_none_on_file_not_found(self): + """Test that graceful=True returns None when binary not found.""" + with patch( + "mdfactory.utils.utilities.subprocess.run", + side_effect=FileNotFoundError("No such file"), + ): + result = run_command(["nonexistent_binary"], graceful=True) + assert result is None + + def test_graceful_returns_none_on_nonzero_exit(self): + """Test that graceful=True returns None on non-zero exit.""" + with patch( + "mdfactory.utils.utilities.subprocess.run", + return_value=subprocess.CompletedProcess( + args=["test"], returncode=1, stdout="", stderr="error" + ), + ): + result = run_command(["test", "--flag"], graceful=True) + assert result is None + + def test_graceful_returns_stdout_on_success(self): + """Test that graceful=True returns stdout on success.""" + with patch( + "mdfactory.utils.utilities.subprocess.run", + return_value=subprocess.CompletedProcess( + args=["echo"], returncode=0, stdout="hello\n", stderr="" + ), + ): + result = run_command(["echo", "hello"], graceful=True) + assert result == "hello" + + def test_check_raises_on_nonzero_exit(self): + """Test that check=True raises on non-zero exit.""" + with patch( + "mdfactory.utils.utilities.subprocess.run", + side_effect=subprocess.CalledProcessError( + returncode=1, cmd=["false"], output="", stderr="error" + ), + ): + with pytest.raises(subprocess.CalledProcessError): + run_command(["false"], check=True) + + def test_returns_completed_process_when_no_capture(self): + """Test that result is CompletedProcess when not capturing output.""" + with patch( + "mdfactory.utils.utilities.subprocess.run", + return_value=subprocess.CompletedProcess( + args=["echo"], returncode=0, stdout=None, stderr=None + ), + ) as mock_run: + result = run_command(["echo", "test"], capture_output=False) + assert isinstance(result, subprocess.CompletedProcess) + assert result.returncode == 0 + mock_run.assert_called_once() diff --git a/mdfactory/utils/sync_config.py b/mdfactory/utils/sync_config.py index 9792998..c077f0d 100644 --- a/mdfactory/utils/sync_config.py +++ b/mdfactory/utils/sync_config.py @@ -285,6 +285,23 @@ def run_config_wizard() -> None: logger.info("Using existing user config. No changes made.") return + # GROMACS setup (optional) + use_gromacs = questionary.confirm("Do you use GROMACS (pdb2gmx)?", default=True).ask() + gmx_path = "" + forcefield_dir = str(data_dir / "forcefields") + if use_gromacs: + gmx_path = questionary.text( + "Path to gmx binary (leave empty to use PATH):", + default="", + ).ask() + forcefield_dir = questionary.text( + "Force field download directory:", + default=str(data_dir / "forcefields"), + ).ask() + download_ffs = questionary.confirm( + "Download CHARMM36m force field now?", default=True + ).ask() + # CGenFF setup (optional) use_cgenff = questionary.confirm("Do you use CGenFF (SILCSBIO)?", default=False).ask() silcsbiodir = "" @@ -312,6 +329,8 @@ def run_config_wizard() -> None: for key, value in options.items(): config[section][key] = str(value) + config["gromacs"]["GMX_PATH"] = gmx_path + config["gromacs"]["FORCEFIELD_DIR"] = normalize_local_path(forcefield_dir) config["cgenff"]["SILCSBIODIR"] = silcsbiodir config["storage"]["PARAMETERS"] = normalize_local_path(param_dir) config["database"]["TYPE"] = backend @@ -330,6 +349,14 @@ def run_config_wizard() -> None: cfg.ensure_dirs() cfg.reload() + # Download force fields if requested + if use_gromacs and download_ffs: + from ..setup.protein import download_all_forcefields + + logger.info("Downloading registered force fields...") + download_all_forcefields() + logger.success("Force fields downloaded.") + init_default = backend in {"sqlite", "csv"} if not questionary.confirm( "Initialize databases now?", diff --git a/mdfactory/utils/utilities.py b/mdfactory/utils/utilities.py index 5303cb3..17da789 100644 --- a/mdfactory/utils/utilities.py +++ b/mdfactory/utils/utilities.py @@ -1,10 +1,12 @@ # ABOUTME: General-purpose utility functions for mdfactory # ABOUTME: Provides working directory management, YAML loading, and file locking +# ABOUTME: Includes subprocess execution wrapper with graceful failure modes """General-purpose utility functions for mdfactory.""" import contextlib import os import shutil +import subprocess import tempfile import time from pathlib import Path @@ -13,6 +15,105 @@ import yaml +def run_command( + cmd: list[str], + *, + timeout: int | None = None, + capture_output: bool = True, + check: bool = False, + text: bool = True, + graceful: bool = False, + **kwargs, +) -> subprocess.CompletedProcess | str | None: + """Run a shell command with flexible error handling. + + Provides a unified interface for subprocess execution with common + patterns: graceful failure for optional commands, strict checking + for critical operations, and timeout support. + + Parameters + ---------- + cmd : list of str + Command and arguments to execute. + timeout : int or None + Timeout in seconds. None for no timeout (default). + capture_output : bool + Capture stdout/stderr. Default True. + check : bool + Raise CalledProcessError on non-zero exit. Default False. + Ignored when graceful=True. + text : bool + Return output as text (str) rather than bytes. Default True. + graceful : bool + Return None on any failure (timeout, non-zero exit, OSError) + instead of raising. Default False. When True, overrides check=True. + **kwargs + Additional arguments passed to subprocess.run (e.g., cwd, env, + stdout, stderr, stdin). + + Returns + ------- + subprocess.CompletedProcess, str, or None + - If graceful=True: Returns stdout string on success, None on any failure. + - If capture_output=True and not graceful: Returns stripped stdout string. + - Otherwise: Returns CompletedProcess object. + + Raises + ------ + subprocess.CalledProcessError + If check=True and command exits with non-zero status. + subprocess.TimeoutExpired + If timeout is exceeded and graceful=False. + FileNotFoundError + If command binary not found and graceful=False. + OSError + On other OS-level failures and graceful=False. + + Examples + -------- + >>> # Graceful failure for optional commands + >>> output = run_command(["sinfo", "--version"], timeout=30, graceful=True) + >>> if output is None: + ... print("SLURM not available") + + >>> # Strict checking for critical operations + >>> run_command(["vmd", "-e", "script.tcl"], check=True) + + >>> # Manual error handling + >>> result = run_command(["fdt", "config"], capture_output=False) + >>> if result.returncode != 0: + ... print("Validation failed") + """ + if graceful: + try: + result = subprocess.run( + cmd, + capture_output=capture_output, + text=text, + timeout=timeout, + check=False, # Handle manually in graceful mode + **kwargs, + ) + if result.returncode != 0: + return None + return result.stdout.strip() if capture_output and text else result.stdout + except (FileNotFoundError, subprocess.TimeoutExpired, OSError): + return None + else: + result = subprocess.run( + cmd, + capture_output=capture_output, + text=text, + timeout=timeout, + check=check, + **kwargs, + ) + # If caller wants just stdout (most common case) + if capture_output and text and not check: + return result.stdout.strip() + return result + + @contextlib.contextmanager def working_directory(path, create=False, cleanup=False, exists_ok=True): """Change working directory and return to previous on exit. diff --git a/mdfactory/workflows.py b/mdfactory/workflows.py index 9cd4b34..b3e1d13 100644 --- a/mdfactory/workflows.py +++ b/mdfactory/workflows.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any -from .build import build_bilayer, build_lnp, build_mixedbox +from .build import build_bilayer, build_lnp, build_mixedbox, build_proteinbox from .models.input import BuildInput from .utils.utilities import load_yaml_file @@ -13,6 +13,7 @@ "mixedbox": build_mixedbox, "bilayer": build_bilayer, "lnp": build_lnp, + "proteinbox": build_proteinbox, } @@ -43,6 +44,9 @@ def run_build_from_dict(inp_dict: dict[Any, Any] | BuildInput): def run_build_from_file(fname: Path): """Load a YAML build specification and dispatch the build. + Relative protein PDB paths are resolved against the YAML file's directory so + the build finds them regardless of the process working directory. + Parameters ---------- fname : Path @@ -50,4 +54,18 @@ def run_build_from_file(fname: Path): """ dct = load_yaml_file(fname) + _resolve_proteinbox_pdb_path(dct, Path(fname).parent) run_build_from_dict(dct) + + +def _resolve_proteinbox_pdb_path(dct: Any, base_dir: Path) -> None: + """Rewrite a relative proteinbox pdb_path to be absolute against base_dir.""" + if not isinstance(dct, dict) or dct.get("simulation_type") != "proteinbox": + return + protein = (dct.get("system") or {}).get("protein") + if not isinstance(protein, dict): + return + pdb_path = protein.get("pdb_path") + if pdb_path is None or Path(pdb_path).is_absolute(): + return + protein["pdb_path"] = str((base_dir / pdb_path).resolve()) diff --git a/pixi.lock b/pixi.lock index c426628..69da3b8 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,124 +1,67 @@ -version: 6 +version: 7 +platforms: +- name: linux-64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 +- name: osx-64 + virtual-packages: + - __unix=0=0 + - __osx=13.0 + - __archspec=0=x86_64 +- name: osx-arm64 + virtual-packages: + - __unix=0=0 + - __osx=13.0 + - __archspec=0=m1 environments: default: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312hbe1986e_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312h163ff36_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/arpack-3.9.1-nompi_hf03ea27_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-h280c20c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.23.1-hc31b594_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.2.51-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h3b011a4_112.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.11-nompi_h3b011a4_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.0-py312h8a5da7c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.2-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py312hcaba1f9_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.3.2-py312h8285ef7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libattr-2.5.2-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hed09d94_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py312hf890105_5.conda @@ -141,12 +84,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_h3d81e11_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_hafda6a7_1003.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.2-nompi_h21f7587_118.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.10.0-nompi_hbf2fc22_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda @@ -155,7 +98,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librdkit-2025.09.5-h3c5c181_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda @@ -166,147 +109,58 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.9-h04c0eec_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.0-h4922eb0_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py312he3d6523_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mdtraj-1.11.1-np2py312h8baca0b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.2-nompi_h5aa5643_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.4-nompi_he05732c_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-mkl_py312h4fb851f_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-system-1.0.0-1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openmm-8.4.0-py312ha28768a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.19.0-py312hd9148b4_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/parmed-4.3.1-py312h7ab193c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py312h50c33e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py312h2596900_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pytables-3.11.1-py312hefc0c3f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.8.0-cpu_mkl_py312_hb1fc07b_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rdkit-2025.09.5-py312h3ecb6ed_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rocm-core-7.0.2-h54a6638_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.48-py312h5253ce2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-h8d10470_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py312h4c3975b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda @@ -317,72 +171,38 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - - pypi: ./ - osx-64: - - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h9f183f7_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.10-nompi_h54214ab_112.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda @@ -390,30 +210,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda @@ -436,87 +247,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.9.2-nompi_h6054f6d_118.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.13.9-he1bc88e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.2-nompi_h015bb1b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda @@ -528,46 +275,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openldap-2.6.10-hd8a590d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda @@ -575,14 +304,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda @@ -591,24 +314,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda @@ -616,7 +332,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda @@ -625,81 +340,57 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - - pypi: ./ - osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-24.8-cuda_None_nompi_py312h59db364_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.23.1-hf9886e1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-h6a3b0d2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-ha1cbb27_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.10-nompi_haf1500d_112.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda @@ -707,26 +398,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_had3affe_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda @@ -753,87 +435,24 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-hf493ff8_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.18.0-he38603e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.2-nompi_h2d3d5cf_118.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.1-h944245b_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1b79a29_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.13.9-h4a9ca0c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.2-nompi_h170865b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda @@ -845,46 +464,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.10-hbe55e7a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h53cfef5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda @@ -892,14 +493,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda @@ -908,24 +503,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda @@ -933,7 +521,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda @@ -942,90 +529,194 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libice-1.1.2-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libsm-1.2.6-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libx11-1.8.13-hf948f5a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxext-1.3.7-h84a0fba_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxt-1.3.1-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - - pypi: ./ - dev: - channels: - - url: https://conda.anaconda.org/conda-forge/ - indexes: - - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit - packages: - linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312hbe1986e_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/arpack-3.9.1-nompi_hf03ea27_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.23.1-hc31b594_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.2.51-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h5c36eea_103.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.8-ha3d0635_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.11-nompi_h54214ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.10.0-nompi_h2d2ed95_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.4-h77d7759_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-16-2.15.1-ha1d9b0f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.1-h7b7ecba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.4-nompi_hb028221_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openldap-2.6.10-hd8a590d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda + - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h3b011a4_112.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda @@ -1033,30 +724,22 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.0-py312h8a5da7c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.2-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freesasa-2.2.1-hd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py312hcaba1f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.3.2-py312h8285ef7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda @@ -1079,95 +762,25 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hed09d94_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py312hf890105_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-12.2.0.37-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.2-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.2-h73754d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_h3d81e11_1000.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.2-nompi_h21f7587_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/librdkit-2025.09.5-h3c5c181_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.8.0-cpu_mkl_h09b866c_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.9-h04c0eec_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.0-h4922eb0_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py312he3d6523_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mdtraj-1.11.1-np2py312h8baca0b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.24.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.2-nompi_h5aa5643_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-mkl_py312h4fb851f_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-system-1.0.0-1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda @@ -1179,44 +792,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openmm-8.4.0-py312ha28768a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.19.0-py312hd9148b4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/parmed-4.3.1-py312h7ab193c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py312h50c33e8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py312h2596900_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytables-3.11.1-py312hefc0c3f_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda @@ -1224,14 +821,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.8.0-cpu_mkl_py312_hb1fc07b_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rdkit-2025.09.5-py312h3ecb6ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda @@ -1240,26 +831,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rocm-core-7.0.2-h54a6638_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.48-py312h5253ce2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-h8d10470_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py312h4c3975b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda @@ -1267,7 +850,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda @@ -1276,153 +858,389 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/ed/ad1755f82cd5a0baafe342e7154696a93e57f04f86515402f14e5beceb36/bump_my_version-1.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-26.0-cuda_None_nompi_py312h52216ed_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.4-h436dbe7_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.15-hdbc54b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.14.3-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-he8604bc_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.11.0-hc1b69ad_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.27.5-h8fb7669_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.13.1-h1fbe2f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.7-he8604bc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-he8604bc_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/biopython-1.88-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-3.2.3-hf9886e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-hb961e35_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.11-nompi_haf1500d_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freesasa-python-2.2.1-py312h163523d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gemmi-0.7.5-py312h84d8aaf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.2.0-nompi_h7eff4e6_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hc7cc350_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-hfd3d5f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-h0419b56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.21.0-hf618e03_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.10.1-nompi_ha00b61a_201.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.4-ha770aab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.0-h7a62e17_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvmlite-0.48.0-py312hedd3284_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.3-nompi_he024a0a_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlopt-2.11.0-np2py312h60fbb24_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.66.0-py312hdf3e818_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.13-hf7f56bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h4f4653e_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/s2n-1.7.6-h1b28cb6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.0-np2py312he5ca3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libice-1.1.2-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libsm-1.2.6-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libx11-1.8.13-hf948f5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxext-1.3.7-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxt-1.3.1-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/8a/11ae7e271870f0ad8fa0012e4265982bebe0fdc21766b161fb8b8fc3aefc/hatch-1.16.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4d/2ca3ca9906ce6e05070f431c54d54ccbaf57a980cfa58032d35b0b0ac1f8/pyinstrument-5.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl - - pypi: ./ - osx-64: - - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h9f183f7_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.10-nompi_h54214ab_112.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl + dev: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312h163ff36_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/arpack-3.9.1-nompi_hf03ea27_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-h280c20c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.23.1-hc31b594_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.2.51-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.11-nompi_h3b011a4_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.0-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.2-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py312hcaba1f9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.3.2-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libattr-2.5.2-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hed09d94_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py312hf890105_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-12.2.0.37-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.2-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.2-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_hafda6a7_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.10.0-nompi_hbf2fc22_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/librdkit-2025.09.5-h3c5c181_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.8.0-cpu_mkl_h09b866c_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.0-h4922eb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py312he3d6523_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mdtraj-1.11.1-np2py312h8baca0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.4-nompi_he05732c_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-mkl_py312h4fb851f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-system-1.0.0-1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openmm-8.4.0-py312ha28768a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.19.0-py312hd9148b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/parmed-4.3.1-py312h7ab193c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py312h50c33e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py312h2596900_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytables-3.11.1-py312hefc0c3f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.8.0-cpu_mkl_py312_hb1fc07b_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rdkit-2025.09.5-py312h3ecb6ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rocm-core-7.0.2-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.48-py312h5253ce2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-h8d10470_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda @@ -1445,87 +1263,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.9.2-nompi_h6054f6d_118.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.13.9-he1bc88e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.2-nompi_h015bb1b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda @@ -1537,46 +1291,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openldap-2.6.10-hd8a590d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda @@ -1584,14 +1320,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda @@ -1600,24 +1330,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda @@ -1625,7 +1348,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda @@ -1634,118 +1356,101 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/ed/ad1755f82cd5a0baafe342e7154696a93e57f04f86515402f14e5beceb36/bump_my_version-1.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/01/b168791bfbfb0322ef6d38d236f6f17a02e41fb7753e23e4cdb0f19ac969/bump_my_version-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/8a/11ae7e271870f0ad8fa0012e4265982bebe0fdc21766b161fb8b8fc3aefc/hatch-1.16.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/97/a87901aef6b7e7e4a34c6dd6cc17dca8594a592ef9d9dd765fca2b7facf7/rich_click-1.9.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/ce/ae2911859847f9ba1d6b23027e53481cbeb50b93234f355a968d300ca2cb/httpx2-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/26/d9/8fa5571ddd21b2b7189bd8b0bb4e90be1659a54dda5af51c7f6bf2b5666f/pyinstrument-5.1.2-cp312-cp312-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/91/4d/2ca3ca9906ce6e05070f431c54d54ccbaf57a980cfa58032d35b0b0ac1f8/pyinstrument-5.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/dd/3357218c69360d1cecc196c230c9a1d5c9afd5dba362056e23e60a5e64e5/httpcore2-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/c4/8112b3c95db60a39c98db0641dd49bd4228f2fedb9d0ef5e4f3f48b52a0d/uv-0.11.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bd/0a7f877ec78e6910868e39a68c2a1964fa0737a4008ee1f3fd7e2fd8915f/hatch-1.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl - - pypi: ./ - osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-24.8-cuda_None_nompi_py312h59db364_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.23.1-hf9886e1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-h6a3b0d2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-ha1cbb27_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.10-nompi_haf1500d_112.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda @@ -1753,26 +1458,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_had3affe_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda @@ -1799,87 +1495,24 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-hf493ff8_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.18.0-he38603e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.2-nompi_h2d3d5cf_118.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.1-h944245b_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1b79a29_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.13.9-h4a9ca0c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.2-nompi_h170865b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda @@ -1891,46 +1524,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.10-hbe55e7a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h53cfef5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda @@ -1938,14 +1553,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda @@ -1954,24 +1563,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda @@ -1979,7 +1581,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda @@ -1988,153 +1589,1683 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libice-1.1.2-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libsm-1.2.6-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libx11-1.8.13-hf948f5a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxext-1.3.7-h84a0fba_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxt-1.3.1-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/ed/ad1755f82cd5a0baafe342e7154696a93e57f04f86515402f14e5beceb36/bump_my_version-1.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/8a/11ae7e271870f0ad8fa0012e4265982bebe0fdc21766b161fb8b8fc3aefc/hatch-1.16.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/50/0512adb83cadfeaa1a215dc9784defff5043c5aa052d15015e3d8013af75/pyinstrument-5.1.2-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl - - pypi: ./ -packages: -- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda - build_number: 7 - sha256: c0cddb66070dd6355311f7667ce2acccf70d1013edaa6e97f22859502fefdb22 - md5: 887b70e1d607fba7957aa02f9ee0d939 - depends: - - llvm-openmp >=9.0.1 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 8244 - timestamp: 1764092331208 -- conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda - build_number: 7 - sha256: 30006902a9274de8abdad5a9f02ef7c8bb3d69a503486af0c1faee30b023e5b7 - md5: eaac87c21aff3ed21ad9656697bb8326 - depends: - - llvm-openmp >=9.0.1 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 8328 - timestamp: 1764092562779 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h5c36eea_103.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.8-ha3d0635_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.11-nompi_h54214ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.10.0-nompi_h2d2ed95_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.4-h77d7759_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-16-2.15.1-ha1d9b0f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.1-h7b7ecba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.4-nompi_hb028221_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openldap-2.6.10-hd8a590d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda + - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/26/d9/8fa5571ddd21b2b7189bd8b0bb4e90be1659a54dda5af51c7f6bf2b5666f/pyinstrument-5.1.2-cp312-cp312-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/01/b168791bfbfb0322ef6d38d236f6f17a02e41fb7753e23e4cdb0f19ac969/bump_my_version-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/ad/57a31ea9ffc53a2fb5cd9a60b5edb9e4df7c526ba80be4517c6d73cf4fa7/uv-0.11.18-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/97/a87901aef6b7e7e4a34c6dd6cc17dca8594a592ef9d9dd765fca2b7facf7/rich_click-1.9.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/ce/ae2911859847f9ba1d6b23027e53481cbeb50b93234f355a968d300ca2cb/httpx2-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/dd/3357218c69360d1cecc196c230c9a1d5c9afd5dba362056e23e60a5e64e5/httpcore2-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e4/bd/0a7f877ec78e6910868e39a68c2a1964fa0737a4008ee1f3fd7e2fd8915f/hatch-1.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freesasa-2.2.1-hd8ed1ab_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.24.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-26.0-cuda_None_nompi_py312h52216ed_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.4-h436dbe7_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.15-hdbc54b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.14.3-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-he8604bc_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.11.0-hc1b69ad_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.27.5-h8fb7669_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.13.1-h1fbe2f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.7-he8604bc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-he8604bc_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/biopython-1.88-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-3.2.3-hf9886e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-hb961e35_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.11-nompi_haf1500d_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freesasa-python-2.2.1-py312h163523d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gemmi-0.7.5-py312h84d8aaf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.2.0-nompi_h7eff4e6_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hc7cc350_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-hfd3d5f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-h0419b56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.21.0-hf618e03_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.10.1-nompi_ha00b61a_201.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.4-ha770aab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.0-h7a62e17_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvmlite-0.48.0-py312hedd3284_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.3-nompi_he024a0a_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlopt-2.11.0-np2py312h60fbb24_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.66.0-py312hdf3e818_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.13-hf7f56bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h4f4653e_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/s2n-1.7.6-h1b28cb6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.0-np2py312he5ca3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libice-1.1.2-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libsm-1.2.6-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libx11-1.8.13-hf948f5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxext-1.3.7-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxt-1.3.1-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/36/01/b168791bfbfb0322ef6d38d236f6f17a02e41fb7753e23e4cdb0f19ac969/bump_my_version-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/97/a87901aef6b7e7e4a34c6dd6cc17dca8594a592ef9d9dd765fca2b7facf7/rich_click-1.9.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/50/0512adb83cadfeaa1a215dc9784defff5043c5aa052d15015e3d8013af75/pyinstrument-5.1.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/ce/ae2911859847f9ba1d6b23027e53481cbeb50b93234f355a968d300ca2cb/httpx2-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/3b/130515418bdd4be1aec5941ca2fa53dc0281750434e835ff633e6f8cd944/uv-0.11.18-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/dd/3357218c69360d1cecc196c230c9a1d5c9afd5dba362056e23e60a5e64e5/httpcore2-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bd/0a7f877ec78e6910868e39a68c2a1964fa0737a4008ee1f3fd7e2fd8915f/hatch-1.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + docs: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312h163ff36_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/arpack-3.9.1-nompi_hf03ea27_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bun-1.3.11-hb9fe482_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-h280c20c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.23.1-hc31b594_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.2.51-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.11-nompi_h3b011a4_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.0-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.2-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py312hcaba1f9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.3.2-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdrhistogram-c-0.11.9-h421ea60_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libattr-2.5.2-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hed09d94_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py312hf890105_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-12.2.0.37-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.2-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.2-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_hafda6a7_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.4.0-h3a9caae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.10.0-nompi_hbf2fc22_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/librdkit-2025.09.5-h3c5c181_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.8.0-cpu_mkl_h09b866c_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.0-h4922eb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lol-html-3.0.1-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ls-hpack-2.3.5-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py312he3d6523_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mdtraj-1.11.1-np2py312h8baca0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.4-nompi_he05732c_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-mkl_py312h4fb851f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-system-1.0.0-1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openmm-8.4.0-py312ha28768a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.19.0-py312hd9148b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/parmed-4.3.1-py312h7ab193c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py312h50c33e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py312h2596900_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytables-3.11.1-py312hefc0c3f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.8.0-cpu_mkl_py312_hb1fc07b_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rdkit-2025.09.5-py312h3ecb6ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rocm-core-7.0.2-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.48-py312h5253ce2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-h8d10470_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.2.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.2.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h5c36eea_103.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bun-1.3.11-h74e7fe7_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.8-ha3d0635_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.11-nompi_h54214ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/hdrhistogram-c-0.11.9-h07817ec_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libarchive-3.8.9-gpl_h2bf6321_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libhwy-1.4.0-h7747b51_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.10.0-nompi_h2d2ed95_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.4-h77d7759_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-16-2.15.1-ha1d9b0f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.1-h7b7ecba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lol-html-3.0.1-h19f9e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ls-hpack-2.3.5-ha3d0635_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lzo-2.10-h4132b18_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.4-nompi_hb028221_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openldap-2.6.10-hd8a590d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda + - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freesasa-2.2.1-hd8ed1ab_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.24.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.2.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-26.0-cuda_None_nompi_py312h52216ed_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.4-h436dbe7_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.15-hdbc54b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.14.3-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-he8604bc_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.11.0-hc1b69ad_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.27.5-h8fb7669_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.13.1-h1fbe2f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.7-he8604bc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-he8604bc_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/biopython-1.88-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bun-1.3.11-hf0d7872_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-3.2.3-hf9886e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-hb961e35_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.11-nompi_haf1500d_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freesasa-python-2.2.1-py312h163523d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gemmi-0.7.5-py312h84d8aaf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.2.0-nompi_h7eff4e6_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdrhistogram-c-0.11.9-h132b30e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hc7cc350_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-hfd3d5f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h6fbacd7_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-h0419b56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.21.0-hf618e03_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.4.0-h493e8d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.10.1-nompi_ha00b61a_201.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.4-ha770aab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.0-h7a62e17_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvmlite-0.48.0-py312hedd3284_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lol-html-3.0.1-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ls-hpack-2.3.5-h1a92334_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.3-nompi_he024a0a_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlopt-2.11.0-np2py312h60fbb24_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.66.0-py312hdf3e818_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.13-hf7f56bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h4f4653e_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/s2n-1.7.6-h1b28cb6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.0-np2py312he5ca3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libice-1.1.2-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libsm-1.2.6-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libx11-1.8.13-hf948f5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxext-1.3.7-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxt-1.3.1-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda build_number: 7 - sha256: 7acaa2e0782cad032bdaf756b536874346ac1375745fb250e9bdd6a48a7ab3cd - md5: a44032f282e7d2acdeb1c240308052dd - depends: - - llvm-openmp >=9.0.1 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 8325 - timestamp: 1764092507920 -- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 - md5: aaa2a381ccc56eac91d63b6c1240312f + sha256: c0cddb66070dd6355311f7667ce2acccf70d1013edaa6e97f22859502fefdb22 + md5: 887b70e1d607fba7957aa02f9ee0d939 depends: - - cpython - - python-gil - license: MIT - license_family: MIT + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 8191 - timestamp: 1744137672556 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312hbe1986e_101.conda - sha256: dc3a4ff296bebd7b2470f2d61da43051a15bb42147214a16887dda676a942172 - md5: 33954359b885ad4797303485c796b692 + size: 8244 + timestamp: 1764092331208 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ambertools-24.8-cuda_None_nompi_py312h163ff36_103.conda + sha256: 04987c1f227f6655730eaad26ebb7bbb68d8ea03c97ae43b086938bbf3491068 + md5: fa6662fac3b06f22ff34e74f2146b84c depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 - arpack >=3.9.1,<3.10.0a0 nompi_* - bzip2 >=1.0.8,<2.0a0 - - fftw >=3.3.10,<4.0a0 + - fftw >=3.3.11,<4.0a0 - joblib - libblas >=3.9.0,<4.0a0 - libboost >=1.86.0,<1.87.0a0 - - libgcc >=13 + - libgcc >=14 - libgfortran - - libgfortran5 >=13.3.0 + - libgfortran5 >=14.3.0 - libgomp - liblapack >=3.9.0,<4.0a0 - - libnetcdf >=4.9.2,<4.9.3.0a0 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 + - libnetcdf >=4.10.0,<4.10.1.0a0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 - matplotlib-base - mkl - - netcdf-fortran >=4.6.1,<4.7.0a0 - - numpy >=1.19,<3 - - openssl >=3.4.1,<4.0a0 + - netcdf-fortran >=4.6.3,<4.7.0a0 + - numpy >=1.25,<3 + - openssl >=3.5.7,<4.0a0 - parmed >=4.0.0,<5.0.0a0 - perl - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - readline >=8.2,<9.0a0 + - readline >=8.3,<9.0a0 - scipy - setuptools - - xorg-libx11 >=1.8.11,<2.0a0 - - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 - xorg-libxt >=1.3.1,<2.0a0 constrains: - - ambermini ==9999999999 - packmol ==9999999999 + - ambermini ==9999999999 license: GPL-3.0-or-later AND LGPL-3.0-or-later AND BSD-3-Clause AND MIT purls: - pkg:pypi/amberutils?source=hash-mapping @@ -2146,5893 +3277,6132 @@ packages: - pkg:pypi/pymsmt?source=hash-mapping - pkg:pypi/pytraj?source=hash-mapping - pkg:pypi/sander?source=hash-mapping - size: 101939238 - timestamp: 1740611336012 -- conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h9f183f7_101.conda - sha256: 7ecfa459ed580f739beaa90d27312b2ee02bea9cbe87ed35fc79b76615a05ba0 - md5: 00140d6ec3d22eae4c3b858a5583dee6 + run_exports: {} + size: 102290954 + timestamp: 1783522982338 +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_2.conda + sha256: 7988c207b2b766dad5ebabf25a92b8d75cb8faed92f256fd7a4e0875c9ec6d58 + md5: 1567f06d717246abab170736af8bad1b depends: - - __osx >=10.13 - - arpack >=3.9.1,<3.10.0a0 nompi_* - - bzip2 >=1.0.8,<2.0a0 - - fftw >=3.3.10,<4.0a0 - - joblib - - libblas >=3.9.0,<4.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libcxx >=18 - - libgfortran >=5 - - libgfortran5 >=13.2.0 - - liblapack >=3.9.0,<4.0a0 - - libnetcdf >=4.9.2,<4.9.3.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=18.1.8 - - matplotlib-base - - netcdf-fortran >=4.6.1,<4.7.0a0 - - numpy >=1.19,<3 - - openssl >=3.4.1,<4.0a0 - - parmed >=4.0.0,<5.0.0a0 - - perl + - __glibc >=2.17,<3.0.a0 + - cffi >=1.0.1 + - libgcc >=14 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - readline >=8.2,<9.0a0 - - scipy - - setuptools - - xorg-libx11 >=1.8.11,<2.0a0 - - xorg-libxext >=1.3.6,<2.0a0 - - xorg-libxt >=1.3.1,<2.0a0 - constrains: - - ambermini ==9999999999 - - packmol ==9999999999 - license: GPL-3.0-or-later AND LGPL-3.0-or-later AND BSD-3-Clause AND MIT + license: MIT + license_family: MIT purls: - - pkg:pypi/amberutils?source=hash-mapping - - pkg:pypi/edgembar?source=hash-mapping - - pkg:pypi/mmpbsa-py?source=hash-mapping - - pkg:pypi/ndfes?source=hash-mapping - - pkg:pypi/packmol-memgen?source=hash-mapping - - pkg:pypi/pdb4amber?source=hash-mapping - - pkg:pypi/pymsmt?source=hash-mapping - - pkg:pypi/pytraj?source=hash-mapping - - pkg:pypi/sander?source=hash-mapping - size: 96398937 - timestamp: 1740612162600 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-24.8-cuda_None_nompi_py312h59db364_101.conda - sha256: 20b6fc7e1f2e21950229357dd8a5672ea827fc1d3da88568cb509d825ca1b4e2 - md5: b50d654a458f333f59966715906f5e43 + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 35646 + timestamp: 1762509443854 +- conda: https://conda.anaconda.org/conda-forge/linux-64/arpack-3.9.1-nompi_hf03ea27_102.conda + sha256: 6d71343420292132be0192ddd962b308f7b8a0a0630d1db83fb9d65e8167c6ce + md5: e09af397232ef1070e0b6cbf4c64aacb depends: - - __osx >=11.0 - - arpack >=3.9.1,<3.10.0a0 nompi_* - - bzip2 >=1.0.8,<2.0a0 - - fftw >=3.3.10,<4.0a0 - - joblib + - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libcxx >=18 - - libgfortran >=5 - - libgfortran5 >=13.2.0 + - libgcc >=13 + - libgfortran + - libgfortran5 >=13.3.0 - liblapack >=3.9.0,<4.0a0 - - libnetcdf >=4.9.2,<4.9.3.0a0 + - libstdcxx >=13 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 130412 + timestamp: 1736083992796 +- conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-hb03c661_1.conda + sha256: 78c516af87437f52d883193cf167378f592ad445294c69f7c69f56059087c40d + md5: 9bb149f49de3f322fca007283eaa2725 + depends: + - __glibc >=2.17,<3.0.a0 + - libattr 2.5.2 hb03c661_1 + - libgcc >=14 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - libattr >=2.5.2,<2.6.0a0 + size: 31386 + timestamp: 1773595914754 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda + sha256: d77a24be15e283d83214121428290dbe55632a6e458378205b39c550afa008cf + md5: 5b8c55fed2e576dde4b0b33693a4fdb1 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.12.* *_cp312 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 237970 + timestamp: 1767045004512 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + sha256: e7af5d1183b06a206192ff440e08db1c4e8b2ca1f8376ee45fb2f3a85d4ee45d + md5: 2c2fae981fd2afd00812c92ac47d023d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=18.1.8 - - matplotlib-base - - netcdf-fortran >=4.6.1,<4.7.0a0 - - numpy >=1.19,<3 - - openssl >=3.4.1,<4.0a0 - - parmed >=4.0.0,<5.0.0a0 - - perl + - lz4-c >=1.10.0,<1.11.0a0 + - snappy >=1.2.1,<1.3.0a0 + - zstd >=1.5.6,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 48427 + timestamp: 1733513201413 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + sha256: e511644d691f05eb12ebe1e971fd6dc3ae55a4df5c253b4e1788b789bdf2dfa6 + md5: 8ccf913aaba749a5496c17629d859ed1 + depends: + - __glibc >=2.17,<3.0.a0 + - brotli-bin 1.2.0 hb03c661_1 + - libbrotlidec 1.2.0 hb03c661_1 + - libbrotlienc 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 20103 + timestamp: 1764017231353 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + sha256: 64b137f30b83b1dd61db6c946ae7511657eead59fdf74e84ef0ded219605aa94 + md5: af39b9a8711d4a8d437b52c1d78eb6a1 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlidec 1.2.0 hb03c661_1 + - libbrotlienc 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 21021 + timestamp: 1764017221344 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda + sha256: 49df13a1bb5e388ca0e4e87022260f9501ed4192656d23dc9d9a1b4bf3787918 + md5: 64088dffd7413a2dd557ce837b4cbbdb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - readline >=8.2,<9.0a0 - - scipy - - setuptools - - xorg-libx11 >=1.8.11,<2.0a0 - - xorg-libxext >=1.3.6,<2.0a0 - - xorg-libxt >=1.3.1,<2.0a0 constrains: - - packmol ==9999999999 - - ambermini ==9999999999 - license: GPL-3.0-or-later AND LGPL-3.0-or-later AND BSD-3-Clause AND MIT + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT purls: - - pkg:pypi/amberutils?source=hash-mapping - - pkg:pypi/edgembar?source=hash-mapping - - pkg:pypi/mmpbsa-py?source=hash-mapping - - pkg:pypi/ndfes?source=hash-mapping - - pkg:pypi/packmol-memgen?source=hash-mapping - - pkg:pypi/pdb4amber?source=hash-mapping - - pkg:pypi/pymsmt?source=hash-mapping - - pkg:pypi/pytraj?source=hash-mapping - - pkg:pypi/sander?source=hash-mapping - size: 94927971 - timestamp: 1740613139251 -- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 - md5: 2934f256a8acfe48f6ebb4fce6cde29c + - pkg:pypi/brotli?source=hash-mapping + size: 368300 + timestamp: 1764017300621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bun-1.3.11-hb9fe482_5.conda + sha256: b1bec9fba865e9502c1a5c7681e4b37d61291e35f3bbdb10664f66c50362df31 + md5: 89f9df3e1f06ad9514829aa645c202cb depends: - - python >=3.9 - - typing-extensions >=4.0.0 + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.28,<3.0.a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libsqlite >=3.53.4,<4.0a0 + - lol-html >=3.0.1,<3.0.2.0a0 + - libzlib >=1.3.2,<2.0a0 + - libhwy >=1.4.0,<1.5.0a0 + - ls-hpack >=2.3.5,<2.3.6.0a0 + - libarchive >=3.8.9,<3.9.0a0 + - c-ares >=1.34.8,<2.0a0 + - libdeflate >=1.25,<1.26.0a0 + - zstd >=1.5.7,<1.6.0a0 + - icu >=75.1,<76.0a0 + - hdrhistogram-c >=0.11.9,<0.11.10.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/annotated-types?source=hash-mapping - size: 18074 - timestamp: 1733247158254 -- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda - sha256: eb0c4e2b24f1fbefaf96ce6c992c6bd64340bc3c06add4d7415ab69222b201da - md5: 11a2b8c732d215d977998ccd69a9d5e8 + purls: [] + run_exports: {} + size: 17940849 + timestamp: 1785503811783 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-h280c20c_1.conda + sha256: 5139b6afbfaca91c47104ba9a6a40f81211e1c9200e96b73ce3a56f0ca1902f4 + md5: 2cef891b791040aab83e218c7d137679 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - c-ares-static <0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 226755 + timestamp: 1786116641939 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.23.1-hc31b594_0.conda + sha256: b6ce82ebe3cf24e70179bd656eacfa97ce9df9a500f2cec6843043466a5e6af8 + md5: 68ceffc6cadae61846a207cae60de094 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - lz4-c >=1.10.0,<1.11.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 353899 + timestamp: 1772620395951 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda + sha256: 3bd6a391ad60e471de76c0e9db34986c4b5058587fbf2efa5a7f54645e28c2c7 + md5: 09262e66b19567aff4f592fb53b28760 + depends: + - __glibc >=2.17,<3.0.a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - freetype >=2.12.1,<3.0a0 + - icu >=75.1,<76.0a0 + - libexpat >=2.6.4,<3.0a0 + - libgcc >=13 + - libglib >=2.82.2,<3.0a0 + - libpng >=1.6.47,<1.7.0a0 + - libstdcxx >=13 + - libxcb >=1.17.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.44.2,<1.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.5,<2.0a0 + - xorg-libx11 >=1.8.11,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + size: 978114 + timestamp: 1741554591855 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda + sha256: 7dafe8173d5f94e46cf9cd597cc8ff476a8357fbbd4433a8b5697b2864845d9c + md5: 648ee28dcd4e07a1940a17da62eccd40 depends: - - exceptiongroup >=1.0.2 - - idna >=2.8 - - python >=3.10 - - typing_extensions >=4.5 - - python - constrains: - - trio >=0.32.0 - - uvloop >=0.21 + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/anyio?source=compressed-mapping - size: 145175 - timestamp: 1767719033569 -- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf - md5: 54898d0f524c9dee622d44bbb081a8ab + - pkg:pypi/cffi?source=hash-mapping + size: 295716 + timestamp: 1761202958833 +- conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda + sha256: 62447faf7e8eb691e407688c0b4b7c230de40d5ecf95bf301111b4d05c5be473 + md5: 43c2bc96af3ae5ed9e8a10ded942aa50 depends: - - python >=3.9 - license: BSD-2-Clause + - numpy >=1.25 + - python + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/appnope?source=hash-mapping - size: 10076 - timestamp: 1733332433806 -- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad - md5: 8ac12aff0860280ee0cff7fa2cf63f3b + - pkg:pypi/contourpy?source=hash-mapping + size: 320386 + timestamp: 1769155979897 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.2.51-hecca717_0.conda + sha256: 9de235d328b7124f715805715e9918eb7f8aa5b9c56a2afa62b84f84f98077a5 + md5: 0413baaa73be1a39d5d8e442184acc78 depends: - - argon2-cffi-bindings - - python >=3.9 - - typing-extensions - constrains: - - argon2_cffi ==999 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi?source=hash-mapping - size: 18715 - timestamp: 1749017288144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_2.conda - sha256: 7988c207b2b766dad5ebabf25a92b8d75cb8faed92f256fd7a4e0875c9ec6d58 - md5: 1567f06d717246abab170736af8bad1b + - __glibc >=2.17,<3.0.a0 + - cuda-version >=13.2,<13.3.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: LicenseRef-NVIDIA-End-User-License-Agreement + purls: [] + size: 35736655 + timestamp: 1773100338749 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda + sha256: ee09ad7610c12c7008262d713416d0b58bf365bc38584dce48950025850bdf3f + md5: cae723309a49399d2949362f4ab5c9e4 depends: - __glibc >=2.17,<3.0.a0 - - cffi >=1.0.1 + - krb5 >=1.21.3,<1.22.0a0 + - libgcc >=13 + - libntlm >=1.8,<2.0a0 + - libstdcxx >=13 + - libxcrypt >=4.4.36 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause-Attribution + license_family: BSD + purls: [] + size: 209774 + timestamp: 1750239039316 +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda + sha256: f20121b67149ff80bf951ccae7442756586d8789204cd08ade59397b22bfd098 + md5: ee1b48795ceb07311dd3e665dd4f5f33 + depends: + - python - libgcc >=14 - - python >=3.12,<3.13.0a0 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 35646 - timestamp: 1762509443854 -- conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda - sha256: b18ea88c1a3e8c9d6a05f1aa71928856cfdcb5fd4ad0353638f4bac3f0b9b9a2 - md5: 66f6b81d4bf42e3da028763e9d873bff + - pkg:pypi/debugpy?source=hash-mapping + size: 2858582 + timestamp: 1769744978783 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.11-nompi_h3b011a4_100.conda + sha256: 6fd5d681fba20adaca771f138ac52dbf0a52e0dc2ac31b9ce7406068d102a9a7 + md5: 0717f4eb3d18259358a1fa77edb18917 depends: - - __osx >=10.13 - - cffi >=1.0.1 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - libstdcxx >=14 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - fftw >=3.3.11,<4.0a0 + size: 2195198 + timestamp: 1776781721834 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda + sha256: aa4a44dba97151221100a637c7f4bde619567afade9c0265f8e1c8eed8d7bd8c + md5: 867127763fbe935bab59815b6e0b7b5c + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.7.4,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libuuid >=2.41.3,<3.0a0 + - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 33431 - timestamp: 1762509769660 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda - sha256: 24c475f6f7abf03ef3cc2ac572b7a6d713bede00ef984591be92cdc439b09fbc - md5: 0a2a07b42db3f92b8dccf0f60b5ebee8 + purls: [] + size: 270705 + timestamp: 1771382710863 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.0-py312h8a5da7c_0.conda + sha256: 777c80a1aa0889e6b637631c31f95d0b048848c5ba710f89ed7cedd3ad318227 + md5: 526f7ffd63820e55d7992cc1cf931a36 depends: - - __osx >=11.0 - - cffi >=1.0.1 + - __glibc >=2.17,<3.0.a0 + - brotli + - libgcc >=14 + - munkres - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 + - unicodedata2 >=15.1.0 license: MIT license_family: MIT purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 34224 - timestamp: 1762509989973 -- conda: https://conda.anaconda.org/conda-forge/linux-64/arpack-3.9.1-nompi_hf03ea27_102.conda - sha256: 6d71343420292132be0192ddd962b308f7b8a0a0630d1db83fb9d65e8167c6ce - md5: e09af397232ef1070e0b6cbf4c64aacb - depends: - - __glibc >=2.17,<3.0.a0 - - libblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libgfortran - - libgfortran5 >=13.3.0 - - liblapack >=3.9.0,<4.0a0 - - libstdcxx >=13 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 130412 - timestamp: 1736083992796 -- conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda - sha256: fbb8ab189e4700bbb856abdee6f71172628004c009cc276e44ffa3fdd2599985 - md5: 4fbf1ddfe3784263c01b7c33e6a6a3f7 + - pkg:pypi/fonttools?source=hash-mapping + size: 2935817 + timestamp: 1773137546716 +- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.2-ha770c72_0.conda + sha256: 36857701b46828b6760c3c1652414ee504e7fc12740261ac6fcff3959b72bd7a + md5: eeec961fec28e747e1e1dc0446277452 depends: - - __osx >=10.13 - - libblas >=3.9.0,<4.0a0 - - libcxx >=18 - - libgfortran >=5 - - libgfortran5 >=13.2.0 - - liblapack >=3.9.0,<4.0a0 - license: BSD-3-Clause - license_family: BSD + - libfreetype 2.14.2 ha770c72_0 + - libfreetype6 2.14.2 h73754d4_0 + license: GPL-2.0-only OR FTL purls: [] - size: 125644 - timestamp: 1736084029191 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda - sha256: 314fb780a811d1bcd31b34266016d1d49e754e37a476f8b18d8ea81599865444 - md5: 7da333ca69131caf002baf360f5de7f9 + size: 174292 + timestamp: 1772757205296 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + sha256: 309cf4f04fec0c31b6771a5809a1909b4b3154a2208f52351e1ada006f4c750c + md5: c94a5994ef49749880a8139cf9afcbe1 depends: - - __osx >=11.0 - - libblas >=3.9.0,<4.0a0 - - libcxx >=18 - - libgfortran >=5 - - libgfortran5 >=13.2.0 - - liblapack >=3.9.0,<4.0a0 - license: BSD-3-Clause - license_family: BSD + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: GPL-2.0-or-later OR LGPL-3.0-or-later purls: [] - size: 120348 - timestamp: 1736084183877 -- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 - md5: 85c4f19f377424eafc4ed7911b291642 - depends: - - python >=3.10 - - python-dateutil >=2.7.0 - - python-tzdata - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/arrow?source=hash-mapping - size: 113854 - timestamp: 1760831179410 -- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 - md5: 9673a61a297b00016442e022d689faa6 - depends: - - python >=3.10 - constrains: - - astroid >=2,<5 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/asttokens?source=hash-mapping - size: 28797 - timestamp: 1763410017955 -- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda - sha256: d078b0d3fdc13b0ff08485af20928a095c80dff03f7021ee18e8426a773ae948 - md5: 2cdaf7f8bda7eb9ce49c3e08f2f65803 + size: 460055 + timestamp: 1718980856608 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py312hcaba1f9_0.conda + sha256: 8e373b49ff22d94580e0c6763a4b22b305d19ef010c0c4b31e9c80621fdc0f07 + md5: 71e55276d288e6bb1e969f05fa4e1f5c depends: - - python >=3.10 - - typing_extensions >=4.0.0 - - python - license: MIT - license_family: MIT + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=14 + - mpc >=1.3.1,<2.0a0 + - mpfr >=4.2.1,<5.0a0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: LGPL-3.0-or-later + license_family: LGPL purls: - - pkg:pypi/async-lru?source=compressed-mapping - size: 21470 - timestamp: 1771623881915 -- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda - sha256: c13d5e42d187b1d0255f591b7ce91201d4ed8a5370f0d986707a802c20c9d32f - md5: 537296d57ea995666c68c821b00e360b + - pkg:pypi/gmpy2?source=hash-mapping + size: 253013 + timestamp: 1773102850428 +- conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.3.2-py312h8285ef7_0.conda + sha256: 03c8d065ef1e07053252412c541b5f1af70bc5fa2f974f129128d90fbdc47fe5 + md5: db6bba1610e5c4256d2892ec2997c425 depends: - - python >=3.10 - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/attrs?source=compressed-mapping - size: 64759 - timestamp: 1764875182184 -- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 - md5: f1976ce927373500cc19d3c0b2c85177 + - pkg:pypi/greenlet?source=hash-mapping + size: 253793 + timestamp: 1771658391409 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda + sha256: 0d09b6dc1ce5c4005ae1c6a19dc10767932ef9a5e9c755cfdbb5189ac8fb0684 + md5: bd77f8da987968ec3927990495dc22e4 depends: - - python >=3.10 - - python - constrains: - - pytz >=2015.7 + - libgcc-ng >=12 + - libjpeg-turbo >=3.0.0,<4.0a0 + - libstdcxx-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/babel?source=compressed-mapping - size: 7684321 - timestamp: 1772555330347 -- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda - sha256: d77a24be15e283d83214121428290dbe55632a6e458378205b39c550afa008cf - md5: 5b8c55fed2e576dde4b0b33693a4fdb1 + purls: [] + size: 756742 + timestamp: 1695661547874 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda + sha256: 1fc50ce3b86710fba3ec9c5714f1612b5ffa4230d70bfe43e2a1436eacba1621 + md5: c223ee1429ba538f3e48cfb4a0b97357 + depends: + - __glibc >=2.17,<3.0.a0 + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.18.0,<9.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 3708864 + timestamp: 1770390337946 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdrhistogram-c-0.11.9-h421ea60_1.conda + sha256: 04363c6cd15b7d9e48b622e196471b725630c84e82bfa7a4adbf36f5e94a2c06 + md5: 5160b2491c798ef0f3e883485c526e42 depends: - - python - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 237970 - timestamp: 1767045004512 -- conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda - sha256: 96eefe04e072e8c31fcac7d5e89c9d4a558d2565eef629cfc691a755b2fa6e59 - md5: c8b7d0fb5ff6087760dde8f5f388b135 + - libzlib >=1.3.1,<2.0a0 + license: BSD-2-Clause OR CC0-1.0 + purls: [] + run_exports: + weak: + - hdrhistogram-c >=0.11.9,<0.11.10.0a0 + size: 38736 + timestamp: 1769816263111 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e + md5: 8b189310083baabfb622af68fd9d3ae3 depends: - - python - - __osx >=10.13 - - python_abi 3.12.* *_cp312 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 238093 - timestamp: 1767044989890 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda - sha256: aee745bfca32f7073d3298157bbb2273d6d83383cb266840cf0a7862b3cd8efc - md5: c2d5961bfd98504b930e704426d16572 + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + purls: [] + size: 12129203 + timestamp: 1720853576813 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 + md5: b38117a3c920364aff79f870c984b4a3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + size: 134088 + timestamp: 1754905959823 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda + sha256: eec7654c2d68f06590862c6e845cc70987b6d6559222b6f0e619dea4268f5dd5 + md5: cd74a9525dc74bbbf93cf8aa2fa9eb5b depends: - python - - python 3.12.* *_cpython - - __osx >=11.0 - - zstd >=1.5.7,<1.6.0a0 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - python_abi 3.12.* *_cp312 - license: BSD-3-Clause AND MIT AND EPL-2.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 241051 - timestamp: 1767045000787 -- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 - md5: 5267bef8efea4127aacd1f4e1f149b6e + - pkg:pypi/kiwisolver?source=hash-mapping + size: 77120 + timestamp: 1773067050308 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 + md5: 3f43953b7d3fb3aaa1d0d0723d91e368 depends: - - python >=3.10 - - soupsieve >=1.2 - - typing-extensions + - keyutils >=1.6.1,<2.0a0 + - libedit >=3.1.20191231,<3.2.0a0 + - libedit >=3.1.20191231,<4.0a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - openssl >=3.3.1,<4.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/beautifulsoup4?source=hash-mapping - size: 90399 - timestamp: 1764520638652 -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - sha256: f8ff1f98423674278964a46c93a1766f9e91960d44efd91c6c3ed56a33813f46 - md5: 7c5ebdc286220e8021bf55e6384acd67 + purls: [] + size: 1370023 + timestamp: 1719463201255 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + sha256: 836ec4b895352110335b9fdcfa83a8dcdbe6c5fb7c06c4929130600caea91c0a + md5: 6f2e2c8f58160147c4d1c6f4c14cbac4 depends: - - python >=3.10 - - webencodings - - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: MIT + license_family: MIT + purls: [] + size: 249959 + timestamp: 1768184673131 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 + md5: 12bd9a3f089ee6c9266a37dab82afabd + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 constrains: - - tinycss2 >=1.1.0,<1.5 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/bleach?source=compressed-mapping - size: 142008 - timestamp: 1770719370680 -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - sha256: 7c07a865e5e4cca233cc4e0eb3f0f5ff6c90776461687b4fb0b1764133e1fd61 - md5: f11a319b9700b203aa14c295858782b6 + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 725507 + timestamp: 1770267139900 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda + sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 + md5: a752488c68f2e7c456bcbd8f16eec275 depends: - - bleach ==6.3.0 pyhcf101f3_1 - - tinycss2 - license: Apache-2.0 AND MIT + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: Apache purls: [] - size: 4409 - timestamp: 1770719370682 -- conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda - sha256: e7af5d1183b06a206192ff440e08db1c4e8b2ca1f8376ee45fb2f3a85d4ee45d - md5: 2c2fae981fd2afd00812c92ac47d023d + size: 261513 + timestamp: 1773113328888 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + sha256: dcd1429a1782864c452057a6c5bc1860f2b637dc20a2b7e6eacd57395bbceff8 + md5: 83b160d4da3e1e847bf044997621ed63 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - - lz4-c >=1.10.0,<1.11.0a0 - - snappy >=1.2.1,<1.3.0a0 - - zstd >=1.5.6,<1.6.0a0 - license: BSD-3-Clause + constrains: + - libabseil-static =20250512.1=cxx17* + - abseil-cpp =20250512.1 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1310612 + timestamp: 1750194198254 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + sha256: 822e4ae421a7e9c04e841323526321185f6659222325e1a9aedec811c686e688 + md5: 86f7414544ae606282352fa1e116b41f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-2-Clause license_family: BSD purls: [] - size: 48427 - timestamp: 1733513201413 -- conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda - sha256: 876bdb1947644b4408f498ac91c61f1f4987d2c57eb47c0aba0d5ee822cd7da9 - md5: 717852102c68a082992ce13a53403f9d + size: 36544 + timestamp: 1769221884824 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_hc2c16d8_100.conda + sha256: 000b4baa9ce849b5fb259af9256331d0c7872361ac63a2bba0d0c48eb35663d0 + md5: 3988040b14a8083b1a5382d99f584e5f depends: - - __osx >=10.13 - - libcxx >=18 - - libzlib >=1.3.1,<2.0a0 + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.2,<2.0a0 - lz4-c >=1.10.0,<1.11.0a0 - - snappy >=1.2.1,<1.3.0a0 - - zstd >=1.5.6,<1.6.0a0 - license: BSD-3-Clause + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause license_family: BSD purls: [] - size: 46990 - timestamp: 1733513422834 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda - sha256: c3fe902114b9a3ac837e1a32408cc2142c147ec054c1038d37aec6814343f48a - md5: 925acfb50a750aa178f7a0aced77f351 + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 876197 + timestamp: 1785250447640 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libattr-2.5.2-hb03c661_1.conda + sha256: 0cef37eb013dc7091f17161c357afbdef9a9bc79ef6462508face6db3f37db77 + md5: 7e7f0a692eb62b95d3010563e7f963b6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - libattr >=2.5.2,<2.6.0a0 + size: 53316 + timestamp: 1773595896163 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + build_number: 5 + sha256: 328d64d4eb51047c39a8039a30eb47695855829d0a11b72d932171cb1dcdfad3 + md5: 9d2f2e3a943d38f972ceef9cde8ba4bf depends: - - __osx >=11.0 - - libcxx >=18 - - libzlib >=1.3.1,<2.0a0 - - lz4-c >=1.10.0,<1.11.0a0 - - snappy >=1.2.1,<1.3.0a0 - - zstd >=1.5.6,<1.6.0a0 + - mkl >=2025.3.0,<2026.0a0 + constrains: + - liblapack 3.11.0 5*_mkl + - liblapacke 3.11.0 5*_mkl + - libcblas 3.11.0 5*_mkl + - blas 2.305 mkl + track_features: + - blas_mkl + - blas_mkl_2 license: BSD-3-Clause license_family: BSD purls: [] - size: 33602 - timestamp: 1733513285902 -- pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl - name: bracex - version: '2.6' - sha256: 0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda - sha256: e511644d691f05eb12ebe1e971fd6dc3ae55a4df5c253b4e1788b789bdf2dfa6 - md5: 8ccf913aaba749a5496c17629d859ed1 + size: 18744 + timestamp: 1765818556597 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hed09d94_4.conda + sha256: 2e9778d8c3bbc6e7698fd87a1499a68ca1f02be37f6aaefa7541eb2728ffbff3 + md5: b708abf3b6a0f3cf2f833d2edf18aff0 depends: - __glibc >=2.17,<3.0.a0 - - brotli-bin 1.2.0 hb03c661_1 - - libbrotlidec 1.2.0 hb03c661_1 - - libbrotlienc 1.2.0 hb03c661_1 + - bzip2 >=1.0.8,<2.0a0 + - icu >=75.1,<76.0a0 - libgcc >=14 - license: MIT - license_family: MIT + - liblzma >=5.8.1,<6.0a0 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - boost-cpp <0.0a0 + license: BSL-1.0 purls: [] - size: 20103 - timestamp: 1764017231353 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda - sha256: c838c71ded28ada251589f6462fc0f7c09132396799eea2701277566a1a863bf - md5: 149d8ee7d6541a02a6117d8814fd9413 + size: 2959099 + timestamp: 1756549412040 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py312hf890105_5.conda + sha256: 7e4fd809fb926627c8c626d1265785c1d0420ea34aa910a0c901c48737474259 + md5: 1630127107ab1cfb9ca9468f9318fa29 depends: - - __osx >=10.13 - - brotli-bin 1.2.0 h8616949_1 - - libbrotlidec 1.2.0 h8616949_1 - - libbrotlienc 1.2.0 h8616949_1 - license: MIT - license_family: MIT + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - numpy >=1.23,<3 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - boost <0.0a0 + - py-boost <0.0a0 + license: BSL-1.0 purls: [] - size: 20194 - timestamp: 1764017661405 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda - sha256: 422ac5c91f8ef07017c594d9135b7ae068157393d2a119b1908c7e350938579d - md5: 48ece20aa479be6ac9a284772827d00c + size: 124523 + timestamp: 1766348623465 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed depends: - - __osx >=11.0 - - brotli-bin 1.2.0 hc919400_1 - - libbrotlidec 1.2.0 hc919400_1 - - libbrotlienc 1.2.0 hc919400_1 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 20237 - timestamp: 1764018058424 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda - sha256: 64b137f30b83b1dd61db6c946ae7511657eead59fdf74e84ef0ded219605aa94 - md5: af39b9a8711d4a8d437b52c1d78eb6a1 + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 depends: - __glibc >=2.17,<3.0.a0 - - libbrotlidec 1.2.0 hb03c661_1 - - libbrotlienc 1.2.0 hb03c661_1 + - libbrotlicommon 1.2.0 hb03c661_1 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 21021 - timestamp: 1764017221344 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda - sha256: dcb5a2b29244b82af2545efad13dfdf8dddb86f88ce64ff415be9e7a10cc0383 - md5: 34803b20dfec7af32ba675c5ccdbedbf + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f depends: - - __osx >=10.13 - - libbrotlidec 1.2.0 h8616949_1 - - libbrotlienc 1.2.0 h8616949_1 + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 18589 - timestamp: 1764017635544 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda - sha256: e2d142052a83ff2e8eab3fe68b9079cad80d109696dc063a3f92275802341640 - md5: 377d015c103ad7f3371be1777f8b584c + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + build_number: 5 + sha256: 8352f472c49c42a83a20387b5f6addab1f910c5a62f4f5b8998d7dc89131ba2e + md5: 9b6cb3aa4b7912121c64b97a76ca43d5 depends: - - __osx >=11.0 - - libbrotlidec 1.2.0 hc919400_1 - - libbrotlienc 1.2.0 hc919400_1 - license: MIT - license_family: MIT + - libblas 3.11.0 5_h5875eb1_mkl + constrains: + - liblapack 3.11.0 5*_mkl + - liblapacke 3.11.0 5*_mkl + - blas 2.305 mkl + track_features: + - blas_mkl + license: BSD-3-Clause + license_family: BSD purls: [] - size: 18628 - timestamp: 1764018033635 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda - sha256: 49df13a1bb5e388ca0e4e87022260f9501ed4192656d23dc9d9a1b4bf3787918 - md5: 64088dffd7413a2dd557ce837b4cbbdb + size: 18385 + timestamp: 1765818571086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-12.2.0.37-hecca717_0.conda + sha256: 5fef778f1e68d1b0a913d6511bec607137e9d2e8e9e44bc564309712e82440fe + md5: e7f96ec366228e01de0047d62b94c446 depends: - __glibc >=2.17,<3.0.a0 + - cuda-version >=13.2,<13.3.0a0 - libgcc >=14 - libstdcxx >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - constrains: - - libbrotlicommon 1.2.0 hb03c661_1 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 368300 - timestamp: 1764017300621 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda - sha256: 8854a80360128157e8d05eb57c1c7e7c1cb10977e4c4557a77d29c859d1f104b - md5: 01fdbccc39e0a7698e9556e8036599b7 - depends: - - __osx >=10.13 - - libcxx >=19 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - constrains: - - libbrotlicommon 1.2.0 h8616949_1 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 389534 - timestamp: 1764017976737 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda - sha256: 6178775a86579d5e8eec6a7ab316c24f1355f6c6ccbe84bb341f342f1eda2440 - md5: 311fcf3f6a8c4eb70f912798035edd35 + license: LicenseRef-NVIDIA-End-User-License-Agreement + purls: [] + size: 184427617 + timestamp: 1773100202937 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda + sha256: 5454709d9fb6e9c3dd6423bc284fa7835a7823bfa8323f6e8786cdd555101fab + md5: 0a5563efed19ca4461cf927419b6eb73 depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - constrains: - - libbrotlicommon 1.2.0 hc919400_1 - license: MIT + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.21.3,<1.22.0a0 + - libgcc >=14 + - libnghttp2 >=1.67.0,<2.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.4,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 359503 - timestamp: 1764018572368 -- conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda - sha256: b6e73522a3fc2d85e8eef363ff9dfe1d7995770b55968dc7e9e23ebd02f56e84 - md5: 8e1cd1dca850168941e200bc5e626e16 - depends: - - python >=3.9 - - python-dateutil >=2.4.0 - - six - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/bson?source=hash-mapping - size: 16836 - timestamp: 1736456262819 -- pypi: https://files.pythonhosted.org/packages/46/ed/ad1755f82cd5a0baafe342e7154696a93e57f04f86515402f14e5beceb36/bump_my_version-1.2.7-py3-none-any.whl - name: bump-my-version - version: 1.2.7 - sha256: 16f89360f979c0a8eb3249ebe3e13ae4f0cb5481d7bb58e12a9f66996922acfd - requires_dist: - - click<8.4 - - httpx>=0.28.1 - - pydantic-settings - - pydantic>=2.0.0 - - questionary - - rich - - rich-click - - tomlkit - - wcmatch>=8.5.1 - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 - md5: d2ffd7602c02f2b316fd921d39876885 + purls: [] + size: 462942 + timestamp: 1767821743793 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 + md5: 6c77a605a7a689d17d4819c0f8ac9a00 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - license: bzip2-1.0.6 - license_family: BSD + license: MIT + license_family: MIT purls: [] - size: 260182 - timestamp: 1771350215188 -- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda - sha256: 9f242f13537ef1ce195f93f0cc162965d6cc79da578568d6d8e50f70dd025c42 - md5: 4173ac3b19ec0a4f400b4f782910368b + size: 73490 + timestamp: 1761979956660 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 + md5: c277e0a4d549b03ac1e9d6cbbe3d017b depends: - - __osx >=10.13 - license: bzip2-1.0.6 + - ncurses + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause license_family: BSD purls: [] - size: 133427 - timestamp: 1771350680709 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df - md5: 620b85a3f45526a8bc4d23fd78fc22f0 + size: 134676 + timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 depends: - - __osx >=11.0 - license: bzip2-1.0.6 + - libgcc-ng >=12 + license: BSD-2-Clause license_family: BSD purls: [] - size: 124834 - timestamp: 1771350416561 -- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e - md5: 920bb03579f15389b9e512095ad995b7 + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 + md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 + constrains: + - expat 2.7.4.* license: MIT license_family: MIT purls: [] - size: 207882 - timestamp: 1765214722852 -- conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - sha256: 2f5bc0292d595399df0d168355b4e9820affc8036792d6984bd751fdda2bcaea - md5: fc9a153c57c9f070bebaa7eef30a8f17 + size: 76798 + timestamp: 1771259418166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb depends: - - __osx >=10.13 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 186122 - timestamp: 1765215100384 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 - md5: bcb3cba70cf1eec964a03b4ba7775f01 + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.2-ha770c72_0.conda + sha256: 2e1bfe1e856eb707d258f669ef6851af583ceaffab5e64821b503b0f7cd09e9e + md5: 26c746d14402a3b6c684d045b23b9437 depends: - - __osx >=11.0 - license: MIT - license_family: MIT + - libfreetype6 >=2.14.2 + license: GPL-2.0-only OR FTL purls: [] - size: 180327 - timestamp: 1765215064054 -- conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.23.1-hc31b594_0.conda - sha256: b6ce82ebe3cf24e70179bd656eacfa97ce9df9a500f2cec6843043466a5e6af8 - md5: 68ceffc6cadae61846a207cae60de094 + size: 8035 + timestamp: 1772757210108 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.2-h73754d4_0.conda + sha256: aba65b94bdbed52de17ec3d0c6f2ebac2ef77071ad22d6900d1614d0dd702a0c + md5: 8eaba3d1a4d7525c6814e861614457fd + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - freetype >=2.14.2 + license: GPL-2.0-only OR FTL + purls: [] + size: 386316 + timestamp: 1772757193822 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 + md5: 0aa00f03f9e39fb9876085dee11a85d4 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1041788 + timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 + md5: d5e96b1ed75ca01906b3d2469b4ce493 + depends: + - libgcc 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27526 + timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee + md5: 9063115da5bc35fdc3e1002e69b9ef6e + depends: + - libgfortran5 15.2.0 h68bc16d_18 + constrains: + - libgfortran-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27523 + timestamp: 1771378269450 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + sha256: 539b57cf50ec85509a94ba9949b7e30717839e4d694bc94f30d41c9d34de2d12 + md5: 646855f357199a12f02a87382d429b75 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.2.0 + constrains: + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 2482475 + timestamp: 1771378241063 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda + sha256: a27e44168a1240b15659888ce0d9b938ed4bdb49e9ea68a7c1ff27bcea8b55ce + md5: bb26456332b07f68bf3b7622ed71c0da + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + constrains: + - glib 2.86.4 *_1 + license: LGPL-2.1-or-later + purls: [] + size: 4398701 + timestamp: 1771863239578 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 + md5: 239c5e9546c38a1e884d69effcf4c882 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 603262 + timestamp: 1771378117851 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_hafda6a7_1003.conda + sha256: b9e6340da35245d5f3b7b044b4070b4980809d340bddf16c942a97a83f146aa4 + md5: 4fe840c6d6b3719b4231ed89d389bb17 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libstdcxx >=14 - - lz4-c >=1.10.0,<1.11.0a0 - - zlib-ng >=2.3.3,<2.4.0a0 - - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 license: BSD-3-Clause license_family: BSD purls: [] - size: 353899 - timestamp: 1772620395951 -- conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda - sha256: 4766ee318fd57b956875064a390d277755ad4dfeb5ef6cd7f592c31058cd7a79 - md5: ea2963a308e8fe5eba56405e7820db6c + run_exports: + weak: + - libhwloc >=2.12.1,<2.12.2.0a0 + size: 2449346 + timestamp: 1765089858592 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.4.0-h3a9caae_0.conda + sha256: 02ab5e50c3921e88ad4dd0bc8f3fe282d2d7d03a20203d3281954b94641deb3d + md5: 9544a7225c8366ea2c397aad5fb53470 depends: - - __osx >=10.13 - - libcxx >=19 - - lz4-c >=1.10.0,<1.11.0a0 - - zlib-ng >=2.3.3,<2.4.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - license_family: BSD + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 OR BSD-3-Clause purls: [] - size: 288387 - timestamp: 1772621206480 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.23.1-hf9886e1_0.conda - sha256: 666200e72dda686cb705365eb6710d123b1e2b0cc4227e51a674a1feea3ba05e - md5: 67a5ede6098a3f291c90a083458ea911 + run_exports: + weak: + - libhwy >=1.4.0,<1.5.0a0 + size: 1431901 + timestamp: 1784325535334 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f + md5: 915f5995e94f60e9a4826e0b0920ee88 depends: - - __osx >=11.0 - - libcxx >=19 - - lz4-c >=1.10.0,<1.11.0a0 - - zlib-ng >=2.3.3,<2.4.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - license_family: BSD + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-only purls: [] - size: 255095 - timestamp: 1772620752027 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc - md5: 4492fd26db29495f0ba23f146cd5638d + size: 790176 + timestamp: 1754908768807 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + sha256: cc9aba923eea0af8e30e0f94f2ad7156e2984d80d1e8e7fe6be5a1f257f0eb32 + md5: 8397539e3a0bbd1695584fb4f927485a depends: - - __unix - license: ISC + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib purls: [] - size: 147413 - timestamp: 1772006283803 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - noarch: python - sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 - md5: 9b347a7ec10940d3f7941ff6c460b551 + size: 633710 + timestamp: 1762094827865 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + build_number: 5 + sha256: b411a9dccb21cd6231f8f66b63916a6520a7b23363e6f9d1d111e8660f2798b0 + md5: 88155c848e1278b0990692e716c9eab4 depends: - - cached_property >=1.5.2,<1.5.3.0a0 + - libblas 3.11.0 5_h5875eb1_mkl + constrains: + - liblapacke 3.11.0 5*_mkl + - libcblas 3.11.0 5*_mkl + - blas 2.305 mkl + track_features: + - blas_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 4134 - timestamp: 1615209571450 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 - md5: 576d629e47797577ab0f1b351297ef4a + size: 18398 + timestamp: 1765818583873 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + sha256: 9787df8c22a59c9a70d3e5a10db9ad663485e75e9ccc3f09bd092cb7b95e0dab + md5: 1390b7c5ac0b1d8e447bc5efa6d3c8c2 depends: - - python >=3.6 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/cached-property?source=hash-mapping - size: 11065 - timestamp: 1615209567874 -- conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda - sha256: edfecb626da69607f926f51ad0d24942bfe9f7a29391d55d4ac62403e878605b - md5: a66a1542c3ed584ca4fdb23955d81e91 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 112995 + timestamp: 1786348617826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.10.0-nompi_hbf2fc22_100.conda + sha256: f38b00b29c9495b71c12465397c735224ebaef71ad01278c3b9cb69dac685b65 + md5: 0eb36a09dad274e750d60b49aaec0af7 + depends: + - __glibc >=2.17,<3.0.a0 + - attr >=2.5.2,<2.6.0a0 + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - hdf4 >=4.2.15,<4.2.16.0a0 + - hdf5 >=1.14.6,<1.14.7.0a0 + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.18.0,<9.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + - libzip >=1.11.2,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libnetcdf >=4.10.0,<4.10.1.0a0 + size: 862222 + timestamp: 1772190364667 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 + md5: b499ce4b026493a13774bcf0f4c33849 depends: - - python >=3.10 + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.5,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/cachetools?source=hash-mapping - size: 19034 - timestamp: 1773120473852 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - sha256: 3bd6a391ad60e471de76c0e9db34986c4b5058587fbf2efa5a7f54645e28c2c7 - md5: 09262e66b19567aff4f592fb53b28760 + purls: [] + size: 666600 + timestamp: 1756834976695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 depends: - __glibc >=2.17,<3.0.a0 - - fontconfig >=2.15.0,<3.0a0 - - fonts-conda-ecosystem - - freetype >=2.12.1,<3.0a0 - - icu >=75.1,<76.0a0 - - libexpat >=2.6.4,<3.0a0 - libgcc >=13 - - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.47,<1.7.0a0 - - libstdcxx >=13 - - libxcb >=1.17.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - pixman >=0.44.2,<1.0a0 - - xorg-libice >=1.1.2,<2.0a0 - - xorg-libsm >=1.2.5,<2.0a0 - - xorg-libx11 >=1.8.11,<2.0a0 - - xorg-libxext >=1.3.6,<2.0a0 - - xorg-libxrender >=0.9.12,<0.10.0a0 - license: LGPL-2.1-only or MPL-1.1 + license: LGPL-2.1-only + license_family: GPL purls: [] - size: 978114 - timestamp: 1741554591855 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda - sha256: d4297c3a9bcff9add3c5a46c6e793b88567354828bcfdb6fc9f6b1ab34aa4913 - md5: 32403b4ef529a2018e4d8c4f2a719f16 + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + sha256: 3b3f19ced060013c2dd99d9d46403be6d319d4601814c772a3472fe2955612b0 + md5: 7c7927b404672409d9917d49bff5f2d6 depends: - - __osx >=10.13 - - fontconfig >=2.15.0,<3.0a0 - - fonts-conda-ecosystem - - freetype >=2.12.1,<3.0a0 - - icu >=75.1,<76.0a0 - - libcxx >=18 - - libexpat >=2.6.4,<3.0a0 - - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.47,<1.7.0a0 - - libzlib >=1.3.1,<2.0a0 - - pixman >=0.44.2,<1.0a0 - license: LGPL-2.1-only or MPL-1.1 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later purls: [] - size: 893252 - timestamp: 1741554808521 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-h6a3b0d2_0.conda - sha256: 00439d69bdd94eaf51656fdf479e0c853278439d22ae151cabf40eb17399d95f - md5: 38f6df8bc8c668417b904369a01ba2e2 + size: 33418 + timestamp: 1734670021371 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + sha256: 36ade759122cdf0f16e2a2562a19746d96cf9c863ffaa812f2f5071ebbe9c03c + md5: 5f13ffc7d30ffec87864e678df9957b4 depends: - - __osx >=11.0 - - fontconfig >=2.15.0,<3.0a0 - - fonts-conda-ecosystem - - freetype >=2.12.1,<3.0a0 - - icu >=75.1,<76.0a0 - - libcxx >=18 - - libexpat >=2.6.4,<3.0a0 - - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.47,<1.7.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - libzlib >=1.3.1,<2.0a0 - - pixman >=0.44.2,<1.0a0 - license: LGPL-2.1-only or MPL-1.1 + license: zlib-acknowledgement purls: [] - size: 896173 - timestamp: 1741554795915 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656 - md5: 765c4d97e877cdbbb88ff33152b86125 + size: 317669 + timestamp: 1770691470744 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda + sha256: bbab2c3e6f650f2bd1bc84d88e6a20fefa6a401fa445bb4b97c509c1b3a89fa8 + md5: a8ac9a6342569d1714ae1b53ae2fcadb depends: - - python >=3.10 - license: ISC - purls: - - pkg:pypi/certifi?source=compressed-mapping - size: 151445 - timestamp: 1772001170301 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda - sha256: 7dafe8173d5f94e46cf9cd597cc8ff476a8357fbbd4433a8b5697b2864845d9c - md5: 648ee28dcd4e07a1940a17da62eccd40 + - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 + - krb5 >=1.21.3,<1.22.0a0 + - libgcc >=14 + - openldap >=2.6.10,<2.7.0a0 + - openssl >=3.5.4,<4.0a0 + license: PostgreSQL + purls: [] + size: 2711480 + timestamp: 1764345810429 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + sha256: 0ef142ac31e6fd59b4af89ac800acb6deb3fbd9cc4ccf070c03cc2c784dc7296 + md5: 07479fc04ba3ddd5d9f760ef1635cfa7 depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 - libgcc >=14 - - pycparser - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 295716 - timestamp: 1761202958833 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda - sha256: e2888785e50ef99c63c29fb3cfbfb44cdd50b3bb7cd5f8225155e362c391936f - md5: cf70c8244e7ceda7e00b1881ad7697a9 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4372578 + timestamp: 1766316228461 +- conda: https://conda.anaconda.org/conda-forge/linux-64/librdkit-2025.09.5-h3c5c181_0.conda + sha256: 06f20f6ce9dbc77000bc3eb3e597e5b4228e1bcdbf2debab949fca3c996a45a2 + md5: 716755a81e23ba9065cb6d5dba07231a depends: - - __osx >=10.13 - - libffi >=3.5.2,<3.6.0a0 - - pycparser - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 288241 - timestamp: 1761203170357 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda - sha256: 597e986ac1a1bd1c9b29d6850e1cdea4a075ce8292af55568952ec670e7dd358 - md5: 503ac138ad3cfc09459738c0f5750705 + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - libboost >=1.86.0,<1.87.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 10130647 + timestamp: 1770019736513 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda + sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 + md5: a587892d3c13b6621a6091be690dbca2 depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pycparser - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 288080 - timestamp: 1761203317419 -- pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - name: cfgv - version: 3.5.0 - sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda - sha256: 05ea76a016c77839b64f9f8ec581775f6c8a259044bd5b45a177e46ab4e7feac - md5: beb628209b2b354b98203066f90b3287 + - libgcc-ng >=12 + license: ISC + purls: [] + size: 205978 + timestamp: 1716828628198 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0c1763c_0.conda + sha256: 3d1d47d465a3e7ac37c9c37802313a4a33ea91057bb7b87b9ec31e309f7dc163 + md5: 56d6a60586cc886c9a6fd3288de4b9a2 depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/charset-normalizer?source=compressed-mapping - size: 53210 - timestamp: 1772816516728 -- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - sha256: 38cfe1ee75b21a8361c8824f5544c3866f303af1762693a178266d7f198e8715 - md5: ea8a6c3256897cc31263de9f455e25d9 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 959511 + timestamp: 1785016103820 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 + md5: eecce068c7e4eddeb169591baac20ac4 depends: - - python >=3.10 - - __unix - - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/click?source=hash-mapping - size: 97676 - timestamp: 1764518652276 -- conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda - sha256: cc17620f8c7f90e45b0e398ff01b41bc2ecf48a600c8e03ca229c251eb9949a3 - md5: 24448fbe066e17f2c3b0bfbe2d251330 + purls: [] + size: 304790 + timestamp: 1745608545575 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e + md5: 1b08cd684f34175e4514474793d44bcb depends: - - click >=7.0,<9 - - python >=3.6,<4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/click-option-group?source=hash-mapping - size: 16770 - timestamp: 1686394351507 -- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 - md5: 2da13f2b299d8e1995bafbbe9689a2f7 + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 he0feb66_18 + constrains: + - libstdcxx-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 5852330 + timestamp: 1771378262446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda + sha256: 3c902ffd673cb3c6ddde624cdb80f870b6c835f8bf28384b0016e7d444dd0145 + md5: 6235adb93d064ecdf3d44faee6f468de depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/comm?source=hash-mapping - size: 14690 - timestamp: 1753453984907 -- conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda - sha256: 62447faf7e8eb691e407688c0b4b7c230de40d5ecf95bf301111b4d05c5be473 - md5: 43c2bc96af3ae5ed9e8a10ded942aa50 + - libstdcxx 15.2.0 h934c35e_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27575 + timestamp: 1771378314494 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 + md5: cd5a90476766d53e901500df9215e927 depends: - - numpy >=1.25 - - python - __glibc >=2.17,<3.0.a0 + - lerc >=4.0.0,<5.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.0,<4.0a0 + - liblzma >=5.8.1,<6.0a0 - libstdcxx >=14 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + size: 435273 + timestamp: 1762022005702 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.8.0-cpu_mkl_h09b866c_102.conda + sha256: 7033538481a66ed4f59f3b88a952f55b85596429271643678fd14a4e0a9666b9 + md5: 0194f4ea9e74964548ddb220b61d4712 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libblas * *mkl + - libcblas >=3.9.0,<4.0a0 - libgcc >=14 - - python_abi 3.12.* *_cp312 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libstdcxx >=14 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=21.1.4 + - mkl >=2025.3.0,<2026.0a0 + - pybind11-abi 4 + - sleef >=3.9.0,<4.0a0 + constrains: + - pytorch-cpu 2.8.0 + - pytorch-gpu <0.0a0 + - pytorch 2.8.0 cpu_mkl_*_102 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/contourpy?source=compressed-mapping - size: 320386 - timestamp: 1769155979897 -- conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda - sha256: 6c03943009b07c6deb3a64afa094b6ca694062b58127a4da6f656a13d508c340 - md5: 625f08687ba33cc9e57865e7bf8e8123 + purls: [] + size: 58954930 + timestamp: 1762096008648 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee + md5: db409b7c1720428638e7c0d509d3e1b5 depends: - - numpy >=1.25 - - python - - __osx >=10.13 - - libcxx >=19 - - python_abi 3.12.* *_cp312 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/contourpy?source=hash-mapping - size: 298198 - timestamp: 1769156053873 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda - sha256: fa1b3967c644c1ffaf8beba3d7aee2301a8db32c0e9a56649a0e496cf3abd27c - md5: f9cce0bc86b46533489a994a47d3c7d2 + purls: [] + size: 40311 + timestamp: 1766271528534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b + md5: 0f03292cc56bf91a077a134ea8747118 depends: - - numpy >=1.25 - - python - - python 3.12.* *_cpython - - __osx >=11.0 - - libcxx >=19 - - python_abi 3.12.* *_cp312 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 895108 + timestamp: 1753948278280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b + md5: aea31d2e5b1091feca96fcfe945c3cf9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - libwebp 1.6.0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/contourpy?source=compressed-mapping - size: 286084 - timestamp: 1769156157865 -- pypi: https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl - name: coverage - version: 7.13.4 - sha256: 40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - name: coverage - version: 7.13.4 - sha256: 2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl - name: coverage - version: 7.13.4 - sha256: 02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda - noarch: generic - sha256: d3e9bbd7340199527f28bbacf947702368f31de60c433a16446767d3c6aaf6fe - md5: f54c1ffb8ecedb85a8b7fcde3a187212 + purls: [] + size: 429011 + timestamp: 1752159441324 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa + md5: 92ed62436b625154323d40d5f2f11dd7 depends: - - python >=3.12,<3.13.0a0 - - python_abi * *_cp312 - license: Python-2.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - pthread-stubs + - xorg-libxau >=1.0.11,<2.0a0 + - xorg-libxdmcp + license: MIT + license_family: MIT purls: [] - size: 46463 - timestamp: 1772728929620 -- pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl - name: cryptography - version: 46.0.5 - sha256: 3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed - requires_dist: - - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' - - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' - - typing-extensions>=4.13.2 ; python_full_version < '3.11' - - bcrypt>=3.1.5 ; extra == 'ssh' - - nox[uv]>=2024.4.15 ; extra == 'nox' - - cryptography-vectors==46.0.5 ; extra == 'test' - - pytest>=7.4.0 ; extra == 'test' - - pytest-benchmark>=4.0 ; extra == 'test' - - pytest-cov>=2.10.1 ; extra == 'test' - - pytest-xdist>=3.5.0 ; extra == 'test' - - pretend>=0.7 ; extra == 'test' - - certifi>=2024 ; extra == 'test' - - pytest-randomly ; extra == 'test-randomorder' - - sphinx>=5.3.0 ; extra == 'docs' - - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' - - sphinx-inline-tabs ; extra == 'docs' - - pyenchant>=3 ; extra == 'docstest' - - readme-renderer>=30.0 ; extra == 'docstest' - - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' - - build>=1.0.0 ; extra == 'sdist' - - ruff>=0.11.11 ; extra == 'pep8test' - - mypy>=1.14 ; extra == 'pep8test' - - check-sdist ; extra == 'pep8test' - - click>=8.0.1 ; extra == 'pep8test' - requires_python: '>=3.8,!=3.9.0,!=3.9.1' -- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.2.51-hecca717_0.conda - sha256: 9de235d328b7124f715805715e9918eb7f8aa5b9c56a2afa62b84f84f98077a5 - md5: 0413baaa73be1a39d5d8e442184acc78 + size: 395888 + timestamp: 1727278577118 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + sha256: 71436e72a286ef8b57d6f4287626ff91991eb03c7bdbe835280521791efd1434 + md5: e7733bc6785ec009e47a224a71917e84 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=13.2,<13.3.0a0 + - icu >=75.1,<76.0a0 - libgcc >=14 - - libstdcxx >=14 - license: LicenseRef-NVIDIA-End-User-License-Agreement - purls: [] - size: 35736655 - timestamp: 1773100338749 -- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda - sha256: 64aebe8ccb3a2c3ff446d3c0c0e88ef4fdb069a5732c03539bf3a37243c4c679 - md5: 45676e3dd76b30ec613f1f822d450eff + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libzlib >=1.3.1,<2.0a0 constrains: - - __cuda >=13 - - cudatoolkit 13.2|13.2.* - license: LicenseRef-NVIDIA-End-User-License-Agreement + - libxml2 2.15.1 + license: MIT + license_family: MIT purls: [] - size: 21908 - timestamp: 1773093709154 -- conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - sha256: bb47aec5338695ff8efbddbc669064a3b10fe34ad881fb8ad5d64fbfa6910ed1 - md5: 4c2a8fef270f6c69591889b93f9f55c1 + run_exports: {} + size: 556302 + timestamp: 1761015637262 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda + sha256: ec0735ae56c3549149eebd7dc22c0bed91fd50c02eaa77ff418613ddda190aa8 + md5: e512be7dc1f84966d50959e900ca121f depends: - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/cycler?source=hash-mapping - size: 14778 - timestamp: 1764466758386 -- pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl - name: cyclopts - version: 4.8.0 - sha256: ef353da05fec36587d4ebce7a6e4b27515d775d184a23bab4b01426f93ddc8d4 - requires_dist: - - attrs>=23.1.0 - - docstring-parser>=0.15,<4.0 - - rich-rst>=1.3.1,<2.0.0 - - rich>=13.6.0 - - tomli>=2.0.0 ; python_full_version < '3.11' - - typing-extensions>=4.8.0 ; python_full_version < '3.11' - - ipdb>=0.13.9 ; extra == 'debug' - - line-profiler>=3.5.1 ; extra == 'debug' - - coverage[toml]>=5.1 ; extra == 'dev' - - mkdocs>=1.4.0 ; extra == 'dev' - - pre-commit>=2.16.0 ; extra == 'dev' - - pydantic>=2.11.2,<3.0.0 ; extra == 'dev' - - pytest-cov>=3.0.0 ; extra == 'dev' - - pytest-mock>=3.7.0 ; extra == 'dev' - - pytest>=8.2.0 ; extra == 'dev' - - pyyaml>=6.0.1 ; extra == 'dev' - - syrupy>=4.0.0 ; extra == 'dev' - - toml>=0.10.2,<1.0.0 ; extra == 'dev' - - trio>=0.10.0 ; extra == 'dev' - - gitpython>=3.1.31 ; extra == 'docs' - - myst-parser[linkify]>=3.0.1,<5.0.0 ; extra == 'docs' - - sphinx-autodoc-typehints>=1.25.2,<4.0.0 ; extra == 'docs' - - sphinx-copybutton>=0.5,<1.0 ; extra == 'docs' - - sphinx-rtd-dark-mode>=1.3.0,<2.0.0 ; extra == 'docs' - - sphinx-rtd-theme>=3.0.0,<4.0.0 ; extra == 'docs' - - sphinx>=7.4.7,<8.2.0 ; extra == 'docs' - - markdown>=3.3 ; extra == 'mkdocs' - - mkdocs>=1.4.0 ; extra == 'mkdocs' - - pymdown-extensions>=10.0 ; extra == 'mkdocs' - - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'toml' - - trio>=0.10.0 ; extra == 'trio' - - pyyaml>=6.0.1 ; extra == 'yaml' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda - sha256: ee09ad7610c12c7008262d713416d0b58bf365bc38584dce48950025850bdf3f - md5: cae723309a49399d2949362f4ab5c9e4 + - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libxml2-16 2.15.1 ha9997c6_0 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.1 + size: 45283 + timestamp: 1761015644057 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda + sha256: 991e7348b0f650d495fb6d8aa9f8c727bdf52dabf5853c0cc671439b160dce48 + md5: a7b27c075c9b7f459f1c022090697cba depends: - __glibc >=2.17,<3.0.a0 - - krb5 >=1.21.3,<1.22.0a0 + - bzip2 >=1.0.8,<2.0a0 - libgcc >=13 - - libntlm >=1.8,<2.0a0 - - libstdcxx >=13 - - libxcrypt >=4.4.36 - - openssl >=3.5.0,<4.0a0 - license: BSD-3-Clause-Attribution + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.3.2,<4.0a0 + license: BSD-3-Clause license_family: BSD purls: [] - size: 209774 - timestamp: 1750239039316 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda - sha256: beee5d279d48d67ba39f1b8f64bc050238d3d465fb9a53098eba2a85e9286949 - md5: 314cd5e4aefc50fec5ffd80621cfb4f8 + size: 109043 + timestamp: 1730442108429 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + sha256: eb8a0db0aa570124f7d2a93d7c7f596e3390df5e047818d873baad32985fc736 + md5: 0de0122d9570a8ab637c6b73db268389 depends: - - __osx >=10.13 - - krb5 >=1.21.3,<1.22.0a0 - - libcxx >=18 - - libntlm >=1.8,<2.0a0 - - openssl >=3.5.0,<4.0a0 - license: BSD-3-Clause-Attribution - license_family: BSD + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other purls: [] - size: 197689 - timestamp: 1750239254864 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-ha1cbb27_0.conda - sha256: 7de03254fa5421e7ec2347c830a59530fb5356022ee0dc26ec1cef0be1de0911 - md5: 2867ea6551e97e53a81787fd967162b1 + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 63713 + timestamp: 1785362952714 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.0-h4922eb0_0.conda + sha256: 543c9f17cf6ee6d7b635823fb9009df421d510c36739534df6ae43eadaf6ff4e + md5: 5e7da5333653c631d27732893b934351 depends: - - __osx >=11.0 - - krb5 >=1.21.3,<1.22.0a0 - - libcxx >=18 - - libntlm >=1.8,<2.0a0 - - openssl >=3.5.0,<4.0a0 - license: BSD-3-Clause-Attribution - license_family: BSD + - __glibc >=2.17,<3.0.a0 + constrains: + - intel-openmp <0.0a0 + - openmp 22.1.0|22.1.0.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE purls: [] - size: 193732 - timestamp: 1750239236574 -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda - sha256: f20121b67149ff80bf951ccae7442756586d8789204cd08ade59397b22bfd098 - md5: ee1b48795ceb07311dd3e665dd4f5f33 + size: 6136884 + timestamp: 1772024545000 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lol-html-3.0.1-hb17b654_0.conda + sha256: 6075e31164285fcd4a087fcc680f8c928c0b47f6fb1aefc99664c2ddf0db4802 + md5: 1ce013403194fb5a6f0ed98cd6ebe26f depends: - - python + - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libstdcxx >=14 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - lol-html >=3.0.1,<3.0.2.0a0 + size: 483710 + timestamp: 1785365109373 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ls-hpack-2.3.5-h280c20c_0.conda + sha256: fe57537d4fd99845c9d94870e46fe78e0e7ace0343991dac765d55777da1e500 + md5: 5f8ace4f3cf7857ab7b80c328dc07622 + depends: - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 + - libgcc >=14 license: MIT license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2858582 - timestamp: 1769744978783 -- conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda - sha256: 310f737be38bd4a53b2c13c6387b880031fc0995a13194511cc08a48d3160462 - md5: 4e508cd9d5d630c7db0bdebb24a3be90 + purls: [] + run_exports: + weak: + - ls-hpack >=2.3.5,<2.3.6.0a0 + size: 99800 + timestamp: 1779602199229 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 + md5: 9de5350a85c4a20c685259b889aa6393 depends: - - python - - libcxx >=19 - - __osx >=10.13 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2764546 - timestamp: 1769744989784 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda - sha256: f0ca130b5ffd6949673d3c61d7b8562ab76ad8debafb83f8b3443d30c172f5eb - md5: da3b5efcb0caabcede61a6ce4e0a7669 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 167055 + timestamp: 1733741040117 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + sha256: 5c6bbeec116e29f08e3dad3d0524e9bc5527098e12fc432c0e5ca53ea16337d4 + md5: 45161d96307e3a447cc3eb5896cf6f8c depends: - - python - - __osx >=11.0 - - python 3.12.* *_cpython - - libcxx >=19 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2752978 - timestamp: 1769744996462 -- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 - md5: 9ce473d1d1be1cc3810856a48b3fab32 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 191060 + timestamp: 1753889274283 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda + sha256: 5f3aad1f3a685ed0b591faad335957dbdb1b73abfd6fc731a0d42718e0653b33 + md5: 93a4752d42b12943a355b682ee43285b depends: - - python >=3.9 - license: BSD-2-Clause + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/decorator?source=hash-mapping - size: 14129 - timestamp: 1740385067843 -- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be - md5: 961b3a227b437d82ad7054484cfa71b2 + - pkg:pypi/markupsafe?source=hash-mapping + size: 26057 + timestamp: 1772445297924 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py312he3d6523_0.conda + sha256: 70cf0e7bfd50ef50eb712a6ca1eef0ef0d63b7884292acc81353327b434b548c + md5: b8dc157bbbb69c1407478feede8b7b42 depends: - - python >=3.6 + - __glibc >=2.17,<3.0.a0 + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.22.0 + - freetype + - kiwisolver >=1.3.1 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libstdcxx >=14 + - numpy >=1.23 + - numpy >=1.23,<3 + - packaging >=20.0 + - pillow >=8 + - pyparsing >=2.3.1 + - python >=3.12,<3.13.0a0 + - python-dateutil >=2.7 + - python_abi 3.12.* *_cp312 + - qhull >=2020.2,<2020.3.0a0 + - tk >=8.6.13,<8.7.0a0 license: PSF-2.0 license_family: PSF purls: - - pkg:pypi/defusedxml?source=hash-mapping - size: 24062 - timestamp: 1615232388757 -- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - name: distlib - version: 0.4.0 - sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 -- pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl - name: docstring-parser - version: 0.17.0 - sha256: cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708 - requires_dist: - - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' - - pydoctor>=25.4.0 ; extra == 'dev' - - pytest ; extra == 'dev' - - pydoctor>=25.4.0 ; extra == 'docs' - - pytest ; extra == 'test' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - name: docutils - version: 0.22.4 - sha256: d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 - md5: 8e662bd460bda79b1ea39194e3c4c9ab - depends: - - python >=3.10 - - typing_extensions >=4.6.0 - license: MIT and PSF-2.0 - purls: - - pkg:pypi/exceptiongroup?source=hash-mapping - size: 21333 - timestamp: 1763918099466 -- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad - md5: ff9efb7f7469aed3c4a8106ffa29593c + - pkg:pypi/matplotlib?source=hash-mapping + size: 8442149 + timestamp: 1763055517581 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mdtraj-1.11.1-np2py312h8baca0b_1.conda + sha256: 66f32f08a8769482f1fae72051dccd74beaf4916cc33bffc9ad5b13db2d49066 + md5: a4c60c0060137d103145b33cdd8001c2 depends: - - python >=3.10 - license: MIT - license_family: MIT + - python + - setuptools + - packaging + - numpy >=1.25.0 + - scipy + - pandas <3 + - pyparsing + - pytables + - snappy + - zlib + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - numpy >=1.23,<3 + - python_abi 3.12.* *_cp312 + - libzlib >=1.3.1,<2.0a0 + constrains: + - gsd !=3.0.0 + license: LGPL-2.1-or-later purls: - - pkg:pypi/executing?source=hash-mapping - size: 30753 - timestamp: 1756729456476 -- conda: https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h3b011a4_112.conda - sha256: a564b8af44a113173c7d42ffe37a8d600e6ea21f6db87d252135ba07914a3d10 - md5: af1311c2d5e4bfc5cce2b86804c77972 + - pkg:pypi/mdtraj?source=hash-mapping + size: 1534947 + timestamp: 1769108693196 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda + sha256: 659d79976f06d2b796a0836414573a737a0856b05facfa77e5cc114081a8b3d4 + md5: f121ddfc96e6a93a26d85906adf06208 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 - libgcc >=14 - - libgfortran - - libgfortran5 >=14.3.0 - libstdcxx >=14 - license: GPL-2.0-or-later - license_family: GPL + - llvm-openmp >=21.1.8 + - tbb >=2022.3.0 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + size: 125728406 + timestamp: 1767634121080 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda + sha256: 1bf794ddf2c8b3a3e14ae182577c624fa92dea975537accff4bc7e5fea085212 + md5: aa14b9a5196a6d8dd364164b7ce56acf + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=13 + - mpfr >=4.2.1,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + size: 116777 + timestamp: 1725629179524 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda + sha256: f25d2474dd557ca66c6231c8f5ace5af312efde1ba8290a6ea5e1732a4e669c0 + md5: 2eeb50cab6652538eee8fc0bc3340c81 + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=13 + license: LGPL-3.0-only + license_family: LGPL purls: [] - size: 1925113 - timestamp: 1771754008607 -- conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.10-nompi_h54214ab_112.conda - sha256: 0b263b6fd1421455d8dc33f4c12766eb4a1d5bdd1ff82634323e18b67a4d139f - md5: 87016517d46d992fa64ed193101e512a + size: 634751 + timestamp: 1725746740014 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 depends: - - __osx >=11.0 - - libcxx >=19 - - libgfortran - - libgfortran5 >=14.3.0 - - llvm-openmp >=19.1.7 - license: GPL-2.0-or-later - license_family: GPL + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause purls: [] - size: 1807039 - timestamp: 1771754664178 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.10-nompi_haf1500d_112.conda - sha256: 473a982a1bbbc04ca4537f779ed54e4c3db81dac9b1a31851eb0df56816de450 - md5: 0879b3d39932eb34940e0b85a01e5b50 + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.4-nompi_he05732c_100.conda + sha256: d3eb87406c53f808b22377d4d9bd170d5cb113939a7740b8102fe2859bcc5ba3 + md5: 133731e893e1735a5e8f13c11b725016 depends: - - __osx >=11.0 - - libcxx >=19 + - libnetcdf * nompi_* + - hdf5 * nompi_* + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libgfortran5 >=14.4.0 - libgfortran - - libgfortran5 >=14.3.0 - - llvm-openmp >=19.1.7 - license: GPL-2.0-or-later - license_family: GPL + - libzlib >=1.3.2,<2.0a0 + - hdf5 >=1.14.6,<1.14.7.0a0 + - libnetcdf >=4.10.0,<4.10.1.0a0 + license: NetCDF purls: [] - size: 757377 - timestamp: 1771753896425 -- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda - sha256: 3e9c0c2e956be0b6495e5102378cebbd666b4a58fe1c114bee41fd9078f9bf82 - md5: e1301f0b6a104ca461697f394be2bd25 + run_exports: + weak: + - netcdf-fortran >=4.6.4,<4.7.0a0 + size: 478512 + timestamp: 1785419253947 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-mkl_py312h4fb851f_1.conda + sha256: 39c82aed18b66039a6f1bcc7673a1e4240243fc3cca48a0be12d0c8d1cba4cf9 + md5: 6c2d17edc3f919957556873840ddf363 depends: - - python >=3.10 - license: Unlicense + - __glibc >=2.17,<3.0.a0 + - libblas * *mkl + - libgcc >=14 + - libstdcxx >=14 + - mkl >=2025.3.0,<2026.0a0 + - numpy >=1.23,<3 + - numpy >=1.23.0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: MIT + license_family: MIT purls: - - pkg:pypi/filelock?source=hash-mapping - size: 25792 - timestamp: 1773119024631 -- conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - sha256: acdb7b73d84268773fcc8192965994554411edc488ec3447925a62154e9d3baa - md5: f1e618f2f783427019071b14a111b30d + - pkg:pypi/numexpr?source=hash-mapping + size: 217775 + timestamp: 1764766736997 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda + sha256: fec4d37e1a7c677ddc07bb968255df74902733398b77acc1d05f9dc599e879df + md5: 3569a8fca2dd3202e4ab08f42499f6d3 depends: - - python >=3.9 - - typing-extensions + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 + - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/flexcache?source=hash-mapping - size: 16674 - timestamp: 1733663669958 -- conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - sha256: 9bdad0cd9fb6d67e48798c03930d634ea2d33a894d30439d3d7bdffd3c21af7b - md5: 6dc4e43174cd552452fdb8c423e90e69 + - pkg:pypi/numpy?source=hash-mapping + size: 8757566 + timestamp: 1770098484112 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda + sha256: 2254dae821b286fb57c61895f2b40e3571a070910fdab79a948ff703e1ea807b + md5: 56f8947aa9d5cf37b0b3d43b83f34192 depends: - - python >=3.9 - - typing-extensions - - typing_extensions - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/flexparser?source=hash-mapping - size: 28686 - timestamp: 1733663636245 -- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b - md5: 0c96522c6bdaed4b1566d11387caaf45 - license: BSD-3-Clause + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - opencl-headers >=2024.10.24 + license: BSD-2-Clause license_family: BSD purls: [] - size: 397370 - timestamp: 1566932522327 -- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c - md5: 34893075a5c9e55cdafac56607368fc6 - license: OFL-1.1 - license_family: Other - purls: [] - size: 96530 - timestamp: 1620479909603 -- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 - md5: 4d59c254e01d9cde7957100457e2d5fb - license: OFL-1.1 - license_family: Other - purls: [] - size: 700814 - timestamp: 1620479612257 -- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 - md5: 49023d73832ef61042f6a237cb2687e7 - license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 - license_family: Other - purls: [] - size: 1620504 - timestamp: 1727511233259 -- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda - sha256: aa4a44dba97151221100a637c7f4bde619567afade9c0265f8e1c8eed8d7bd8c - md5: 867127763fbe935bab59815b6e0b7b5c + size: 106742 + timestamp: 1743700382939 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-system-1.0.0-1.tar.bz2 + sha256: cb0ce5ce5ede1be2fd9edf88abd3ce3e6c2b7397c0283d623e4d8ccf96a1ed09 + md5: 577a4bd049737b11a24524e39a16a1f3 depends: - - __glibc >=2.17,<3.0.a0 - - libexpat >=2.7.4,<3.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libgcc >=14 - - libuuid >=2.41.3,<3.0a0 - - libzlib >=1.3.1,<2.0a0 - license: MIT - license_family: MIT + - ocl-icd + license: BSD 3-Clause + license_family: BSD purls: [] - size: 270705 - timestamp: 1771382710863 -- conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda - sha256: a972a114e618891bb50e50d8b13f5accb0085847f3aab1cf208e4552c1ab9c24 - md5: 4646a20e8bbb54903d6b8e631ceb550d + size: 4253 + timestamp: 1575483836797 +- conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda + sha256: 2b6ce54174ec19110e1b3c37455f7cd138d0e228a75727a9bba443427da30a36 + md5: 45c3d2c224002d6d0d7769142b29f986 depends: - - __osx >=11.0 - - libexpat >=2.7.4,<3.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libzlib >=1.3.1,<2.0a0 - license: MIT - license_family: MIT + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: Apache-2.0 + license_family: APACHE purls: [] - size: 237866 - timestamp: 1771382969241 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda - sha256: 851e9c778bfc54645dcab7038c0383445cbebf16f6bb2d3f62ce422b1605385a - md5: d06ae1a11b46cc4c74177ecd28de7c7a + size: 55357 + timestamp: 1749853464518 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d + md5: 11b3379b191f63139e29c0d19dee24cd depends: - - __osx >=11.0 - - libexpat >=2.7.4,<3.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpng >=1.6.50,<1.7.0a0 + - libstdcxx >=14 + - libtiff >=4.7.1,<4.8.0a0 - libzlib >=1.3.1,<2.0a0 - license: MIT - license_family: MIT + license: BSD-2-Clause + license_family: BSD purls: [] - size: 237308 - timestamp: 1771382999247 -- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 - md5: fee5683a3f04bd15cbd8318b096a27ab + size: 355400 + timestamp: 1758489294972 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda + sha256: cb0b07db15e303e6f0a19646807715d28f1264c6350309a559702f4f34f37892 + md5: 2e5bf4f1da39c0b32778561c3c4e5878 depends: - - fonts-conda-forge - license: BSD-3-Clause + - __glibc >=2.17,<3.0.a0 + - cyrus-sasl >=2.1.27,<3.0a0 + - krb5 >=1.21.3,<1.22.0a0 + - libgcc >=13 + - libstdcxx >=13 + - openssl >=3.5.0,<4.0a0 + license: OLDAP-2.8 license_family: BSD purls: [] - size: 3667 - timestamp: 1566974674465 -- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 - md5: a7970cd949a077b7cb9696379d338681 + size: 780253 + timestamp: 1748010165522 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openmm-8.4.0-py312ha28768a_2.conda + sha256: a0a19178ad323074a4595af936c72647245b7b59d975a816fcf1c2c3af3a14db + md5: bd6e733023b03e2d681267a1e048f3c1 depends: - - font-ttf-ubuntu - - font-ttf-inconsolata - - font-ttf-dejavu-sans-mono - - font-ttf-source-code-pro - license: BSD-3-Clause - license_family: BSD + - __glibc >=2.28,<3.0.a0 + - cuda-nvrtc >=13.0.88,<14.0a0 + - cuda-version >=13.0,<14 + - libcufft >=12.0.0.61,<13.0a0 + - libgcc >=14 + - libstdcxx >=14 + - numpy >=1.23,<3 + - ocl-icd >=2.3.3,<3.0a0 + - ocl-icd-system + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - rocm-core >=7.0.2,<7.1.0a0 + - scipy + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/openmm?source=hash-mapping + size: 14574920 + timestamp: 1765254789351 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + sha256: 44c877f8af015332a5d12f5ff0fb20ca32f896526a7d0cdb30c769df1144fb5c + md5: f61eb8cd60ff9057122a3d338b99c00f + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache purls: [] - size: 4059 - timestamp: 1762351264405 -- conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.0-py312h8a5da7c_0.conda - sha256: 777c80a1aa0889e6b637631c31f95d0b048848c5ba710f89ed7cedd3ad318227 - md5: 526f7ffd63820e55d7992cc1cf931a36 + size: 3164551 + timestamp: 1769555830639 +- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.19.0-py312hd9148b4_0.conda + sha256: e5cb326247948543cc2b0495e5da4d9b724ebd6e6b6907c9ed6adc1b969aeb2a + md5: 6103697b406b6605e744623d9f3d0e0b depends: - __glibc >=2.17,<3.0.a0 - - brotli - libgcc >=14 - - munkres + - libstdcxx >=14 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - unicodedata2 >=15.1.0 - license: MIT - license_family: MIT + - typing-extensions >=4.12 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/fonttools?source=compressed-mapping - size: 2935817 - timestamp: 1773137546716 -- conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda - sha256: 0e46ae1958c436e74d05a3295fde62894ffbd0ab37149965924e4aad1fa089df - md5: 62be3aaedc2839c381b94014db9aee25 + - pkg:pypi/optree?source=hash-mapping + size: 482838 + timestamp: 1771868357488 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda + sha256: f633d5f9b28e4a8f66a6ec9c89ef1b6743b880b0511330184b4ab9b7e2dda247 + md5: e597b3e812d9613f659b7d87ad252d18 depends: - - __osx >=11.0 - - brotli - - munkres + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - numpy >=1.22.4 + - numpy >=1.23,<3 - python >=3.12,<3.13.0a0 + - python-dateutil >=2.8.2 + - python-tzdata >=2022.7 - python_abi 3.12.* *_cp312 - - unicodedata2 >=15.1.0 - license: MIT - license_family: MIT + - pytz >=2020.1 + constrains: + - xarray >=2022.12.0 + - qtpy >=2.3.0 + - html5lib >=1.1 + - pandas-gbq >=0.19.0 + - tzdata >=2022.7 + - fsspec >=2022.11.0 + - fastparquet >=2022.12.0 + - odfpy >=1.4.1 + - pyxlsb >=1.0.10 + - scipy >=1.10.0 + - sqlalchemy >=2.0.0 + - pytables >=3.8.0 + - bottleneck >=1.3.6 + - pyarrow >=10.0.1 + - numexpr >=2.8.4 + - pyqt5 >=5.15.9 + - xlsxwriter >=3.0.5 + - openpyxl >=3.1.0 + - blosc >=1.21.3 + - matplotlib >=3.6.3 + - lxml >=4.9.2 + - numba >=0.56.4 + - s3fs >=2022.11.0 + - tabulate >=0.9.0 + - xlrd >=2.0.1 + - gcsfs >=2022.11.0 + - pyreadstat >=1.2.0 + - python-calamine >=0.1.7 + - zstandard >=0.19.0 + - psycopg2 >=2.9.6 + - beautifulsoup4 >=4.11.2 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/fonttools?source=compressed-mapping - size: 2941308 - timestamp: 1773150230279 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda - sha256: d87752e84621f90e9350262200fef55f054472f7779323f51717b557208e2a16 - md5: c14625bf00c41c00cea174f459287fc4 + - pkg:pypi/pandas?source=hash-mapping + size: 15099922 + timestamp: 1759266031115 +- conda: https://conda.anaconda.org/conda-forge/linux-64/parmed-4.3.1-py312h7ab193c_0.conda + sha256: 245ac71a689e9d4aed0afcfb408b8fcfc8707c7eead413bb39ece091c09c248a + md5: 5abf27d6d75333d890d1ff16bc087bf3 depends: - - __osx >=11.0 - - brotli - - munkres + - libgcc >=14 + - libstdcxx >=14 + - numpy >=1.14 + - pandas - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - unicodedata2 >=15.1.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/fonttools?source=hash-mapping - size: 2859891 - timestamp: 1765633073562 -- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 - md5: d3549fd50d450b6d9e7dddff25dd2110 - depends: - - cached-property >=1.3.0 - - python >=3.9,<4 - license: MPL-2.0 - license_family: MOZILLA + license: LGPL-2.1-or-later + license_family: LGPL purls: - - pkg:pypi/fqdn?source=hash-mapping - size: 16705 - timestamp: 1733327494780 -- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.2-ha770c72_0.conda - sha256: 36857701b46828b6760c3c1652414ee504e7fc12740261ac6fcff3959b72bd7a - md5: eeec961fec28e747e1e1dc0446277452 - depends: - - libfreetype 2.14.2 ha770c72_0 - - libfreetype6 2.14.2 h73754d4_0 - license: GPL-2.0-only OR FTL - purls: [] - size: 174292 - timestamp: 1772757205296 -- conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda - sha256: f71ca0e10a15ae2325d2957239c2273551cc2d21a7ab9501114a85a07f149aaa - md5: 0959d7034baef0a8274387fde505c347 + - pkg:pypi/parmed?source=hash-mapping + size: 19363186 + timestamp: 1769768006554 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + sha256: 5e6f7d161356fefd981948bea5139c5aa0436767751a6930cb1ca801ebb113ff + md5: 7a3bff861a6583f1889021facefc08b1 depends: - - libfreetype 2.14.2 h694c41f_0 - - libfreetype6 2.14.2 h58fbd8d_0 - license: GPL-2.0-only OR FTL + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 174265 - timestamp: 1772756344793 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda - sha256: 3c02ecdbfd94d25721811f51d0f400bf705005a728011e19db9975a8985e1021 - md5: ca730d8e7d1de1f71013edfef0e08f13 + size: 1222481 + timestamp: 1763655398280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda + build_number: 7 + sha256: 9ec32b6936b0e37bcb0ed34f22ec3116e75b3c0964f9f50ecea5f58734ed6ce9 + md5: f2cfec9406850991f4e3d960cc9e3321 depends: - - libfreetype 2.14.2 hce30654_0 - - libfreetype6 2.14.2 hdfa99f5_0 - license: GPL-2.0-only OR FTL + - libgcc-ng >=12 + - libxcrypt >=4.4.36 + license: GPL-1.0-or-later OR Artistic-1.0-Perl purls: [] - size: 173786 - timestamp: 1772756361577 -- conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 - sha256: bfa5ddb943992643a2ee2e258507beac2bdb17825e74d05f1bb0b79bb2c924fc - md5: e4a165cdbbaed5bbb6e653b823156151 + size: 13344463 + timestamp: 1703310653947 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py312h50c33e8_0.conda + sha256: 782b6b578a0e61f6ef5cca5be993d902db775a2eb3d0328a3c4ff515858e7f2c + md5: c5eff3ada1a829f0bdb780dc4b62bbae depends: - - freetype - - python >=3.7 - license: BSD-3-Clause - license_family: BSD + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libjpeg-turbo >=3.1.2,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - libxcb >=1.17.0,<2.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - lcms2 >=2.18,<3.0a0 + - python_abi 3.12.* *_cp312 + - zlib-ng >=2.3.3,<2.4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - openjpeg >=2.5.4,<3.0a0 + license: HPND purls: - - pkg:pypi/freetype-py?source=hash-mapping - size: 58932 - timestamp: 1650983451848 -- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda - sha256: 239b67edf1c5e5caed52cf36e9bed47cb21b37721779828c130e6b3fd9793c1b - md5: 496c6c9411a6284addf55c898d6ed8d7 + - pkg:pypi/pillow?source=hash-mapping + size: 1029755 + timestamp: 1770794002406 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + sha256: 43d37bc9ca3b257c5dd7bf76a8426addbdec381f6786ff441dc90b1a49143b6a + md5: c01af13bdc553d1a8fbfff6e8db075f0 depends: - - python >=3.10 + - libgcc >=14 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 450960 + timestamp: 1754665235234 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda + sha256: d834fd656133c9e4eaf63ffe9a117c7d0917d86d89f7d64073f4e3a0020bd8a7 + md5: dd94c506b119130aef5a9382aed648e7 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/fsspec?source=compressed-mapping - size: 148757 - timestamp: 1770387898414 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - sha256: 309cf4f04fec0c31b6771a5809a1909b4b3154a2208f52351e1ada006f4c750c - md5: c94a5994ef49749880a8139cf9afcbe1 - depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 - license: GPL-2.0-or-later OR LGPL-3.0-or-later - purls: [] - size: 460055 - timestamp: 1718980856608 -- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - sha256: 75aa5e7a875afdcf4903b7dc98577672a3dc17b528ac217b915f9528f93c85fc - md5: 427101d13f19c4974552a4e5b072eef1 - depends: - - __osx >=10.13 - - libcxx >=16 - license: GPL-2.0-or-later OR LGPL-3.0-or-later - purls: [] - size: 428919 - timestamp: 1718981041839 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda - sha256: 76e222e072d61c840f64a44e0580c2503562b009090f55aa45053bf1ccb385dd - md5: eed7278dfbab727b56f2c0b64330814b + - pkg:pypi/psutil?source=hash-mapping + size: 225545 + timestamp: 1769678155334 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 + md5: b3c17d95b5a10c6e64a21fa17573e70e depends: - - __osx >=11.0 - - libcxx >=16 - license: GPL-2.0-or-later OR LGPL-3.0-or-later + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT purls: [] - size: 365188 - timestamp: 1718981343258 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py312hcaba1f9_0.conda - sha256: 8e373b49ff22d94580e0c6763a4b22b305d19ef010c0c4b31e9c80621fdc0f07 - md5: 71e55276d288e6bb1e969f05fa4e1f5c + size: 8252 + timestamp: 1726802366959 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py312h2596900_1.conda + sha256: 15b5392d2755b771cb6830ae0377ed71bf2bcfd33a347dbc3195df80568ce42c + md5: 7766c2ad93e65415f4289de684261550 depends: - __glibc >=2.17,<3.0.a0 - - gmp >=6.3.0,<7.0a0 + - cairo >=1.18.4,<2.0a0 + - libexpat >=2.7.3,<3.0a0 - libgcc >=14 - - mpc >=1.3.1,<2.0a0 - - mpfr >=4.2.1,<5.0a0 + - libzlib >=1.3.1,<2.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - license: LGPL-3.0-or-later - license_family: LGPL + license: LGPL-2.1-only OR MPL-1.1 purls: - - pkg:pypi/gmpy2?source=compressed-mapping - size: 253013 - timestamp: 1773102850428 -- conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda - sha256: b6e68690f17c78f5ea070d89675c5e2b119159e5e7a4a0f98a432d1450844776 - md5: c1301858e6d7ea06bc310982c1f670b9 + - pkg:pypi/pycairo?source=hash-mapping + size: 119839 + timestamp: 1770726341594 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda + sha256: 4d14d7634c8f351ff1e63d733f6bb15cba9a0ec77e468b0de9102014a4ddc103 + md5: cfbd96e5a0182dfb4110fc42dda63e57 depends: - - __osx >=11.0 - - gmp >=6.3.0,<7.0a0 - - mpc >=1.3.1,<2.0a0 - - mpfr >=4.2.1,<5.0a0 - - python >=3.12,<3.13.0a0 + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 - python_abi 3.12.* *_cp312 - license: LGPL-3.0-or-later - license_family: LGPL + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT purls: - - pkg:pypi/gmpy2?source=hash-mapping - size: 200623 - timestamp: 1773103275478 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda - sha256: 88f51ca492ac2f44e460c2720b0896b114be0224a71679a3dadb8ada2ecf2bb4 - md5: 7c386b698b9cbcff65a00d4893cf48ae + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1890081 + timestamp: 1746625309715 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytables-3.11.1-py312hefc0c3f_0.conda + sha256: 2c7efb2498ebaa96273fa7c6b3b51bf9f4408e7838eaa3d114ace6e0a4e93481 + md5: f76dda533faf9f9c79d1c2340da6573d depends: - - __osx >=11.0 - - gmp >=6.3.0,<7.0a0 - - mpc >=1.3.1,<2.0a0 - - mpfr >=4.2.1,<5.0a0 + - __glibc >=2.17,<3.0.a0 + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - c-blosc2 >=2.23.0,<2.24.0a0 + - hdf5 >=1.14.6,<1.14.7.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - numexpr + - numpy >=1.20.0 + - numpy >=1.23,<3 + - packaging + - py-cpuinfo - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: LGPL-3.0-or-later - license_family: LGPL + - typing-extensions >=4.4.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/gmpy2?source=compressed-mapping - size: 193399 - timestamp: 1773103057948 -- conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.3.2-py312h8285ef7_0.conda - sha256: 03c8d065ef1e07053252412c541b5f1af70bc5fa2f974f129128d90fbdc47fe5 - md5: db6bba1610e5c4256d2892ec2997c425 + - pkg:pypi/tables?source=hash-mapping + size: 1631211 + timestamp: 1772371744337 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda + sha256: a44655c1c3e1d43ed8704890a91e12afd68130414ea2c0872e154e5633a13d7e + md5: 7eccb41177e15cc672e1babe9056018e depends: - - python - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - - libstdcxx >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/greenlet?source=compressed-mapping - size: 253793 - timestamp: 1771658391409 -- conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda - sha256: db9f45ec272e76f66ff845351656a60ce0e750cab33e4b67fcea0e86ae6736bd - md5: f7560a95e10c7547ddd4cccb00847fff + license: Python-2.0 + purls: [] + size: 31608571 + timestamp: 1772730708989 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.8.0-cpu_mkl_py312_hb1fc07b_102.conda + sha256: 80c932a9b95fedaa08a03063adeb8807ecde2c57eb4756d386652c7f3ec0c0f7 + md5: b3e8517778ac3397ef5e3b2ad9ed4773 depends: - - python - - libcxx >=19 - - __osx >=11.0 + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 + - filelock + - fsspec + - jinja2 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libblas * *mkl + - libcblas >=3.9.0,<4.0a0 + - libgcc >=14 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libstdcxx >=14 + - libtorch 2.8.0 cpu_mkl_h09b866c_102 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=21.1.4 + - mkl >=2025.3.0,<2026.0a0 + - networkx + - numpy >=1.23,<3 + - optree >=0.13.0 + - pybind11 + - pybind11-abi 4 + - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT + - setuptools + - sleef >=3.9.0,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.10.0 + constrains: + - pytorch-cpu 2.8.0 + - pytorch-gpu <0.0a0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/greenlet?source=hash-mapping - size: 249071 - timestamp: 1771658485183 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda - sha256: 4b11b05d11c024162eb3486f0cf5dc5878fc27915ec03108aaee8946f36deead - md5: 5ec9884fbb0d262e2ae35293fccbd263 + - pkg:pypi/torch?source=hash-mapping + size: 24721307 + timestamp: 1762097814781 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda + sha256: cb142bfd92f6e55749365ddc244294fa7b64db6d08c45b018ff1c658907bfcbf + md5: 15878599a87992e44c059731771591cb depends: - - python - - python 3.12.* *_cpython - - __osx >=11.0 - - libcxx >=19 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 + - yaml >=0.2.5,<0.3.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/greenlet?source=compressed-mapping - size: 250518 - timestamp: 1771658526528 -- pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl - name: griddataformats - version: 1.1.0 - sha256: d3dfc4d9f16d5e7bb4d387195434f24ef0339aa246ebfbf357fdfb4b26828385 - requires_dist: - - numpy>=1.21 - - scipy - - mrcfile - - pytest ; extra == 'test' - - numpy ; extra == 'test' - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb - md5: b8993c19b0c32a2f7b66cbb58ca27069 - depends: - - python >=3.10 - - typing_extensions - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/h11?source=compressed-mapping - size: 39069 - timestamp: 1767729720872 -- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 - md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 - depends: - - python >=3.10 - - hyperframe >=6.1,<7 - - hpack >=4.1,<5 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/h2?source=hash-mapping - size: 95967 - timestamp: 1756364871835 -- pypi: https://files.pythonhosted.org/packages/e4/8a/11ae7e271870f0ad8fa0012e4265982bebe0fdc21766b161fb8b8fc3aefc/hatch-1.16.5-py3-none-any.whl - name: hatch - version: 1.16.5 - sha256: d9b8047f2cd10d3349eb6e8f278ad728a04f91495aace305c257d5c2747188fb - requires_dist: - - backports-zstd>=1.0.0 ; python_full_version < '3.14' - - click>=8.0.6 - - hatchling>=1.27.0 - - httpx>=0.22.0 - - hyperlink>=21.0.0 - - keyring>=23.5.0 - - packaging>=24.2 - - pexpect~=4.8 - - platformdirs>=2.5.0 - - pyproject-hooks - - python-discovery>=1.1 - - rich>=11.2.0 - - shellingham>=1.4.0 - - tomli-w>=1.0 - - tomlkit>=0.11.1 - - userpath~=1.7 - - uv>=0.5.23 - - virtualenv>=21 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl - name: hatchling - version: 1.29.0 - sha256: 50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0 - requires_dist: - - packaging>=24.2 - - pathspec>=0.10.1 - - pluggy>=1.0.0 - - tomli>=1.2.2 ; python_full_version < '3.11' - - trove-classifiers - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda - sha256: 0d09b6dc1ce5c4005ae1c6a19dc10767932ef9a5e9c755cfdbb5189ac8fb0684 - md5: bd77f8da987968ec3927990495dc22e4 + - pkg:pypi/pyyaml?source=hash-mapping + size: 198293 + timestamp: 1770223620706 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda + noarch: python + sha256: be66c1f85c3b48137200d62c12d918f4f8ad329423daef04fed292818efd3c28 + md5: 082985717303dab433c976986c674b35 depends: - - libgcc-ng >=12 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libstdcxx-ng >=12 - - libzlib >=1.2.13,<2.0.0a0 + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - zeromq >=4.3.5,<4.4.0a0 + - _python_abi3_support 1.* + - cpython >=3.12 license: BSD-3-Clause license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 211567 + timestamp: 1771716961404 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + sha256: 776363493bad83308ba30bcb88c2552632581b143e8ee25b1982c8c743e73abc + md5: 353823361b1d27eb3960efb076dfcaf6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: LicenseRef-Qhull purls: [] - size: 756742 - timestamp: 1695661547874 -- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda - sha256: 8c767cc71226e9eb62649c903c68ba73c5f5e7e3696ec0319d1f90586cebec7d - md5: 7ce543bf38dbfae0de9af112ee178af2 + size: 552937 + timestamp: 1720813982144 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rdkit-2025.09.5-py312h3ecb6ed_0.conda + sha256: 2a7d3f80028ddd2611c7161745e7b25ed8ea68f98e3716701031ce0ad02e65a2 + md5: 0debb97ca2e922053426914d0fa07f78 depends: - - libcxx >=15.0.7 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libzlib >=1.2.13,<2.0.0a0 + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - libboost >=1.86.0,<1.87.0a0 + - libboost-python >=1.86.0,<1.87.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libpq >=18.1,<19.0a0 + - librdkit 2025.09.5 h3c5c181_0 + - libstdcxx >=14 + - matplotlib-base + - numpy >=1.23,<3 + - pandas + - pillow + - pycairo + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - reportlab + - sqlalchemy license: BSD-3-Clause license_family: BSD + purls: + - pkg:pypi/rdkit?source=hash-mapping + size: 20209344 + timestamp: 1770019899047 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL purls: [] - size: 724103 - timestamp: 1695661907511 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda - sha256: c3b01e3c3fe4ca1c4d28c287eaa5168a4f2fd3ffd76690082ac919244c22fa90 - md5: ff5d749fd711dc7759e127db38005924 + size: 345073 + timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rocm-core-7.0.2-h54a6638_0.conda + sha256: 2e762eb624cc396fd0ca902fd48749c61359f1eb91f1217ce11a78f66d5d16bb + md5: b56d3c2be254c7f1939167ac36a6c920 depends: - - libcxx >=15.0.7 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libzlib >=1.2.13,<2.0.0a0 - license: BSD-3-Clause - license_family: BSD + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + license: MIT + license_family: MIT purls: [] - size: 762257 - timestamp: 1695661864625 -- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda - sha256: 1fc50ce3b86710fba3ec9c5714f1612b5ffa4230d70bfe43e2a1436eacba1621 - md5: c223ee1429ba538f3e48cfb4a0b97357 + size: 20304 + timestamp: 1760541804711 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda + sha256: 62f46e85caaba30b459da7dfcf3e5488ca24fd11675c33ce4367163ab191a42c + md5: 3ffc5a3572db8751c2f15bacf6a0e937 depends: + - python - __glibc >=2.17,<3.0.a0 - - libaec >=1.1.5,<2.0a0 - - libcurl >=8.18.0,<9.0a0 + - libgcc >=14 + - python_abi 3.12.* *_cp312 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 383750 + timestamp: 1764543174231 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda + sha256: e3ad577361d67f6c078a6a7a3898bf0617b937d44dc4ccd57aa3336f2b5778dd + md5: 3e38daeb1fb05a95656ff5af089d2e4c + depends: + - __glibc >=2.17,<3.0.a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD - purls: [] - size: 3708864 - timestamp: 1770390337946 -- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda - sha256: 4bcc7d54a011f1d515da2fb3406659574bae5f284bced126c756ed9ef151459f - md5: b74e900265ad3808337cd542cfad6733 + purls: + - pkg:pypi/scipy?source=hash-mapping + size: 17109648 + timestamp: 1771880675810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + sha256: 57afc2ab5bdb24cf979964018dddbc5dfaee130b415e6863765e45aed2175ee4 + md5: e8a0b4f5e82ecacffaa5e805020473cb depends: - - __osx >=10.13 - - libaec >=1.1.5,<2.0a0 - - libcurl >=8.18.0,<9.0a0 - - libcxx >=19 - - libgfortran - - libgfortran5 >=14.3.0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 - license: BSD-3-Clause - license_family: BSD + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - libgcc >=14 + - libstdcxx >=14 + license: BSL-1.0 purls: [] - size: 3526365 - timestamp: 1770391694712 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_had3affe_106.conda - sha256: e91c2b8fe62d73bb56bdb9b5adcdcbedbd164ced288e0f361b8eb3f017ddcd7b - md5: 2d1270d283403c542680e969bea70355 + size: 1951720 + timestamp: 1756274576844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + sha256: 48f3f6a76c34b2cfe80de9ce7f2283ecb55d5ed47367ba91e8bb8104e12b8f11 + md5: 98b6c9dc80eb87b2519b97bcf7e578dd depends: - - __osx >=11.0 - - libaec >=1.1.5,<2.0a0 - - libcurl >=8.18.0,<9.0a0 - - libcxx >=19 - - libgfortran - - libgfortran5 >=14.3.0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 license: BSD-3-Clause license_family: BSD purls: [] - size: 3299759 - timestamp: 1770390513189 -- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba - md5: 0a802cb9888dd14eeefc611f05c40b6e + size: 45829 + timestamp: 1762948049098 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.48-py312h5253ce2_0.conda + sha256: ae7154760597c7112d6bce24fe0fc7850ee830503f7646fcfd4d35fece34d162 + md5: 8279b6aea65b8141f6ec46804321bf8a depends: - - python >=3.9 + - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/hpack?source=hash-mapping - size: 30731 - timestamp: 1737618390337 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b - md5: 4f14640d58e2cc0aa0819d9d8ba125bb + - pkg:pypi/sqlalchemy?source=hash-mapping + size: 3704046 + timestamp: 1772644902869 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-h8d10470_1.conda + sha256: 2e3238234ae094d5a5f7c559410ea8875351b6bac0d9d0e576bf64b732b8029e + md5: e3259be3341da4bc06c5b7a78c8bf1bd depends: - - python >=3.9 - - h11 >=0.16 - - h2 >=3,<5 - - sniffio 1.* - - anyio >=4.0,<5.0 - - certifi - - python - license: BSD-3-Clause + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libhwloc >=2.12.1,<2.12.2.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 181262 + timestamp: 1762509955687 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL license_family: BSD + purls: [] + size: 3301196 + timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py312h4c3975b_0.conda + sha256: bed440cad040f0fe76266f9a527feecbaf00385b68a96532aa69614fe5153f8e + md5: e03a4bf52d2170d64c816b2a52972097 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/httpcore?source=hash-mapping - size: 49483 - timestamp: 1745602916758 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 - md5: d6989ead454181f4f9bc987d3dc4e285 + - pkg:pypi/tornado?source=hash-mapping + size: 850918 + timestamp: 1765458857375 +- conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda + sha256: 895bbfe9ee25c98c922799de901387d842d7c01cae45c346879865c6a907f229 + md5: 0b6c506ec1f272b685240e70a29261b8 depends: - - anyio - - certifi - - httpcore 1.* - - idna - - python >=3.9 - license: BSD-3-Clause - license_family: BSD + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/httpx?source=hash-mapping - size: 63082 - timestamp: 1733663449209 -- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 - md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + - pkg:pypi/unicodedata2?source=hash-mapping + size: 410641 + timestamp: 1770909099497 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b + md5: fb901ff28063514abb6046c9ec2c4a45 depends: - - python >=3.9 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: MIT license_family: MIT - purls: - - pkg:pypi/hyperframe?source=hash-mapping - size: 17397 - timestamp: 1737618427549 -- pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl - name: hyperlink - version: 21.0.0 - sha256: e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4 - requires_dist: - - idna>=2.5 - - typing ; python_full_version < '3.5' - requires_python: '>=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e - md5: 8b189310083baabfb622af68fd9d3ae3 + purls: [] + size: 58628 + timestamp: 1734227592886 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 + md5: 1c74ff8c35dcadf952a16f752ca5aa49 depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - libgcc >=13 + - libuuid >=2.38.1,<3.0a0 + - xorg-libice >=1.1.2,<2.0a0 license: MIT license_family: MIT purls: [] - size: 12129203 - timestamp: 1720853576813 -- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - sha256: 2e64307532f482a0929412976c8450c719d558ba20c0962832132fd0d07ba7a7 - md5: d68d48a3060eb5abdc1cdc8e2a3a5966 + size: 27590 + timestamp: 1741896361728 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + sha256: 516d4060139dbb4de49a4dcdc6317a9353fb39ebd47789c14e6fe52de0deee42 + md5: 861fb6ccbc677bb9a9fb2468430b9c6a depends: - - __osx >=10.13 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxcb >=1.17.0,<2.0a0 license: MIT license_family: MIT purls: [] - size: 11761697 - timestamp: 1720853679409 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620 - md5: 5eb22c1d7b3fc4abb50d92d621583137 + size: 839652 + timestamp: 1770819209719 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b + md5: b2895afaf55bf96a8c8282a2e47a5de0 depends: - - __osx >=11.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 11857802 - timestamp: 1720853997952 -- pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - name: identify - version: 2.6.18 - sha256: 8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737 - requires_dist: - - ukkonen ; extra == 'license' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 - md5: 53abe63df7e10a6ba605dc5f9f961d36 - depends: - - python >=3.10 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/idna?source=hash-mapping - size: 50721 - timestamp: 1760286526795 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 - md5: 63ccfdc3a3ce25b027b8767eb722fca8 - depends: - - python >=3.9 - - zipp >=3.20 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/importlib-metadata?source=hash-mapping - size: 34641 - timestamp: 1747934053147 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - sha256: acc1d991837c0afb67c75b77fdc72b4bf022aac71fedd8b9ea45918ac9b08a80 - md5: c85c76dc67d75619a92f51dfbce06992 - depends: - - python >=3.9 - - zipp >=3.1.0 - constrains: - - importlib-resources >=6.5.2,<6.5.3.0a0 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/importlib-resources?source=hash-mapping - size: 33781 - timestamp: 1736252433366 -- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - name: iniconfig - version: 2.3.0 - sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - sha256: 5c1f3e874adaf603449f2b135d48f168c5d510088c78c229bda0431268b43b27 - md5: 4b53d436f3fbc02ce3eeaf8ae9bebe01 - depends: - - appnope - - __osx - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.8.0 - - jupyter_core >=5.1,!=6.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.10 - - pyzmq >=25 - - tornado >=6.4.1 - - traitlets >=5.4.0 - - python - constrains: - - appnope >=0.1.2 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 132260 - timestamp: 1770566135697 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - sha256: b77ed58eb235e5ad80e742b03caeed4bbc2a2ef064cb9a2deee3b75dfae91b2a - md5: 8b267f517b81c13594ed68d646fd5dcb - depends: - - __linux - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.8.0 - - jupyter_core >=5.1,!=6.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.10 - - pyzmq >=25 - - tornado >=6.4.1 - - traitlets >=5.4.0 - - python - constrains: - - appnope >=0.1.2 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=compressed-mapping - size: 133644 - timestamp: 1770566133040 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda - sha256: 1f90e346baab7926bc52d7b60c0625087e96b4fab1bdb9a7fe83ac842312c930 - md5: 326c46b8ec2a1b4964927c7ea55ebf49 - depends: - - __unix - - decorator >=5.1.0 - - ipython_pygments_lexers >=1.0.0 - - jedi >=0.18.2 - - matplotlib-inline >=0.1.6 - - prompt-toolkit >=3.0.41,<3.1.0 - - pygments >=2.14.0 - - python >=3.12 - - stack_data >=0.6.0 - - traitlets >=5.13.0 - - pexpect >4.6 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython?source=compressed-mapping - size: 648197 - timestamp: 1772790149194 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 - md5: bd80ba060603cc228d9d81c257093119 + size: 15321 + timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 + md5: 1dafce8548e38671bea82e3f5c6ce22f depends: - - pygments - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython-pygments-lexers?source=hash-mapping - size: 13993 - timestamp: 1737123723464 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda - sha256: 6bb58afb7eabc8b4ac0c7e92707fb498313cc0164cf04e7ba1090dbf49af514b - md5: d68e3f70d1f068f1b66d94822fdc644e + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 20591 + timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + sha256: 79c60fc6acfd3d713d6340d3b4e296836a0f8c51602327b32794625826bd052f + md5: 34e54f03dfea3e7a2dcf1453a85f1085 depends: - - comm >=0.1.3 - - ipython >=6.1.0 - - jupyterlab_widgets >=3.0.15,<3.1.0 - - python >=3.10 - - traitlets >=4.3.1 - - widgetsnbextension >=4.0.14,<4.1.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipywidgets?source=hash-mapping - size: 114376 - timestamp: 1762040524661 -- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed - md5: 0b0154421989637d424ccf0f104be51a + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 50326 + timestamp: 1769445253162 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + sha256: 044c7b3153c224c6cedd4484dd91b389d2d7fd9c776ad0f4a34f099b3389f4a1 + md5: 96d57aba173e878a2089d5638016dc5e depends: - - arrow >=0.15.0 - - python >=3.9 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/isoduration?source=hash-mapping - size: 19832 - timestamp: 1733493720346 -- pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - name: jaraco-classes - version: 3.4.0 - sha256: f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 - requires_dist: - - more-itertools - - sphinx>=3.5 ; extra == 'docs' - - jaraco-packaging>=9.3 ; extra == 'docs' - - rst-linker>=1.9 ; extra == 'docs' - - furo ; extra == 'docs' - - sphinx-lint ; extra == 'docs' - - jaraco-tidelift>=1.4 ; extra == 'docs' - - pytest>=6 ; extra == 'testing' - - pytest-checkdocs>=2.4 ; extra == 'testing' - - pytest-cov ; extra == 'testing' - - pytest-mypy ; extra == 'testing' - - pytest-enabler>=2.2 ; extra == 'testing' - - pytest-ruff>=0.2.1 ; extra == 'testing' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl - name: jaraco-context - version: 6.1.1 - sha256: 0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808 - requires_dist: - - backports-tarfile ; python_full_version < '3.12' - - pytest>=6,!=8.1.* ; extra == 'test' - - jaraco-test>=5.6.0 ; extra == 'test' - - portend ; extra == 'test' - - sphinx>=3.5 ; extra == 'doc' - - jaraco-packaging>=9.3 ; extra == 'doc' - - rst-linker>=1.9 ; extra == 'doc' - - furo ; extra == 'doc' - - sphinx-lint ; extra == 'doc' - - jaraco-tidelift>=1.4 ; extra == 'doc' - - pytest-checkdocs>=2.4 ; extra == 'check' - - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - - pytest-cov ; extra == 'cover' - - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - name: jaraco-functools - version: 4.4.0 - sha256: 9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176 - requires_dist: - - more-itertools - - pytest>=6,!=8.1.* ; extra == 'test' - - jaraco-classes ; extra == 'test' - - sphinx>=3.5 ; extra == 'doc' - - jaraco-packaging>=9.3 ; extra == 'doc' - - rst-linker>=1.9 ; extra == 'doc' - - furo ; extra == 'doc' - - sphinx-lint ; extra == 'doc' - - jaraco-tidelift>=1.4 ; extra == 'doc' - - pytest-checkdocs>=2.4 ; extra == 'check' - - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - - pytest-cov ; extra == 'cover' - - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 - md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 + purls: [] + size: 33005 + timestamp: 1734229037766 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda + sha256: a8afba4a55b7b530eb5c8ad89737d60d60bc151a03fbef7a2182461256953f0e + md5: 279b0de5f6ba95457190a1c459a64e31 depends: - - parso >=0.8.3,<0.9.0 - - python >=3.9 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/jedi?source=hash-mapping - size: 843646 - timestamp: 1733300981994 -- pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - name: jeepney - version: 0.9.0 - sha256: 97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 - requires_dist: - - pytest ; extra == 'test' - - pytest-trio ; extra == 'test' - - pytest-asyncio>=0.17 ; extra == 'test' - - testpath ; extra == 'test' - - trio ; extra == 'test' - - async-timeout ; python_full_version < '3.11' and extra == 'test' - - trio ; extra == 'trio' - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b - md5: 04558c96691bed63104678757beb4f8d + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libice >=1.1.1,<2.0a0 + - xorg-libsm >=1.2.4,<2.0a0 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 379686 + timestamp: 1731860547604 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad + md5: a77f85f77be52ff59391544bfe73390a depends: - - markupsafe >=2.0 - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jinja2?source=compressed-mapping - size: 120685 - timestamp: 1764517220861 -- conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - sha256: 301539229d7be6420c084490b8145583291123f0ce6b92f56be5948a2c83a379 - md5: 615de2a4d97af50c350e5cf160149e77 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 85189 + timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda + sha256: 47cfe31255b91b4a6fa0e9dbaf26baa60ac97e033402dbc8b90ba5fee5ffe184 + md5: 8035e5b54c08429354d5d64027041cad depends: - - python >=3.10 - - setuptools - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/joblib?source=hash-mapping - size: 226448 - timestamp: 1765794135253 -- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda - sha256: ba03ca5a6db38d9f48bd30172e8c512dea7a686a5c7701c6fcdb7b3023dae2ad - md5: 8d5f66ebf832c4ce28d5c37a0e76605c + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libsodium >=1.0.20,<1.0.21.0a0 + - krb5 >=1.21.3,<1.22.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 310648 + timestamp: 1757370847287 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_3.conda + sha256: 16080a1c7724f7d25727cdc23c7658e0cec2db52448c1dc0c33467ee2c6e1c62 + md5: 6acb86426229f96f93e5468d1df3a5e8 depends: - - python >=3.10 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/json5?source=hash-mapping - size: 34017 - timestamp: 1767325114901 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda - sha256: 1a1328476d14dfa8b84dbacb7f7cd7051c175498406dc513ca6c679dc44f3981 - md5: cd2214824e36b0180141d422aba01938 + - __glibc >=2.17,<3.0.a0 + - libzlib 1.3.2 h25fd6f3_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 96132 + timestamp: 1785362957588 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda + sha256: ea4e50c465d70236408cb0bfe0115609fd14db1adcd8bd30d8918e0291f8a75f + md5: 2aadb0d17215603a82a2a6b0afd9a4cb depends: - - python >=3.10 - - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Zlib + license_family: Other + purls: [] + size: 122618 + timestamp: 1770167931827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 13967 - timestamp: 1765026384757 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda - sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 - md5: ada41c863af263cc4c5fcbaff7c3e4dc + purls: [] + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 + md5: aaa2a381ccc56eac91d63b6c1240312f depends: - - attrs >=22.2.0 - - jsonschema-specifications >=2023.3.6 - - python >=3.10 - - referencing >=0.28.4 - - rpds-py >=0.25.0 - - python + - cpython + - python-gil + license: MIT + license_family: MIT + purls: [] + size: 8191 + timestamp: 1744137672556 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 + md5: 2934f256a8acfe48f6ebb4fce6cde29c + depends: + - python >=3.9 + - typing-extensions >=4.0.0 license: MIT license_family: MIT purls: - - pkg:pypi/jsonschema?source=compressed-mapping - size: 82356 - timestamp: 1767839954256 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 - md5: 439cd0f567d697b20a8f45cb70a1005a + - pkg:pypi/annotated-types?source=hash-mapping + size: 18074 + timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda + sha256: eb0c4e2b24f1fbefaf96ce6c992c6bd64340bc3c06add4d7415ab69222b201da + md5: 11a2b8c732d215d977998ccd69a9d5e8 depends: + - exceptiongroup >=1.0.2 + - idna >=2.8 - python >=3.10 - - referencing >=0.31.0 + - typing_extensions >=4.5 - python + constrains: + - trio >=0.32.0 + - uvloop >=0.21 license: MIT license_family: MIT purls: - - pkg:pypi/jsonschema-specifications?source=hash-mapping - size: 19236 - timestamp: 1757335715225 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda - sha256: 6886fc61e4e4edd38fd38729976b134e8bd2143f7fce56cc80d7ac7bac99bce1 - md5: 8368d58342d0825f0843dc6acdd0c483 + - pkg:pypi/anyio?source=hash-mapping + size: 145175 + timestamp: 1767719033569 +- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf + md5: 54898d0f524c9dee622d44bbb081a8ab + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/appnope?source=hash-mapping + size: 10076 + timestamp: 1733332433806 +- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad + md5: 8ac12aff0860280ee0cff7fa2cf63f3b depends: - - jsonschema >=4.26.0,<4.26.1.0a0 - - fqdn - - idna - - isoduration - - jsonpointer >1.13 - - rfc3339-validator - - rfc3986-validator >0.1.0 - - rfc3987-syntax >=1.1.0 - - uri-template - - webcolors >=24.6.0 + - argon2-cffi-bindings + - python >=3.9 + - typing-extensions + constrains: + - argon2_cffi ==999 license: MIT license_family: MIT - purls: [] - size: 4740 - timestamp: 1767839954258 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - sha256: 897ad2e2c2335ef3c2826d7805e16002a1fd0d509b4ae0bc66617f0e0ff07bc2 - md5: 62b7c96c6cd77f8173cc5cada6a9acaa + purls: + - pkg:pypi/argon2-cffi?source=hash-mapping + size: 18715 + timestamp: 1749017288144 +- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 + md5: 85c4f19f377424eafc4ed7911b291642 depends: - - importlib-metadata >=4.8.3 - - jupyter_server >=1.1.2 - python >=3.10 + - python-dateutil >=2.7.0 + - python-tzdata - python - license: BSD-3-Clause - license_family: BSD + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/jupyter-lsp?source=hash-mapping - size: 60377 - timestamp: 1756388269267 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - sha256: e402bd119720862a33229624ec23645916a7d47f30e1711a4af9e005162b84f3 - md5: 8a3d6d0523f66cf004e563a50d9392b3 + - pkg:pypi/arrow?source=hash-mapping + size: 113854 + timestamp: 1760831179410 +- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 + md5: 9673a61a297b00016442e022d689faa6 depends: - - jupyter_core >=5.1 - python >=3.10 - - python-dateutil >=2.8.2 - - pyzmq >=25.0 - - tornado >=6.4.1 - - traitlets >=5.3 - - python - license: BSD-3-Clause - license_family: BSD + constrains: + - astroid >=2,<5 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/jupyter-client?source=compressed-mapping - size: 112785 - timestamp: 1767954655912 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a - md5: b38fe4e78ee75def7e599843ef4c1ab0 + - pkg:pypi/asttokens?source=hash-mapping + size: 28797 + timestamp: 1763410017955 +- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda + sha256: d078b0d3fdc13b0ff08485af20928a095c80dff03f7021ee18e8426a773ae948 + md5: 2cdaf7f8bda7eb9ce49c3e08f2f65803 depends: - - __unix - - python - - platformdirs >=2.5 - python >=3.10 - - traitlets >=5.3 + - typing_extensions >=4.0.0 - python - constrains: - - pywin32 >=300 - license: BSD-3-Clause - license_family: BSD + license: MIT + license_family: MIT purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 65503 - timestamp: 1760643864586 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda - sha256: e9964aaaf6d24a685cd5ce9d75731b643ed7f010fb979574a6580cd2f974c6cd - md5: 31e11c30bbee1682a55627f953c6725a + - pkg:pypi/async-lru?source=hash-mapping + size: 21470 + timestamp: 1771623881915 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda + sha256: c13d5e42d187b1d0255f591b7ce91201d4ed8a5370f0d986707a802c20c9d32f + md5: 537296d57ea995666c68c821b00e360b depends: - - jsonschema-with-format-nongpl >=4.18.0 - - packaging - - python >=3.9 - - python-json-logger >=2.0.4 - - pyyaml >=5.3 - - referencing - - rfc3339-validator - - rfc3986-validator >=0.1.1 - - traitlets >=5.3 + - python >=3.10 - python - license: BSD-3-Clause - license_family: BSD + license: MIT + license_family: MIT purls: - - pkg:pypi/jupyter-events?source=hash-mapping - size: 24306 - timestamp: 1770937604863 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - sha256: 74c4e642be97c538dae1895f7052599dfd740d8bd251f727bce6453ce8d6cd9a - md5: d79a87dcfa726bcea8e61275feed6f83 + - pkg:pypi/attrs?source=hash-mapping + size: 64759 + timestamp: 1764875182184 +- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 + md5: f1976ce927373500cc19d3c0b2c85177 depends: - - anyio >=3.1.0 - - argon2-cffi >=21.1 - - jinja2 >=3.0.3 - - jupyter_client >=7.4.4 - - jupyter_core >=4.12,!=5.0.* - - jupyter_events >=0.11.0 - - jupyter_server_terminals >=0.4.4 - - nbconvert-core >=6.4.4 - - nbformat >=5.3.0 - - overrides >=5.0 - - packaging >=22.0 - - prometheus_client >=0.9 - python >=3.10 - - pyzmq >=24 - - send2trash >=1.8.2 - - terminado >=0.8.3 - - tornado >=6.2.0 - - traitlets >=5.6.0 - - websocket-client >=1.7 - python + constrains: + - pytz >=2015.7 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-server?source=hash-mapping - size: 347094 - timestamp: 1755870522134 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 - md5: 7b8bace4943e0dc345fc45938826f2b8 + - pkg:pypi/babel?source=hash-mapping + size: 7684321 + timestamp: 1772555330347 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 + md5: 5267bef8efea4127aacd1f4e1f149b6e depends: - python >=3.10 - - terminado >=0.8.3 - - python - license: BSD-3-Clause - license_family: BSD + - soupsieve >=1.2 + - typing-extensions + license: MIT + license_family: MIT purls: - - pkg:pypi/jupyter-server-terminals?source=hash-mapping - size: 22052 - timestamp: 1768574057200 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda - sha256: 53705b629a27cc91600f99b70c39181bbc428cdbc70f22aa40cea21e9fa6d560 - md5: c4b96d937d1b03203c00fed92e713cd1 + - pkg:pypi/beautifulsoup4?source=hash-mapping + size: 90399 + timestamp: 1764520638652 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + sha256: f8ff1f98423674278964a46c93a1766f9e91960d44efd91c6c3ed56a33813f46 + md5: 7c5ebdc286220e8021bf55e6384acd67 depends: - - async-lru >=1.0.0 - - httpx >=0.25.0,<1 - - ipykernel >=6.5.0,!=6.30.0 - - jinja2 >=3.0.3 - - jupyter-lsp >=2.0.0 - - jupyter_core - - jupyter_server >=2.4.0,<3 - - jupyterlab_server >=2.28.0,<3 - - notebook-shim >=0.2 - - packaging - python >=3.10 - - setuptools >=41.1.0 - - tomli >=1.2.2 - - tornado >=6.2.0 - - traitlets - license: BSD-3-Clause - license_family: BSD + - webencodings + - python + constrains: + - tinycss2 >=1.1.0,<1.5 + license: Apache-2.0 AND MIT purls: - - pkg:pypi/jupyterlab?source=compressed-mapping - size: 7994221 - timestamp: 1771885064306 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 - md5: fd312693df06da3578383232528c468d + - pkg:pypi/bleach?source=hash-mapping + size: 142008 + timestamp: 1770719370680 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + sha256: 7c07a865e5e4cca233cc4e0eb3f0f5ff6c90776461687b4fb0b1764133e1fd61 + md5: f11a319b9700b203aa14c295858782b6 + depends: + - bleach ==6.3.0 pyhcf101f3_1 + - tinycss2 + license: Apache-2.0 AND MIT + purls: [] + size: 4409 + timestamp: 1770719370682 +- conda: https://conda.anaconda.org/conda-forge/noarch/bson-0.5.10-pyhd8ed1ab_0.conda + sha256: b6e73522a3fc2d85e8eef363ff9dfe1d7995770b55968dc7e9e23ebd02f56e84 + md5: 8e1cd1dca850168941e200bc5e626e16 depends: - - pygments >=2.4.1,<3 - python >=3.9 - constrains: - - jupyterlab >=4.0.8,<5.0.0 + - python-dateutil >=2.4.0 + - six license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab-pygments?source=hash-mapping - size: 18711 - timestamp: 1733328194037 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee - md5: a63877cb23de826b1620d3adfccc4014 + - pkg:pypi/bson?source=hash-mapping + size: 16836 + timestamp: 1736456262819 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc + md5: 4492fd26db29495f0ba23f146cd5638d depends: - - babel >=2.10 - - jinja2 >=3.0.3 - - json5 >=0.9.0 - - jsonschema >=4.18 - - jupyter_server >=1.21,<3 - - packaging >=21.3 - - python >=3.10 - - requests >=2.31 - - python + - __unix + license: ISC + purls: [] + size: 147413 + timestamp: 1772006283803 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + noarch: python + sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 + md5: 9b347a7ec10940d3f7941ff6c460b551 + depends: + - cached_property >=1.5.2,<1.5.3.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/jupyterlab-server?source=hash-mapping - size: 51621 - timestamp: 1761145478692 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda - sha256: 5c03de243d7ae6247f39a402f4785d95e61c3be79ef18738e8f17155585d31a8 - md5: dbf8b81974504fa51d34e436ca7ef389 + purls: [] + size: 4134 + timestamp: 1615209571450 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 + md5: 576d629e47797577ab0f1b351297ef4a depends: - - python >=3.10 - - python - constrains: - - jupyterlab >=3,<5 + - python >=3.6 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab-widgets?source=hash-mapping - size: 216779 - timestamp: 1762267481404 -- pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - name: keyring - version: 25.7.0 - sha256: be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f - requires_dist: - - pywin32-ctypes>=0.2.0 ; sys_platform == 'win32' - - secretstorage>=3.2 ; sys_platform == 'linux' - - jeepney>=0.4.2 ; sys_platform == 'linux' - - importlib-metadata>=4.11.4 ; python_full_version < '3.12' - - jaraco-classes - - jaraco-functools - - jaraco-context - - pytest>=6,!=8.1.* ; extra == 'test' - - pyfakefs ; extra == 'test' - - sphinx>=3.5 ; extra == 'doc' - - jaraco-packaging>=9.3 ; extra == 'doc' - - rst-linker>=1.9 ; extra == 'doc' - - furo ; extra == 'doc' - - sphinx-lint ; extra == 'doc' - - jaraco-tidelift>=1.4 ; extra == 'doc' - - pytest-checkdocs>=2.4 ; extra == 'check' - - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - - pytest-cov ; extra == 'cover' - - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - pygobject-stubs ; extra == 'type' - - shtab ; extra == 'type' - - types-pywin32 ; extra == 'type' - - shtab>=1.1.0 ; extra == 'completion' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 - md5: b38117a3c920364aff79f870c984b4a3 + - pkg:pypi/cached-property?source=hash-mapping + size: 11065 + timestamp: 1615209567874 +- conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.0.5-pyhd8ed1ab_0.conda + sha256: edfecb626da69607f926f51ad0d24942bfe9f7a29391d55d4ac62403e878605b + md5: a66a1542c3ed584ca4fdb23955d81e91 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-or-later - purls: [] - size: 134088 - timestamp: 1754905959823 -- conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda - sha256: 1cd9ca9d99acbe449df1590325cbae1e97d0a8a6979ea91ee61fca863b3a5764 - md5: 0c458c33cd3b609c6a64180af46201c9 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cachetools?source=hash-mapping + size: 19034 + timestamp: 1773120473852 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda + sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656 + md5: 765c4d97e877cdbbb88ff33152b86125 depends: - - __osx >=10.13 - - opencl-headers >=2024.10.24 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 34262 - timestamp: 1732916591723 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda - sha256: 74abb94591a42b0986b864cdd1c3b5b2e6e6b6851d5b8d0ecbe038fc14f036be - md5: fb5a6baf5df9abc027955a408c61ac74 + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=hash-mapping + size: 151445 + timestamp: 1772001170301 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.5-pyhd8ed1ab_0.conda + sha256: 05ea76a016c77839b64f9f8ec581775f6c8a259044bd5b45a177e46ab4e7feac + md5: beb628209b2b354b98203066f90b3287 depends: - - __osx >=11.0 - - opencl-headers >=2024.10.24 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 35128 - timestamp: 1732916821283 -- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda - sha256: eec7654c2d68f06590862c6e845cc70987b6d6559222b6f0e619dea4268f5dd5 - md5: cd74a9525dc74bbbf93cf8aa2fa9eb5b + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=hash-mapping + size: 53210 + timestamp: 1772816516728 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda + sha256: 38cfe1ee75b21a8361c8824f5544c3866f303af1762693a178266d7f198e8715 + md5: ea8a6c3256897cc31263de9f455e25d9 depends: + - python >=3.10 + - __unix - python - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/kiwisolver?source=compressed-mapping - size: 77120 - timestamp: 1773067050308 -- conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda - sha256: 6ab69d441b3400cdf773f67e20f9fae7c37f076d32c31d06843f12d2099e70ce - md5: 4a38b6e74b5a7ea22f1840226e5103a8 + - pkg:pypi/click?source=hash-mapping + size: 97676 + timestamp: 1764518652276 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-option-group-0.5.6-pyhd8ed1ab_0.conda + sha256: cc17620f8c7f90e45b0e398ff01b41bc2ecf48a600c8e03ca229c251eb9949a3 + md5: 24448fbe066e17f2c3b0bfbe2d251330 depends: - - python - - libcxx >=19 - - __osx >=11.0 - - python_abi 3.12.* *_cp312 + - click >=7.0,<9 + - python >=3.6,<4.0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/kiwisolver?source=hash-mapping - size: 69432 - timestamp: 1773067281295 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda - sha256: 8de440f0e33ab6895e81f2c47c51e59d177349a832087a0367e8e259c97f4833 - md5: 58261af35f0d33fd28e2257b208a1be0 + - pkg:pypi/click-option-group?source=hash-mapping + size: 16770 + timestamp: 1686394351507 +- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 + md5: 2da13f2b299d8e1995bafbbe9689a2f7 depends: + - python >=3.9 - python - - __osx >=11.0 - - libcxx >=19 - - python 3.12.* *_cpython - - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/kiwisolver?source=compressed-mapping - size: 68490 - timestamp: 1773067215781 -- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 - md5: 3f43953b7d3fb3aaa1d0d0723d91e368 - depends: - - keyutils >=1.6.1,<2.0a0 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1370023 - timestamp: 1719463201255 -- conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - sha256: 83b52685a4ce542772f0892a0f05764ac69d57187975579a0835ff255ae3ef9c - md5: d4765c524b1d91567886bde656fb514b + - pkg:pypi/comm?source=hash-mapping + size: 14690 + timestamp: 1753453984907 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda + noarch: generic + sha256: d3e9bbd7340199527f28bbacf947702368f31de60c433a16446767d3c6aaf6fe + md5: f54c1ffb8ecedb85a8b7fcde3a187212 depends: - - __osx >=10.13 - - libcxx >=16 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT + - python >=3.12,<3.13.0a0 + - python_abi * *_cp312 + license: Python-2.0 purls: [] - size: 1185323 - timestamp: 1719463492984 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - sha256: 4442f957c3c77d69d9da3521268cad5d54c9033f1a73f99cde0a3658937b159b - md5: c6dc8a0fdec13a0565936655c33069a1 - depends: - - __osx >=11.0 - - libcxx >=16 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT + size: 46463 + timestamp: 1772728929620 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda + sha256: 64aebe8ccb3a2c3ff446d3c0c0e88ef4fdb069a5732c03539bf3a37243c4c679 + md5: 45676e3dd76b30ec613f1f822d450eff + constrains: + - __cuda >=13 + - cudatoolkit 13.2|13.2.* + license: LicenseRef-NVIDIA-End-User-License-Agreement purls: [] - size: 1155530 - timestamp: 1719463474401 -- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 - md5: 9b965c999135d43a3d0f7bd7d024e26a + size: 21908 + timestamp: 1773093709154 +- conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + sha256: bb47aec5338695ff8efbddbc669064a3b10fe34ad881fb8ad5d64fbfa6910ed1 + md5: 4c2a8fef270f6c69591889b93f9f55c1 depends: - python >=3.10 - license: MIT - license_family: MIT + - python + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/lark?source=compressed-mapping - size: 94312 - timestamp: 1761596921009 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda - sha256: 836ec4b895352110335b9fdcfa83a8dcdbe6c5fb7c06c4929130600caea91c0a - md5: 6f2e2c8f58160147c4d1c6f4c14cbac4 + - pkg:pypi/cycler?source=hash-mapping + size: 14778 + timestamp: 1764466758386 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 + md5: 9ce473d1d1be1cc3810856a48b3fab32 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libjpeg-turbo >=3.1.2,<4.0a0 - - libtiff >=4.7.1,<4.8.0a0 - license: MIT - license_family: MIT - purls: [] - size: 249959 - timestamp: 1768184673131 -- conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda - sha256: 3ec16c491425999a8461e1b7c98558060a4645a20cf4c9ac966103c724008cc2 - md5: 753acc10c7277f953f168890e5397c80 + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/decorator?source=hash-mapping + size: 14129 + timestamp: 1740385067843 +- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be + md5: 961b3a227b437d82ad7054484cfa71b2 + depends: + - python >=3.6 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/defusedxml?source=hash-mapping + size: 24062 + timestamp: 1615232388757 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab depends: - - __osx >=10.13 - - libjpeg-turbo >=3.1.2,<4.0a0 - - libtiff >=4.7.1,<4.8.0a0 - license: MIT - license_family: MIT - purls: [] - size: 226870 - timestamp: 1768184917403 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda - sha256: d768da024ab74a4b30642401877fa914a68bdc238667f16b1ec2e0e98b2451a6 - md5: 6631a7bd2335bb9699b1dbc234b19784 + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad + md5: ff9efb7f7469aed3c4a8106ffa29593c depends: - - __osx >=11.0 - - libjpeg-turbo >=3.1.2,<4.0a0 - - libtiff >=4.7.1,<4.8.0a0 + - python >=3.10 license: MIT license_family: MIT - purls: [] - size: 211756 - timestamp: 1768184994800 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 - md5: 12bd9a3f089ee6c9266a37dab82afabd - depends: - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - binutils_impl_linux-64 2.45.1 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 725507 - timestamp: 1770267139900 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 - md5: a752488c68f2e7c456bcbd8f16eec275 + purls: + - pkg:pypi/executing?source=hash-mapping + size: 30753 + timestamp: 1756729456476 +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.25.1-pyhd8ed1ab_0.conda + sha256: 3e9c0c2e956be0b6495e5102378cebbd666b4a58fe1c114bee41fd9078f9bf82 + md5: e1301f0b6a104ca461697f394be2bd25 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 261513 - timestamp: 1773113328888 -- conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda - sha256: f918716c71c8bebbc0c40e1050878aa512fea92c1d17c363ca35650bc60f6c35 - md5: d2fe7e177d1c97c985140bd54e2a5e33 + - python >=3.10 + license: Unlicense + purls: + - pkg:pypi/filelock?source=hash-mapping + size: 25792 + timestamp: 1773119024631 +- conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + sha256: acdb7b73d84268773fcc8192965994554411edc488ec3447925a62154e9d3baa + md5: f1e618f2f783427019071b14a111b30d depends: - - __osx >=11.0 - - libcxx >=19 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 215089 - timestamp: 1773114468701 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda - sha256: 66e5ffd301a44da696f3efc2f25d6d94f42a9adc0db06c44ad753ab844148c51 - md5: 095e5749868adab9cae42d4b460e5443 + - python >=3.9 + - typing-extensions + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/flexcache?source=hash-mapping + size: 16674 + timestamp: 1733663669958 +- conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + sha256: 9bdad0cd9fb6d67e48798c03930d634ea2d33a894d30439d3d7bdffd3c21af7b + md5: 6dc4e43174cd552452fdb8c423e90e69 depends: - - __osx >=11.0 - - libcxx >=19 - license: Apache-2.0 - license_family: Apache + - python >=3.9 + - typing-extensions + - typing_extensions + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/flexparser?source=hash-mapping + size: 28686 + timestamp: 1733663636245 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b + md5: 0c96522c6bdaed4b1566d11387caaf45 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 164222 - timestamp: 1773114244984 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda - sha256: dcd1429a1782864c452057a6c5bc1860f2b637dc20a2b7e6eacd57395bbceff8 - md5: 83b160d4da3e1e847bf044997621ed63 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - constrains: - - libabseil-static =20250512.1=cxx17* - - abseil-cpp =20250512.1 - license: Apache-2.0 - license_family: Apache + size: 397370 + timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c + md5: 34893075a5c9e55cdafac56607368fc6 + license: OFL-1.1 + license_family: Other purls: [] - size: 1310612 - timestamp: 1750194198254 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda - sha256: a878efebf62f039a1f1733c1e150a75a99c7029ece24e34efdf23d56256585b1 - md5: ddf1acaed2276c7eb9d3c76b49699a11 - depends: - - __osx >=10.13 - - libcxx >=18 - constrains: - - abseil-cpp =20250512.1 - - libabseil-static =20250512.1=cxx17* - license: Apache-2.0 - license_family: Apache + size: 96530 + timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 + md5: 4d59c254e01d9cde7957100457e2d5fb + license: OFL-1.1 + license_family: Other purls: [] - size: 1162435 - timestamp: 1750194293086 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda - sha256: 7f0ee9ae7fa2cf7ac92b0acf8047c8bac965389e48be61bf1d463e057af2ea6a - md5: 360dbb413ee2c170a0a684a33c4fc6b8 - depends: - - __osx >=11.0 - - libcxx >=18 - constrains: - - libabseil-static =20250512.1=cxx17* - - abseil-cpp =20250512.1 - license: Apache-2.0 - license_family: Apache + size: 700814 + timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 + md5: 49023d73832ef61042f6a237cb2687e7 + license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 + license_family: Other purls: [] - size: 1174081 - timestamp: 1750194620012 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - sha256: 822e4ae421a7e9c04e841323526321185f6659222325e1a9aedec811c686e688 - md5: 86f7414544ae606282352fa1e116b41f + size: 1620504 + timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 + md5: fee5683a3f04bd15cbd8318b096a27ab depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - license: BSD-2-Clause + - fonts-conda-forge + license: BSD-3-Clause license_family: BSD purls: [] - size: 36544 - timestamp: 1769221884824 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda - sha256: b42ac9c684c730cb97cb3931a0a97aaf791da38bace4f6944eca10de609e5946 - md5: 975f98248cde8d54884c6d1eb5184e13 + size: 3667 + timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 + md5: a7970cd949a077b7cb9696379d338681 depends: - - __osx >=10.13 - - libcxx >=19 - license: BSD-2-Clause + - font-ttf-ubuntu + - font-ttf-inconsolata + - font-ttf-dejavu-sans-mono + - font-ttf-source-code-pro + license: BSD-3-Clause license_family: BSD purls: [] - size: 30555 - timestamp: 1769222189944 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - sha256: af9cd8db11eb719e38a3340c88bb4882cf19b5b4237d93845224489fc2a13b46 - md5: 13e6d9ae0efbc9d2e9a01a91f4372b41 + size: 4059 + timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 + md5: d3549fd50d450b6d9e7dddff25dd2110 depends: - - __osx >=11.0 - - libcxx >=19 - license: BSD-2-Clause - license_family: BSD + - cached-property >=1.3.0 + - python >=3.9,<4 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/fqdn?source=hash-mapping + size: 16705 + timestamp: 1733327494780 +- conda: https://conda.anaconda.org/conda-forge/noarch/freesasa-2.2.1-hd8ed1ab_5.conda + sha256: ac83d523dde3ae3aaccd36488dd092ac331b91138b0a9977def9100ae3cdd293 + md5: 2d93ef3ecf39a1bdabd813c96d205a3b + depends: + - freesasa-python >=2.2.1,<2.2.2.0a0 + license: MIT + license_family: MIT purls: [] - size: 30390 - timestamp: 1769222133373 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda - build_number: 5 - sha256: 328d64d4eb51047c39a8039a30eb47695855829d0a11b72d932171cb1dcdfad3 - md5: 9d2f2e3a943d38f972ceef9cde8ba4bf + run_exports: {} + size: 7137 + timestamp: 1756396943890 +- conda: https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2 + sha256: bfa5ddb943992643a2ee2e258507beac2bdb17825e74d05f1bb0b79bb2c924fc + md5: e4a165cdbbaed5bbb6e653b823156151 depends: - - mkl >=2025.3.0,<2026.0a0 - constrains: - - liblapack 3.11.0 5*_mkl - - liblapacke 3.11.0 5*_mkl - - libcblas 3.11.0 5*_mkl - - blas 2.305 mkl - track_features: - - blas_mkl - - blas_mkl_2 + - freetype + - python >=3.7 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/freetype-py?source=hash-mapping + size: 58932 + timestamp: 1650983451848 +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.2.0-pyhd8ed1ab_0.conda + sha256: 239b67edf1c5e5caed52cf36e9bed47cb21b37721779828c130e6b3fd9793c1b + md5: 496c6c9411a6284addf55c898d6ed8d7 + depends: + - python >=3.10 license: BSD-3-Clause license_family: BSD - purls: [] - size: 18744 - timestamp: 1765818556597 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - build_number: 5 - sha256: 4754de83feafa6c0b41385f8dab1b13f13476232e16f524564a340871a9fc3bc - md5: 36d2e68a156692cbae776b75d6ca6eae + purls: + - pkg:pypi/fsspec?source=hash-mapping + size: 148757 + timestamp: 1770387898414 +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 - constrains: - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas - - libcblas 3.11.0 5*_openblas - - mkl <2026 - - liblapacke 3.11.0 5*_openblas + - python >=3.10 + - typing_extensions + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h11?source=hash-mapping + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.1,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=hash-mapping + size: 95967 + timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 30731 + timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b + md5: 4f14640d58e2cc0aa0819d9d8ba125bb + depends: + - python >=3.9 + - h11 >=0.16 + - h2 >=3,<5 + - sniffio 1.* + - anyio >=4.0,<5.0 + - certifi + - python license: BSD-3-Clause license_family: BSD - purls: [] - size: 18476 - timestamp: 1765819054657 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - build_number: 5 - sha256: 620a6278f194dcabc7962277da6835b1e968e46ad0c8e757736255f5ddbfca8d - md5: bcc025e2bbaf8a92982d20863fe1fb69 + purls: + - pkg:pypi/httpcore?source=hash-mapping + size: 49483 + timestamp: 1745602916758 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 + md5: d6989ead454181f4f9bc987d3dc4e285 depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 - constrains: - - libcblas 3.11.0 5*_openblas - - liblapack 3.11.0 5*_openblas - - liblapacke 3.11.0 5*_openblas - - blas 2.305 openblas - - mkl <2026 + - anyio + - certifi + - httpcore 1.* + - idna + - python >=3.9 license: BSD-3-Clause license_family: BSD - purls: [] - size: 18546 - timestamp: 1765819094137 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hed09d94_4.conda - sha256: 2e9778d8c3bbc6e7698fd87a1499a68ca1f02be37f6aaefa7541eb2728ffbff3 - md5: b708abf3b6a0f3cf2f833d2edf18aff0 + purls: + - pkg:pypi/httpx?source=hash-mapping + size: 63082 + timestamp: 1733663449209 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - icu >=75.1,<76.0a0 - - libgcc >=14 - - liblzma >=5.8.1,<6.0a0 - - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - boost-cpp <0.0a0 - license: BSL-1.0 - purls: [] - size: 2959099 - timestamp: 1756549412040 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda - sha256: 6d80e4e92fcf8a6bbd8424b43e6ec5b66d8b334e2bdbf2a8ef3f89bfc24c1bb3 - md5: 84262b75468880fe2fa6858ea0ce8e0e + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 + md5: 53abe63df7e10a6ba605dc5f9f961d36 depends: - - __osx >=10.13 - - bzip2 >=1.0.8,<2.0a0 - - icu >=75.1,<76.0a0 - - libcxx >=19 - - liblzma >=5.8.1,<6.0a0 - - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - boost-cpp <0.0a0 - license: BSL-1.0 - purls: [] - size: 2122047 - timestamp: 1756549807422 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-hf493ff8_4.conda - sha256: 045e14f278f081c642325e382a81bb7593cd19a91a0208a6ee86217954493b8d - md5: abb88838f1ba4f91f588f8eb47bc0549 + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=hash-mapping + size: 50721 + timestamp: 1760286526795 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 + md5: 63ccfdc3a3ce25b027b8767eb722fca8 depends: - - __osx >=11.0 - - bzip2 >=1.0.8,<2.0a0 - - icu >=75.1,<76.0a0 - - libcxx >=19 - - liblzma >=5.8.1,<6.0a0 - - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - boost-cpp <0.0a0 - license: BSL-1.0 - purls: [] - size: 1961984 - timestamp: 1756549827458 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py312hf890105_5.conda - sha256: 7e4fd809fb926627c8c626d1265785c1d0420ea34aa910a0c901c48737474259 - md5: 1630127107ab1cfb9ca9468f9318fa29 + - python >=3.9 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=hash-mapping + size: 34641 + timestamp: 1747934053147 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + sha256: acc1d991837c0afb67c75b77fdc72b4bf022aac71fedd8b9ea45918ac9b08a80 + md5: c85c76dc67d75619a92f51dfbce06992 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.9 + - zipp >=3.1.0 constrains: - - boost <0.0a0 - - py-boost <0.0a0 - license: BSL-1.0 - purls: [] - size: 124523 - timestamp: 1766348623465 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda - sha256: 861bb5393b15e98bfac3b1f02237e7a8c3da24e3e584cff5ba08c12cf76cbc9c - md5: 84650ec666b21b2a0bf4218f0b94d1e8 + - importlib-resources >=6.5.2,<6.5.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-resources?source=hash-mapping + size: 33781 + timestamp: 1736252433366 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + sha256: 5c1f3e874adaf603449f2b135d48f168c5d510088c78c229bda0431268b43b27 + md5: 4b53d436f3fbc02ce3eeaf8ae9bebe01 depends: - - __osx >=10.13 - - libcxx >=19 - - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - appnope + - __osx + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python constrains: - - boost <0.0a0 - - py-boost <0.0a0 - license: BSL-1.0 - purls: [] - size: 105794 - timestamp: 1766350642066 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda - sha256: ed62e2a4fa93de99dda3a79db77c7f595bd1377abe03b225ffd4aeefab38e108 - md5: a1bd6dbd8795a23cae468f59a0795883 + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + size: 132260 + timestamp: 1770566135697 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda + sha256: b77ed58eb235e5ad80e742b03caeed4bbc2a2ef064cb9a2deee3b75dfae91b2a + md5: 8b267f517b81c13594ed68d646fd5dcb depends: - - __osx >=11.0 - - libcxx >=19 - - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 + - __linux + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python constrains: - - boost <0.0a0 - - py-boost <0.0a0 - license: BSL-1.0 - purls: [] - size: 104511 - timestamp: 1766349822546 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e - md5: 72c8fd1af66bd67bf580645b426513ed + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + size: 133644 + timestamp: 1770566133040 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.11.0-pyhecfbec7_0.conda + sha256: 1f90e346baab7926bc52d7b60c0625087e96b4fab1bdb9a7fe83ac842312c930 + md5: 326c46b8ec2a1b4964927c7ea55ebf49 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 79965 - timestamp: 1764017188531 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - sha256: 4c19b211b3095f541426d5a9abac63e96a5045e509b3d11d4f9482de53efe43b - md5: f157c098841474579569c85a60ece586 + - __unix + - decorator >=5.1.0 + - ipython_pygments_lexers >=1.0.0 + - jedi >=0.18.2 + - matplotlib-inline >=0.1.6 + - prompt-toolkit >=3.0.41,<3.1.0 + - pygments >=2.14.0 + - python >=3.12 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - pexpect >4.6 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython?source=hash-mapping + size: 648197 + timestamp: 1772790149194 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 + md5: bd80ba060603cc228d9d81c257093119 depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 78854 - timestamp: 1764017554982 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 - md5: 006e7ddd8a110771134fcc4e1e3a6ffa + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython-pygments-lexers?source=hash-mapping + size: 13993 + timestamp: 1737123723464 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + sha256: 6bb58afb7eabc8b4ac0c7e92707fb498313cc0164cf04e7ba1090dbf49af514b + md5: d68e3f70d1f068f1b66d94822fdc644e depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 79443 - timestamp: 1764017945924 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b - md5: 366b40a69f0ad6072561c1d09301c886 + - comm >=0.1.3 + - ipython >=6.1.0 + - jupyterlab_widgets >=3.0.15,<3.1.0 + - python >=3.10 + - traitlets >=4.3.1 + - widgetsnbextension >=4.0.14,<4.1.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipywidgets?source=hash-mapping + size: 114376 + timestamp: 1762040524661 +- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed + md5: 0b0154421989637d424ccf0f104be51a depends: - - __glibc >=2.17,<3.0.a0 - - libbrotlicommon 1.2.0 hb03c661_1 - - libgcc >=14 + - arrow >=0.15.0 + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 34632 - timestamp: 1764017199083 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - sha256: 729158be90ae655a4e0427fe4079767734af1f9b69ff58cf94ca6e8d4b3eb4b7 - md5: 63186ac7a8a24b3528b4b14f21c03f54 + purls: + - pkg:pypi/isoduration?source=hash-mapping + size: 19832 + timestamp: 1733493720346 +- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 + md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 depends: - - __osx >=10.13 - - libbrotlicommon 1.2.0 h8616949_1 - license: MIT - license_family: MIT - purls: [] - size: 30835 - timestamp: 1764017584474 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf - md5: 079e88933963f3f149054eec2c487bc2 + - parso >=0.8.3,<0.9.0 + - python >=3.9 + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/jedi?source=hash-mapping + size: 843646 + timestamp: 1733300981994 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d depends: - - __osx >=11.0 - - libbrotlicommon 1.2.0 hc919400_1 - license: MIT - license_family: MIT - purls: [] - size: 29452 - timestamp: 1764017979099 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d - md5: 4ffbb341c8b616aa2494b6afb26a0c5f + - markupsafe >=2.0 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=hash-mapping + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + sha256: 301539229d7be6420c084490b8145583291123f0ce6b92f56be5948a2c83a379 + md5: 615de2a4d97af50c350e5cf160149e77 depends: - - __glibc >=2.17,<3.0.a0 - - libbrotlicommon 1.2.0 hb03c661_1 - - libgcc >=14 + - python >=3.10 + - setuptools + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/joblib?source=hash-mapping + size: 226448 + timestamp: 1765794135253 +- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda + sha256: ba03ca5a6db38d9f48bd30172e8c512dea7a686a5c7701c6fcdb7b3023dae2ad + md5: 8d5f66ebf832c4ce28d5c37a0e76605c + depends: + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/json5?source=hash-mapping + size: 34017 + timestamp: 1767325114901 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda + sha256: 1a1328476d14dfa8b84dbacb7f7cd7051c175498406dc513ca6c679dc44f3981 + md5: cd2214824e36b0180141d422aba01938 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + size: 13967 + timestamp: 1765026384757 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 + md5: ada41c863af263cc4c5fcbaff7c3e4dc + depends: + - attrs >=22.2.0 + - jsonschema-specifications >=2023.3.6 + - python >=3.10 + - referencing >=0.28.4 + - rpds-py >=0.25.0 + - python license: MIT license_family: MIT - purls: [] - size: 298378 - timestamp: 1764017210931 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - sha256: 8ece7b41b6548d6601ac2c2cd605cf2261268fc4443227cc284477ed23fbd401 - md5: 12a58fd3fc285ce20cf20edf21a0ff8f + purls: + - pkg:pypi/jsonschema?source=hash-mapping + size: 82356 + timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 + md5: 439cd0f567d697b20a8f45cb70a1005a depends: - - __osx >=10.13 - - libbrotlicommon 1.2.0 h8616949_1 + - python >=3.10 + - referencing >=0.31.0 + - python license: MIT license_family: MIT - purls: [] - size: 310355 - timestamp: 1764017609985 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 - md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 + purls: + - pkg:pypi/jsonschema-specifications?source=hash-mapping + size: 19236 + timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + sha256: 6886fc61e4e4edd38fd38729976b134e8bd2143f7fce56cc80d7ac7bac99bce1 + md5: 8368d58342d0825f0843dc6acdd0c483 depends: - - __osx >=11.0 - - libbrotlicommon 1.2.0 hc919400_1 + - jsonschema >=4.26.0,<4.26.1.0a0 + - fqdn + - idna + - isoduration + - jsonpointer >1.13 + - rfc3339-validator + - rfc3986-validator >0.1.0 + - rfc3987-syntax >=1.1.0 + - uri-template + - webcolors >=24.6.0 license: MIT license_family: MIT purls: [] - size: 290754 - timestamp: 1764018009077 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda - build_number: 5 - sha256: 8352f472c49c42a83a20387b5f6addab1f910c5a62f4f5b8998d7dc89131ba2e - md5: 9b6cb3aa4b7912121c64b97a76ca43d5 + size: 4740 + timestamp: 1767839954258 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + sha256: 897ad2e2c2335ef3c2826d7805e16002a1fd0d509b4ae0bc66617f0e0ff07bc2 + md5: 62b7c96c6cd77f8173cc5cada6a9acaa depends: - - libblas 3.11.0 5_h5875eb1_mkl - constrains: - - liblapack 3.11.0 5*_mkl - - liblapacke 3.11.0 5*_mkl - - blas 2.305 mkl - track_features: - - blas_mkl + - importlib-metadata >=4.8.3 + - jupyter_server >=1.1.2 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD - purls: [] - size: 18385 - timestamp: 1765818571086 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - build_number: 5 - sha256: 8077c29ea720bd152be6e6859a3765228cde51301fe62a3b3f505b377c2cb48c - md5: b31d771cbccff686e01a687708a7ca41 + purls: + - pkg:pypi/jupyter-lsp?source=hash-mapping + size: 60377 + timestamp: 1756388269267 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + sha256: e402bd119720862a33229624ec23645916a7d47f30e1711a4af9e005162b84f3 + md5: 8a3d6d0523f66cf004e563a50d9392b3 depends: - - libblas 3.11.0 5_he492b99_openblas - constrains: - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas + - jupyter_core >=5.1 + - python >=3.10 + - python-dateutil >=2.8.2 + - pyzmq >=25.0 + - tornado >=6.4.1 + - traitlets >=5.3 + - python license: BSD-3-Clause license_family: BSD - purls: [] - size: 18484 - timestamp: 1765819073006 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - build_number: 5 - sha256: 38809c361bbd165ecf83f7f05fae9b791e1baa11e4447367f38ae1327f402fc0 - md5: efd8bd15ca56e9d01748a3beab8404eb + purls: + - pkg:pypi/jupyter-client?source=hash-mapping + size: 112785 + timestamp: 1767954655912 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a + md5: b38fe4e78ee75def7e599843ef4c1ab0 depends: - - libblas 3.11.0 5_h51639a9_openblas + - __unix + - python + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python constrains: - - liblapacke 3.11.0 5*_openblas - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas + - pywin32 >=300 license: BSD-3-Clause license_family: BSD - purls: [] - size: 18548 - timestamp: 1765819108956 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-12.2.0.37-hecca717_0.conda - sha256: 5fef778f1e68d1b0a913d6511bec607137e9d2e8e9e44bc564309712e82440fe - md5: e7f96ec366228e01de0047d62b94c446 + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + size: 65503 + timestamp: 1760643864586 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda + sha256: e9964aaaf6d24a685cd5ce9d75731b643ed7f010fb979574a6580cd2f974c6cd + md5: 31e11c30bbee1682a55627f953c6725a depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=13.2,<13.3.0a0 - - libgcc >=14 - - libstdcxx >=14 - license: LicenseRef-NVIDIA-End-User-License-Agreement - purls: [] - size: 184427617 - timestamp: 1773100202937 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda - sha256: 5454709d9fb6e9c3dd6423bc284fa7835a7823bfa8323f6e8786cdd555101fab - md5: 0a5563efed19ca4461cf927419b6eb73 + - jsonschema-with-format-nongpl >=4.18.0 + - packaging + - python >=3.9 + - python-json-logger >=2.0.4 + - pyyaml >=5.3 + - referencing + - rfc3339-validator + - rfc3986-validator >=0.1.1 + - traitlets >=5.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-events?source=hash-mapping + size: 24306 + timestamp: 1770937604863 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + sha256: 74c4e642be97c538dae1895f7052599dfd740d8bd251f727bce6453ce8d6cd9a + md5: d79a87dcfa726bcea8e61275feed6f83 depends: - - __glibc >=2.17,<3.0.a0 - - krb5 >=1.21.3,<1.22.0a0 - - libgcc >=14 - - libnghttp2 >=1.67.0,<2.0a0 - - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.4,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: curl - license_family: MIT - purls: [] - size: 462942 - timestamp: 1767821743793 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda - sha256: 1a0af3b7929af3c5893ebf50161978f54ae0256abb9532d4efba2735a0688325 - md5: de1910529f64ba4a9ac9005e0be78601 + - anyio >=3.1.0 + - argon2-cffi >=21.1 + - jinja2 >=3.0.3 + - jupyter_client >=7.4.4 + - jupyter_core >=4.12,!=5.0.* + - jupyter_events >=0.11.0 + - jupyter_server_terminals >=0.4.4 + - nbconvert-core >=6.4.4 + - nbformat >=5.3.0 + - overrides >=5.0 + - packaging >=22.0 + - prometheus_client >=0.9 + - python >=3.10 + - pyzmq >=24 + - send2trash >=1.8.2 + - terminado >=0.8.3 + - tornado >=6.2.0 + - traitlets >=5.6.0 + - websocket-client >=1.7 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-server?source=hash-mapping + size: 347094 + timestamp: 1755870522134 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 + md5: 7b8bace4943e0dc345fc45938826f2b8 depends: - - __osx >=10.13 - - krb5 >=1.21.3,<1.22.0a0 - - libnghttp2 >=1.67.0,<2.0a0 - - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.4,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: curl - license_family: MIT - purls: [] - size: 419089 - timestamp: 1767822218800 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.18.0-he38603e_0.conda - sha256: 11c78b3e89bc332933386f0a11ac60d9200afb7a811b9e3bec98aef8d4a6389b - md5: 36190179a799f3aee3c2d20a8a2b970d + - python >=3.10 + - terminado >=0.8.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-server-terminals?source=hash-mapping + size: 22052 + timestamp: 1768574057200 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda + sha256: 53705b629a27cc91600f99b70c39181bbc428cdbc70f22aa40cea21e9fa6d560 + md5: c4b96d937d1b03203c00fed92e713cd1 + depends: + - async-lru >=1.0.0 + - httpx >=0.25.0,<1 + - ipykernel >=6.5.0,!=6.30.0 + - jinja2 >=3.0.3 + - jupyter-lsp >=2.0.0 + - jupyter_core + - jupyter_server >=2.4.0,<3 + - jupyterlab_server >=2.28.0,<3 + - notebook-shim >=0.2 + - packaging + - python >=3.10 + - setuptools >=41.1.0 + - tomli >=1.2.2 + - tornado >=6.2.0 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab?source=hash-mapping + size: 7994221 + timestamp: 1771885064306 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 + md5: fd312693df06da3578383232528c468d + depends: + - pygments >=2.4.1,<3 + - python >=3.9 + constrains: + - jupyterlab >=4.0.8,<5.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-pygments?source=hash-mapping + size: 18711 + timestamp: 1733328194037 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee + md5: a63877cb23de826b1620d3adfccc4014 + depends: + - babel >=2.10 + - jinja2 >=3.0.3 + - json5 >=0.9.0 + - jsonschema >=4.18 + - jupyter_server >=1.21,<3 + - packaging >=21.3 + - python >=3.10 + - requests >=2.31 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-server?source=hash-mapping + size: 51621 + timestamp: 1761145478692 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + sha256: 5c03de243d7ae6247f39a402f4785d95e61c3be79ef18738e8f17155585d31a8 + md5: dbf8b81974504fa51d34e436ca7ef389 + depends: + - python >=3.10 + - python + constrains: + - jupyterlab >=3,<5 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-widgets?source=hash-mapping + size: 216779 + timestamp: 1762267481404 +- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 + md5: 9b965c999135d43a3d0f7bd7d024e26a depends: - - __osx >=11.0 - - krb5 >=1.21.3,<1.22.0a0 - - libnghttp2 >=1.67.0,<2.0a0 - - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.4,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: curl + - python >=3.10 + license: MIT license_family: MIT - purls: [] - size: 402681 - timestamp: 1767822693908 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - sha256: fa002b43752fe5860e588435525195324fe250287105ebd472ac138e97de45e6 - md5: 836389b6b9ae58f3fbcf7cafebd5c7f2 + purls: + - pkg:pypi/lark?source=hash-mapping + size: 94312 + timestamp: 1761596921009 +- conda: https://conda.anaconda.org/conda-forge/noarch/legacy-cgi-2.6.4-pyhcf101f3_0.conda + sha256: 403f11e1a3a9f539aceb009c6f0939098075d2b2efdc6dd9c0d0dd02eb404fc4 + md5: b22fe9a3d53bc833659823e48f879db9 depends: - - __osx >=11.0 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 570141 - timestamp: 1772001147762 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - sha256: ce1049fa6fda9cf08ff1c50fb39573b5b0ea6958375d8ea7ccd8456ab81a0bcb - md5: e9c56daea841013e7774b5cd46f41564 + - python >=3.10,<3.13 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/legacy-cgi?source=hash-mapping + run_exports: {} + size: 19854 + timestamp: 1761574469157 +- conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda + sha256: 97dcff433510cfb92f775f6c5da4e18d19ff70d8921187dae0725158946fb3e5 + md5: 237d294ca3acd678f590f1754e090802 depends: - - __osx >=11.0 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 568910 - timestamp: 1772001095642 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 - md5: 6c77a605a7a689d17d4819c0f8ac9a00 + - packaging >=17.1 + - python >=3.10 + - setuptools + - typing_extensions + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/lightning-utilities?source=hash-mapping + size: 32379 + timestamp: 1772138325789 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + sha256: 7b1da4b5c40385791dbc3cc85ceea9fad5da680a27d5d3cb8bfaa185e304a89e + md5: 5b5203189eb668f042ac2b0826244964 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - mdurl >=0.1,<1 + - python >=3.10 license: MIT license_family: MIT - purls: [] - size: 73490 - timestamp: 1761979956660 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda - sha256: 025f8b1e85dd8254e0ca65f011919fb1753070eb507f03bca317871a884d24de - md5: 31aa65919a729dc48180893f62c25221 + purls: + - pkg:pypi/markdown-it-py?source=hash-mapping + size: 64736 + timestamp: 1754951288511 +- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 + md5: 00e120ce3e40bad7bfc78861ce3c4a25 depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 70840 - timestamp: 1761980008502 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - sha256: 5e0b6961be3304a5f027a8c00bd0967fc46ae162cffb7553ff45c70f51b8314c - md5: a6130c709305cd9828b4e1bd9ba0000c + - python >=3.10 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/matplotlib-inline?source=hash-mapping + size: 15175 + timestamp: 1761214578417 +- conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda + sha256: d35c53a76385e92ca2534af47372dc4116ab7be7c4f769e00c0876561d951395 + md5: 1d086e68846dadf85ee208c9459bc8bc depends: - - __osx >=11.0 + - python >=3.9 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/mda-xdrlib?source=hash-mapping + size: 16535 + timestamp: 1734100847370 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 + md5: 592132998493b3ff25fd7479396e8351 + depends: + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 55420 - timestamp: 1761980066242 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 - md5: c277e0a4d549b03ac1e9d6cbbe3d017b + purls: + - pkg:pypi/mdurl?source=hash-mapping + size: 14465 + timestamp: 1733255681319 +- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + sha256: d3fb4beb5e0a52b6cc33852c558e077e1bfe44df1159eb98332d69a264b14bae + md5: b11e360fc4de2b0035fc8aaa74f17fd6 depends: - - ncurses - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause + - python >=3.10 + - typing_extensions + - python + license: BSD-3-Clause license_family: BSD - purls: [] - size: 134676 - timestamp: 1738479519902 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - sha256: 6cc49785940a99e6a6b8c6edbb15f44c2dd6c789d9c283e5ee7bdfedd50b4cd6 - md5: 1f4ed31220402fcddc083b4bff406868 + purls: + - pkg:pypi/mistune?source=hash-mapping + size: 74250 + timestamp: 1766504456031 +- conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda + sha256: 0f6f76159c40af2c7ae483934513e27a9e4137364ec717c1d69511dd4bbc6a68 + md5: 74149a38e723261f9f61bddd69e90022 depends: - - ncurses - - __osx >=10.13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause + - python >=3.10 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 115563 - timestamp: 1738479554273 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 - md5: 44083d2d2c2025afca315c7a172eab2b + purls: + - pkg:pypi/mpmath?source=hash-mapping + size: 464419 + timestamp: 1771870721583 +- conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + sha256: d09c47c2cf456de5c09fa66d2c3c5035aa1fa228a1983a433c47b876aa16ce90 + md5: 37293a85a0f4f77bbd9cf7aaefc62609 depends: - - ncurses - - __osx >=11.0 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause + - python >=3.9 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/munkres?source=hash-mapping + size: 15851 + timestamp: 1749895533014 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.24.0-pyhcf101f3_0.conda + sha256: 57f525a1c55b08c3204fab05d2c74a3e9c2172d7f08f1ecaa07e19865cc1d7cf + md5: 42ef6cbb3e1d0e6689b9dd160f560eae + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/narwhals?source=hash-mapping + run_exports: {} + size: 289946 + timestamp: 1783943716915 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + sha256: 1b66960ee06874ddceeebe375d5f17fb5f393d025a09e15b830ad0c4fffb585b + md5: 00f5b8dafa842e0c27c1cd7296aa4875 + depends: + - jupyter_client >=6.1.12 + - jupyter_core >=4.12,!=5.0.* + - nbformat >=5.1 + - python >=3.8 + - traitlets >=5.4 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 107691 - timestamp: 1738479560845 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 - md5: 172bf1cd1ff8629f2b1179945ed45055 + purls: + - pkg:pypi/nbclient?source=hash-mapping + size: 28473 + timestamp: 1766485646962 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda + sha256: 628fea99108df8e33396bb0b88658ec3d58edf245df224f57c0dce09615cbed2 + md5: b14079a39ae60ac7ad2ec3d9eab075ca depends: - - libgcc-ng >=12 - license: BSD-2-Clause + - beautifulsoup4 + - bleach-with-css !=5.0.0 + - defusedxml + - importlib-metadata >=3.6 + - jinja2 >=3.0 + - jupyter_core >=4.7 + - jupyterlab_pygments + - markupsafe >=2.0 + - mistune >=2.0.3,<4 + - nbclient >=0.5.0 + - nbformat >=5.7 + - packaging + - pandocfilters >=1.4.1 + - pygments >=2.4.1 + - python >=3.10 + - traitlets >=5.1 + - python + constrains: + - pandoc >=2.9.2,<4.0.0 + - nbconvert ==7.17.0 *_0 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 112766 - timestamp: 1702146165126 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - sha256: 0d238488564a7992942aa165ff994eca540f687753b4f0998b29b4e4d030ff43 - md5: 899db79329439820b7e8f8de41bca902 - license: BSD-2-Clause + purls: + - pkg:pypi/nbconvert?source=hash-mapping + size: 202284 + timestamp: 1769709543555 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 + md5: bbe1963f1e47f594070ffe87cdf612ea + depends: + - jsonschema >=2.6 + - jupyter_core >=4.12,!=5.0.* + - python >=3.9 + - python-fastjsonschema >=2.15 + - traitlets >=5.1 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 106663 - timestamp: 1702146352558 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f - md5: 36d33e440c31857372a72137f78bacf5 + purls: + - pkg:pypi/nbformat?source=hash-mapping + size: 100945 + timestamp: 1733402844974 +- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 + md5: 598fd7d4d0de2455fb74f56063969a97 + depends: + - python >=3.9 license: BSD-2-Clause license_family: BSD - purls: [] - size: 107458 - timestamp: 1702146414478 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 - md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - expat 2.7.4.* - license: MIT - license_family: MIT - purls: [] - size: 76798 - timestamp: 1771259418166 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - sha256: 8d9d79b2de7d6f335692391f5281607221bf5d040e6724dad4c4d77cd603ce43 - md5: a684eb8a19b2aa68fde0267df172a1e3 + purls: + - pkg:pypi/nest-asyncio?source=hash-mapping + size: 11543 + timestamp: 1733325673691 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 + md5: a2c1eeadae7a309daed9d62c96012a2b depends: - - __osx >=10.13 + - python >=3.11 + - python constrains: - - expat 2.7.4.* - license: MIT - license_family: MIT - purls: [] - size: 74578 - timestamp: 1771260142624 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - sha256: 03887d8080d6a8fe02d75b80929271b39697ecca7628f0657d7afaea87761edf - md5: a92e310ae8dfc206ff449f362fc4217f - depends: - - __osx >=11.0 + - numpy >=1.25 + - scipy >=1.11.2 + - matplotlib-base >=3.8 + - pandas >=2.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/networkx?source=hash-mapping + size: 1587439 + timestamp: 1765215107045 +- conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + sha256: d38542a151a90417065c1a234866f97fd1ea82a81de75ecb725955ab78f88b4b + md5: 9a66894dfd07c4510beb6b3f9672ccc0 constrains: - - expat 2.7.4.* - license: MIT - license_family: MIT - purls: [] - size: 68199 - timestamp: 1771260020767 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 - md5: a360c33a5abe61c07959e449fa1453eb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 58592 - timestamp: 1769456073053 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda - sha256: 951958d1792238006fdc6fce7f71f1b559534743b26cc1333497d46e5903a2d6 - md5: 66a0dc7464927d0853b590b6f53ba3ea - depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 53583 - timestamp: 1769456300951 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 - md5: 43c04d9cb46ef176bb2a4c77e324d599 - depends: - - __osx >=11.0 - license: MIT - license_family: MIT + - mkl <0.a0 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 40979 - timestamp: 1769456747661 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.2-ha770c72_0.conda - sha256: 2e1bfe1e856eb707d258f669ef6851af583ceaffab5e64821b503b0f7cd09e9e - md5: 26c746d14402a3b6c684d045b23b9437 + size: 3843 + timestamp: 1582593857545 +- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda + sha256: c25d4c77747af7598c843b626c051e93ea3d8d0a7782bae1c48ba8f4dd1961d4 + md5: ad642622e20bd5dbe4157d35ddf1db47 depends: - - libfreetype6 >=2.14.2 - license: GPL-2.0-only OR FTL - purls: [] - size: 8035 - timestamp: 1772757210108 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda - sha256: d24d0a404a2e999c1b7bac519fedf1f36acf6c76e77d74ddd9ed809f104b4a8c - md5: bf29ee73174c610d7cad0b081b500df7 + - importlib_resources >=5.0 + - jupyter_server >=2.4.0,<3 + - jupyterlab >=4.5.5,<4.6 + - jupyterlab_server >=2.28.0,<3 + - notebook-shim >=0.2,<0.3 + - python >=3.10 + - tornado >=6.2.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/notebook?source=hash-mapping + size: 10111074 + timestamp: 1771943769401 +- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 + md5: e7f89ea5f7ea9401642758ff50a2d9c1 depends: - - libfreetype6 >=2.14.2 - license: GPL-2.0-only OR FTL - purls: [] - size: 8085 - timestamp: 1772756328684 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda - sha256: 6061ef5321b8e697d5577d8dfe7a4c75bfe3e706c956d0d84bfec6bea3ed9f77 - md5: a3a53232936b55ffea76806aefe19e8b + - jupyter_server >=1.8,<3 + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/notebook-shim?source=hash-mapping + size: 16817 + timestamp: 1733408419340 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda + sha256: a437b074cb53a94275ea2d8294776115780a9b9177acdf0dac658887e61e69b0 + md5: 577353bf1c9f4dca81e99400cc7ca571 depends: - - libfreetype6 >=2.14.2 - license: GPL-2.0-only OR FTL - purls: [] - size: 8076 - timestamp: 1772756349852 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.2-h73754d4_0.conda - sha256: aba65b94bdbed52de17ec3d0c6f2ebac2ef77071ad22d6900d1614d0dd702a0c - md5: 8eaba3d1a4d7525c6814e861614457fd + - python >=3.11 + - setuptools + license: CC-BY-4.0 + license_family: CC + purls: + - pkg:pypi/openff-amber-ff-ports?source=hash-mapping + size: 222475 + timestamp: 1758297599388 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda + sha256: 0ddbef322b893062565e91218689a9ffff1380f6742df12d783ce63b49325c57 + md5: a58c407277d3c04a21cea9eaeea03618 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libpng >=1.6.55,<1.7.0a0 - - libzlib >=1.3.1,<2.0a0 + - python >=3.11 + - setuptools constrains: - - freetype >=2.14.2 - license: GPL-2.0-only OR FTL - purls: [] - size: 386316 - timestamp: 1772757193822 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda - sha256: 75dcab3b5c2c1fe3b2d5a4b97230bc04d9c11151739d9644ec9fa2728886cc1d - md5: 2e6760656fde7df787fdef045d0fc65d + - openforcefields ==9999999999 + license: CC-BY-4.0 + license_family: CC + purls: + - pkg:pypi/openforcefields?source=hash-mapping + size: 118532 + timestamp: 1768284405101 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda + sha256: b3e15d74848dc27a9f3f4a62bb19a7fbad7c15beb7b7f8d830542123b5916690 + md5: b667da0d26a7e9f5234028e7265bf524 depends: - - __osx >=11.0 - - libpng >=1.6.55,<1.7.0a0 - - libzlib >=1.3.1,<2.0a0 - constrains: - - freetype >=2.14.2 - license: GPL-2.0-only OR FTL + - openff-interchange-base 0.5.2 pyhd8ed1ab_0 + - openff-toolkit >=0.17.0,<0.19 + - openmm 8.* + - pyedr + - python >=3.11,<3.14.0a0 + license: MIT + license_family: MIT purls: [] - size: 364817 - timestamp: 1772756327104 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda - sha256: 24dd0e0bee56e87935f885929f67659f1d3b8a01e7546568de2919cffd9e2e36 - md5: e726e134a392ae5d7bafa6cc4a3d5725 + size: 8500 + timestamp: 1772496921112 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda + sha256: 47fe4b3b12c11540d17cbb20f96359541a0c0e8b9f681fa71a78eb2e915a292b + md5: 5f8bce63ca0ef1faa52fbcaf7db51607 depends: - - __osx >=11.0 - - libpng >=1.6.55,<1.7.0a0 - - libzlib >=1.3.1,<2.0a0 + - numpy <2.5 + - openff-toolkit-base >=0.16.0,<0.19 + - openff-units >=0.2.0,<0.4.0 + - openff-utilities >=0.1.5 + - pydantic >=2,<3,<2.12 + - python >=3.11,<3.14.0a0 constrains: - - freetype >=2.14.2 - license: GPL-2.0-only OR FTL - purls: [] - size: 338032 - timestamp: 1772756347899 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 - md5: 0aa00f03f9e39fb9876085dee11a85d4 + - jax >=0.3 + - smirnoff-plugins >=2024.07.0 + - jaxlib >=0.3 + - foyer =1 + - lammps >=2023.08.02 + license: MIT + license_family: MIT + purls: + - pkg:pypi/openff-interchange?source=hash-mapping + size: 700362 + timestamp: 1772496904797 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda + sha256: 607df80ceff8320dc9b9f30cdc403d91b9fd6f686b98aa2c065c04beb4ca9583 + md5: 9be58005f9ec3010dc9bb94dc22a46e5 depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - constrains: - - libgcc-ng ==15.2.0=*_18 - - libgomp 15.2.0 he0feb66_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 1041788 - timestamp: 1771378212382 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda - sha256: 83366f11615ab234aa1e0797393f9e07b78124b5a24c4a9f8af0113d02df818e - md5: 9a5cb96e43f5c2296690186e15b3296f + - openff-nagl-base 0.5.4 pyhd8ed1ab_1 + - openff-nagl-models >=0.1.0 + - python >=3.11 + license: MIT + license_family: MIT + purls: [] + size: 7436 + timestamp: 1761911874786 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda + sha256: 6046497a5c336c30ecb05b8c6770b532999c01812ff5702a79fe692e0c4bf8d6 + md5: 4f28458d1282a127010650bff68bfb7c depends: - - _openmp_mutex + - click + - click-option-group + - openff-toolkit-base >=0.11.0 + - openff-units + - pydantic <3 + - python >=3.11 + - pytorch >=2.0,<=2.8 + - pytorch-lightning + - pyyaml + - rdkit + - rich + - tqdm constrains: - - libgcc-ng ==15.2.0=*_18 - - libgomp 15.2.0 18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 423025 - timestamp: 1771378225170 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - sha256: 1d9c4f35586adb71bcd23e31b68b7f3e4c4ab89914c26bed5f2859290be5560e - md5: 92df6107310b1fff92c4cc84f0de247b + - openeye-toolkits + - dgl >=1.0,<=3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/openff-nagl?source=hash-mapping + size: 119924 + timestamp: 1761845487531 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda + sha256: 824a8c18c03b52589ab1006b13c38511243d00acf3a165e22cd01be7555487e6 + md5: 6b73b0f2cf46c04e09c30bf062cff01b depends: - - _openmp_mutex + - importlib_resources + - packaging + - platformdirs + - python >=3.11 constrains: - - libgcc-ng ==15.2.0=*_18 - - libgomp 15.2.0 18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 401974 - timestamp: 1771378877463 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 - md5: d5e96b1ed75ca01906b3d2469b4ce493 + - openff-nagl >=0.4.0 + license: CC-BY-4.0 + license_family: CC + purls: + - pkg:pypi/openff-nagl-models?source=hash-mapping + size: 53380163 + timestamp: 1757448697983 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda + sha256: 4e7b9c31083727470518ddec9cf5edc569de22bf8e18c09e27b9905805ce8ca3 + md5: b71429982dee66c51f68e99eff602d3a depends: - - libgcc 15.2.0 he0feb66_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL + - ambertools >=22 + - ipywidgets + - mdtraj + - notebook + - openff-interchange >=0.4,<0.6 + - openff-nagl >=0.4 + - openff-toolkit-base 0.18.0 pyhd8ed1ab_3 + - openmm >=7.6 + - python >=3.11,<3.14a0 + - rdkit !=2024.03.6 + license: MIT + license_family: MIT purls: [] - size: 27526 - timestamp: 1771378224552 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee - md5: 9063115da5bc35fdc3e1002e69b9ef6e + size: 10159 + timestamp: 1771436993690 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda + sha256: 0c85e518e00677ebc8332da4f4b37f519b3eddcbc526962a8e47af8f42f5a11c + md5: f6d43b327ba02dcbddb1e138bf188c11 depends: - - libgfortran5 15.2.0 h68bc16d_18 + - bson + - cachetools + - networkx >=2.5 + - numpy + - openff-amber-ff-ports >=2025.09.0 + - openff-forcefields + - openff-units + - openff-utilities + - packaging + - python >=3.11,<3.14a0 + - python-constraint + - typing_extensions + - xmltodict <1.0.3 constrains: - - libgfortran-ng ==15.2.0=*_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 27523 - timestamp: 1771378269450 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda - sha256: fb06c2a2ef06716a0f2a6550f5d13cdd1d89365993068512b7ae3c34e6e665d9 - md5: 34a9f67498721abcfef00178bcf4b190 + - qcportal >=0.50.0 + - pydantic <3.0.0a0 + - openforcefield ==9999999999 + license: MIT + license_family: MIT + purls: + - pkg:pypi/openff-toolkit?source=hash-mapping + size: 8109584 + timestamp: 1771436972576 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda + sha256: ce06098d58e12bbcdae73a5eb62556b59a0b39b01518ee61fd2713b7fb5948d4 + md5: 764a41caac8b44b289721573cf25e471 depends: - - libgfortran5 15.2.0 hd16e46c_18 + - numpy + - openff-utilities + - pint >=0.24.0,<0.26 + - python >=3.11,<3.15.0a0 constrains: - - libgfortran-ng ==15.2.0=*_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 139761 - timestamp: 1771378423828 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - sha256: 63f89087c3f0c8621c5c89ecceec1e56e5e1c84f65fc9c5feca33a07c570a836 - md5: 26981599908ed2205366e8fc91b37fc6 + - openmm >7.6 + license: MIT + license_family: MIT + purls: + - pkg:pypi/openff-units?source=hash-mapping + size: 24811 + timestamp: 1767915235307 +- conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda + sha256: a8a4275c9c0ba101fe59ec65178958b29f453e83452b533c80f77401f42f9229 + md5: 77e1227050daa7fc8792bf1286a3e57c depends: - - libgfortran5 15.2.0 hdae7583_18 - constrains: - - libgfortran-ng ==15.2.0=*_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 138973 - timestamp: 1771379054939 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - sha256: 539b57cf50ec85509a94ba9949b7e30717839e4d694bc94f30d41c9d34de2d12 - md5: 646855f357199a12f02a87382d429b75 + - python >=3.11 + license: MIT + license_family: MIT + purls: + - pkg:pypi/openff-utilities?source=hash-mapping + size: 18866 + timestamp: 1765557239153 +- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c + md5: e51f1e4089cad105b6cac64bd8166587 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=15.2.0 - constrains: - - libgfortran 15.2.0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 2482475 - timestamp: 1771378241063 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - sha256: ddaf9dcf008c031b10987991aa78643e03c24a534ad420925cbd5851b31faa11 - md5: ca52daf58cea766656266c8771d8be81 + - python >=3.9 + - typing_utils + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/overrides?source=hash-mapping + size: 30139 + timestamp: 1734587755455 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 + md5: b76541e68fea4d511b1ac46a28dcd2c6 depends: - - libgcc >=15.2.0 - constrains: - - libgfortran 15.2.0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 1062274 - timestamp: 1771378232014 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - sha256: 91033978ba25e6a60fb86843cf7e1f7dc8ad513f9689f991c9ddabfaf0361e7e - md5: c4a6f7989cffb0544bfd9207b6789971 + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + size: 72010 + timestamp: 1769093650580 +- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f + md5: 457c2c8c08e54905d6954e79cb5b5db9 depends: - - libgcc >=15.2.0 - constrains: - - libgfortran 15.2.0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 598634 - timestamp: 1771378886363 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda - sha256: a27e44168a1240b15659888ce0d9b938ed4bdb49e9ea68a7c1ff27bcea8b55ce - md5: bb26456332b07f68bf3b7622ed71c0da + - python !=3.0,!=3.1,!=3.2,!=3.3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandocfilters?source=hash-mapping + size: 11627 + timestamp: 1631603397334 +- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + sha256: 42b2d77ccea60752f3aa929a6413a7835aaacdbbde679f2f5870a744fa836b94 + md5: 97c1ce2fffa1209e7afb432810ec6e12 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/parso?source=hash-mapping + size: 82287 + timestamp: 1770676243987 +- conda: https://conda.anaconda.org/conda-forge/noarch/pdbfixer-1.12-pyhd8ed1ab_1.conda + sha256: 00d71eb14af7634d48c32600c55ae385b8d45b3d4c74b6c75c404f5af358cd4d + md5: ae0b5e880fd6f5815fe7d2075200c33b + depends: + - legacy-cgi + - numpy + - openmm >=8.2 + - python >=3.10 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/pdbfixer?source=hash-mapping + run_exports: {} + size: 546457 + timestamp: 1775865529631 +- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a + md5: d0d408b1f18883a944376da5cf8101ea depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - libiconv >=1.18,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - pcre2 >=10.47,<10.48.0a0 - constrains: - - glib 2.86.4 *_1 - license: LGPL-2.1-or-later - purls: [] - size: 4398701 - timestamp: 1771863239578 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda - sha256: d45fd67e18e793aeb2485a7efe3e882df594601ed6136ed1863c56109e4ad9e3 - md5: b8437d8dc24f46da3565d7f0c5a96d45 + - ptyprocess >=0.5 + - python >=3.9 + license: ISC + purls: + - pkg:pypi/pexpect?source=hash-mapping + size: 53561 + timestamp: 1733302019362 +- conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda + sha256: 9fbaf42c68eeecd36e578cd39c16a9f8d4f2ecb6bf80d087bd08c88e48ccab4d + md5: e8d84977b2cab87277e1ac38173fe69c depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - libiconv >=1.18,<2.0a0 - - libintl >=0.25.1,<1.0a0 - - libzlib >=1.3.1,<2.0a0 - - pcre2 >=10.47,<10.48.0a0 + - python >=3.11 + - platformdirs >=2.1.0 + - flexcache >=0.3 + - flexparser >=0.4 + - typing_extensions >=4.0.0 + - python constrains: - - glib 2.86.4 *_1 - license: LGPL-2.1-or-later - purls: [] - size: 4186085 - timestamp: 1771863964173 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda - sha256: a4254a241a96198e019ced2e0d2967e4c0ef64fac32077a45c065b32dc2b15d2 - md5: 673069f6725ed7b1073f9b96094294d1 + - numpy >=1.23 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pint?source=hash-mapping + size: 244993 + timestamp: 1762481838471 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.2.1-pyh8b19718_0.conda + sha256: c205bae42eb11b364fe3663a817cbcbc20a8ef544c6b2ff6f391013487117259 + md5: 77b6cf3b0689d9fc68561df0db9b856d depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - libiconv >=1.18,<2.0a0 - - libintl >=0.25.1,<1.0a0 - - libzlib >=1.3.1,<2.0a0 - - pcre2 >=10.47,<10.48.0a0 - constrains: - - glib 2.86.4 *_1 - license: LGPL-2.1-or-later - purls: [] - size: 4108927 - timestamp: 1771864169970 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 - md5: 239c5e9546c38a1e884d69effcf4c882 + - python >=3.10,<3.13.0a0 + - setuptools + - wheel + license: MIT + license_family: MIT + purls: + - pkg:pypi/pip?source=hash-mapping + run_exports: {} + size: 1199593 + timestamp: 1785914492234 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda + sha256: 7f263219cecf0ba6d74c751efa60c4676ce823157ca90aa43ebba5ac615ca0fa + md5: 4fefefb892ce9cc1539405bec2f1a6cd depends: - - __glibc >=2.17,<3.0.a0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 603262 - timestamp: 1771378117851 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.1-default_h3d81e11_1000.conda - sha256: eecaf76fdfc085d8fed4583b533c10cb7f4a6304be56031c43a107e01a56b7e2 - md5: d821210ab60be56dd27b5525ed18366d + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=hash-mapping + size: 25643 + timestamp: 1771233827084 +- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda + sha256: 75b2589159d04b3fb92db16d9970b396b9124652c784ab05b66f584edc97f283 + md5: 7526d20621b53440b0aae45d4797847e depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - libxml2 >=2.13.8,<2.14.0a0 + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/prometheus-client?source=hash-mapping + size: 56634 + timestamp: 1768476602855 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae + md5: edb16f14d920fb3faf17f5ce582942d6 + depends: + - python >=3.10 + - wcwidth + constrains: + - prompt_toolkit 3.0.52 license: BSD-3-Clause license_family: BSD - purls: [] - size: 2450422 - timestamp: 1752761850672 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f - md5: 915f5995e94f60e9a4826e0b0920ee88 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: LGPL-2.1-only - purls: [] - size: 790176 - timestamp: 1754908768807 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - sha256: a1c8cecdf9966921e13f0ae921309a1f415dfbd2b791f2117cf7e8f5e61a48b6 - md5: 210a85a1119f97ea7887188d176db135 + purls: + - pkg:pypi/prompt-toolkit?source=hash-mapping + size: 273927 + timestamp: 1756321848365 +- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 + md5: 7d9daffbb8d8e0af0f769dbbcd173a54 depends: - - __osx >=10.13 - license: LGPL-2.1-only - purls: [] - size: 737846 - timestamp: 1754908900138 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - sha256: de0336e800b2af9a40bdd694b03870ac4a848161b35c8a2325704f123f185f03 - md5: 4d5a7445f0b25b6a3ddbb56e790f5251 + - python >=3.9 + license: ISC + purls: + - pkg:pypi/ptyprocess?source=hash-mapping + size: 19457 + timestamp: 1733302371990 +- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 + md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 depends: - - __osx >=11.0 - license: LGPL-2.1-only - purls: [] - size: 750379 - timestamp: 1754909073836 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda - sha256: 8c352744517bc62d24539d1ecc813b9fdc8a785c780197c5f0b84ec5b0dfe122 - md5: a8e54eefc65645193c46e8b180f62d22 + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pure-eval?source=hash-mapping + size: 16668 + timestamp: 1733569518868 +- conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + sha256: 6d8f03c13d085a569fde931892cded813474acbef2e03381a1a87f420c7da035 + md5: 46830ee16925d5ed250850503b5dc3a8 depends: - - __osx >=10.13 - - libiconv >=1.18,<2.0a0 - license: LGPL-2.1-or-later - purls: [] - size: 96909 - timestamp: 1753343977382 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - sha256: 99d2cebcd8f84961b86784451b010f5f0a795ed1c08f1e7c76fbb3c22abf021a - md5: 5103f6a6b210a3912faf8d7db516918c + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/py-cpuinfo?source=hash-mapping + size: 25766 + timestamp: 1733236452235 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda + sha256: d429f6f255fbe49f09b9ae1377aa8cbc4d9285b8b220c17ae2ad9c4894c91317 + md5: 1594696beebf1ecb6d29a1136f859a74 depends: - - __osx >=11.0 - - libiconv >=1.18,<2.0a0 - license: LGPL-2.1-or-later + - pybind11-global 2.13.6 *_3 + - python >=3.9 + constrains: + - pybind11-abi ==4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pybind11?source=hash-mapping + size: 186821 + timestamp: 1747935138653 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 + sha256: d4fb485b79b11042a16dc6abfb0c44c4f557707c2653ac47c81e5d32b24a3bb0 + md5: 878f923dd6acc8aeb47a75da6c4098be + license: BSD-3-Clause + license_family: BSD purls: [] - size: 90957 - timestamp: 1751558394144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - sha256: cc9aba923eea0af8e30e0f94f2ad7156e2984d80d1e8e7fe6be5a1f257f0eb32 - md5: 8397539e3a0bbd1695584fb4f927485a + size: 9906 + timestamp: 1610372835205 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda + sha256: c044cfcbe6ef0062d0960e9f9f0de5f8818cec84ed901219ff9994b9a9e57237 + md5: 730a5284e26d6bdb73332dafb26aec82 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - __unix + - python >=3.9 constrains: - - jpeg <0.0.0a - license: IJG AND BSD-3-Clause AND Zlib - purls: [] - size: 633710 - timestamp: 1762094827865 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda - sha256: ebe2877abc046688d6ea299e80d8322d10c69763f13a102010f90f7168cc5f54 - md5: 48dda187f169f5a8f1e5e07701d5cdd9 + - pybind11-abi ==4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pybind11-global?source=hash-mapping + size: 180116 + timestamp: 1747934418811 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 + md5: 12c566707c80111f9799308d9e265aef + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 110100 + timestamp: 1733195786147 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda + sha256: 26779821ba83b896f319837d7c5301cc244dee41b311d2bd57cbd693ed9e43ef + md5: 918d9adfc81cb14ab4cced31d22c7711 + depends: + - annotated-types >=0.6.0 + - pydantic-core 2.33.2 + - python >=3.10 + - typing-extensions >=4.6.1 + - typing-inspection >=0.4.0 + - typing_extensions >=4.12.2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic?source=hash-mapping + size: 307863 + timestamp: 1759584847417 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda + sha256: de95768c61325511ff2b477398ba57ea513c4052faab905a02784bb1f35bb550 + md5: 96431184a69661798fc2e90495192d69 depends: - - __osx >=10.13 - constrains: - - jpeg <0.0.0a - license: IJG AND BSD-3-Clause AND Zlib - purls: [] - size: 586189 - timestamp: 1762095332781 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda - sha256: 6c061c56058bb10374daaef50e81b39cf43e8aee21f0037022c0c39c4f31872f - md5: f0695fbecf1006f27f4395d64bd0c4b8 + - mda-xdrlib + - numpy + - python >=3.9 + - tqdm + license: LGPL-2.1-or-later + purls: + - pkg:pypi/pyedr?source=hash-mapping + size: 344942 + timestamp: 1734172243506 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a + md5: 6b6ece66ebcae2d5f326c77ef2c5a066 depends: - - __osx >=11.0 - constrains: - - jpeg <0.0.0a - license: IJG AND BSD-3-Clause AND Zlib - purls: [] - size: 551197 - timestamp: 1762095054358 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda - build_number: 5 - sha256: b411a9dccb21cd6231f8f66b63916a6520a7b23363e6f9d1d111e8660f2798b0 - md5: 88155c848e1278b0990692e716c9eab4 + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=hash-mapping + size: 889287 + timestamp: 1750615908735 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + sha256: 417fba4783e528ee732afa82999300859b065dc59927344b4859c64aae7182de + md5: 3687cc0b82a8b4c17e1f0eb7e47163d5 depends: - - libblas 3.11.0 5_h5875eb1_mkl - constrains: - - liblapacke 3.11.0 5*_mkl - - libcblas 3.11.0 5*_mkl - - blas 2.305 mkl - track_features: - - blas_mkl + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyparsing?source=hash-mapping + size: 110893 + timestamp: 1769003998136 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 license: BSD-3-Clause license_family: BSD - purls: [] - size: 18398 - timestamp: 1765818583873 -- conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda - build_number: 5 - sha256: 2c915fe2b3d806d4b82776c882ba66ba3e095e9e2c41cc5c3375bffec6bddfdc - md5: eb5b1c25d4ac30813a6ca950a58710d6 + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda + sha256: 953ae7a61c363fb57076f141c9c8def2e3767f11884165f6c4a2cccfb4d8b159 + md5: 2848429a376fd2676af5b320a28fd3d9 depends: - - libblas 3.11.0 5_he492b99_openblas - constrains: - - libcblas 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas - license: BSD-3-Clause + - python >=3.9 + license: BSD-2-Clause license_family: BSD - purls: [] - size: 18491 - timestamp: 1765819090240 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda - build_number: 5 - sha256: 735a6e6f7d7da6f718b6690b7c0a8ae4815afb89138aa5793abe78128e951dbb - md5: ca9d752201b7fa1225bca036ee300f2b + purls: + - pkg:pypi/python-constraint?source=hash-mapping + size: 26134 + timestamp: 1734363065821 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 depends: - - libblas 3.11.0 5_h51639a9_openblas - constrains: - - libcblas 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + size: 233310 + timestamp: 1751104122689 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 + md5: 23029aae904a2ba587daba708208012f + depends: + - python >=3.9 + - python license: BSD-3-Clause license_family: BSD - purls: [] - size: 18551 - timestamp: 1765819121855 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb - md5: c7c83eecbb72d88b940c249af56c8b17 + purls: + - pkg:pypi/fastjsonschema?source=hash-mapping + size: 244628 + timestamp: 1755304154927 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda + sha256: 97327b9509ae3aae28d27217a5d7bd31aff0ab61a02041e9c6f98c11d8a53b29 + md5: 32780d6794b8056b78602103a04e90ef depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - xz 5.8.2.* - license: 0BSD + - cpython 3.12.13.* + - python_abi * *_cp312 + license: Python-2.0 purls: [] - size: 113207 - timestamp: 1768752626120 -- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - sha256: 7ab3c98abd3b5d5ec72faa8d9f5d4b50dcee4970ed05339bc381861199dabb41 - md5: 688a0c3d57fa118b9c97bf7e471ab46c + size: 46449 + timestamp: 1772728979370 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca + md5: a61bf9ec79426938ff785eb69dbb1960 depends: - - __osx >=10.13 - constrains: - - xz 5.8.2.* - license: 0BSD - purls: [] - size: 105482 - timestamp: 1768753411348 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - sha256: 7bfc7ffb2d6a9629357a70d4eadeadb6f88fa26ebc28f606b1c1e5e5ed99dc7e - md5: 009f0d956d7bfb00de86901d16e486c7 + - python >=3.6 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/python-json-logger?source=hash-mapping + size: 13383 + timestamp: 1677079727691 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda + sha256: 467134ef39f0af2dbb57d78cb3e4821f01003488d331a8dd7119334f4f47bfbd + md5: 7ead57407430ba33f681738905278d03 depends: - - __osx >=11.0 + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/tzdata?source=hash-mapping + size: 143542 + timestamp: 1765719982349 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + build_number: 8 + sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809 + md5: c3efd25ac4d74b1584d2f7a57195ddf1 constrains: - - xz 5.8.2.* - license: 0BSD + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD purls: [] - size: 92242 - timestamp: 1768752982486 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.2-nompi_h21f7587_118.conda - sha256: ad260036929255d8089f748db0dce193d0d588ad7f88c06027dd9d8662cc1cc6 - md5: 5f05af73150f62adab1492ab2d18d573 + size: 6958 + timestamp: 1752805918820 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda + sha256: 997bbca0f4578ba92159a4ac6f54f5cc9f60a1e5241c66aedbf774f2db025a04 + md5: e44e84a3b731b1a27cbc75d825db9463 depends: - - __glibc >=2.17,<3.0.a0 - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - hdf4 >=4.2.15,<4.2.16.0a0 - - hdf5 >=1.14.6,<1.14.7.0a0 - - libaec >=1.1.4,<2.0a0 - - libcurl >=8.14.1,<9.0a0 - - libgcc >=14 - - libstdcxx >=14 - - libxml2 >=2.13.8,<2.14.0a0 - - libzip >=1.11.2,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 - - zlib - - zstd >=1.5.7,<1.6.0a0 + - fsspec >=2022.5.0 + - python >=3.10 + - packaging >=23.0 + - pyyaml >5.4 + - pytorch >=2.1.0 + - tqdm >=4.57.0 + - torchmetrics >0.7.0 + - typing_extensions >4.5.0 + - lightning-utilities >=0.10.0 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/pytorch-lightning?source=hash-mapping + size: 500422 + timestamp: 1769802510053 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + sha256: 8d2a8bf110cc1fc3df6904091dead158ba3e614d8402a83e51ed3a8aa93cdeb0 + md5: bc8e3267d44011051f2eb14d22fb0960 + depends: + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 844115 - timestamp: 1754055003755 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.9.2-nompi_h6054f6d_118.conda - sha256: 6145bccbc484d069938a9bad162f573c0421ccbf7a8ac9d34228d1706956af4d - md5: c92aa5e0a14b8381e7aaf78714a03ff6 + purls: + - pkg:pypi/pytz?source=hash-mapping + size: 189015 + timestamp: 1742920947249 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 + md5: 870293df500ca7e18bedefa5838a22ab depends: - - __osx >=10.13 - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - hdf4 >=4.2.15,<4.2.16.0a0 - - hdf5 >=1.14.6,<1.14.7.0a0 - - libaec >=1.1.4,<2.0a0 - - libcurl >=8.14.1,<9.0a0 - - libcxx >=19 - - libxml2 >=2.13.8,<2.14.0a0 - - libzip >=1.11.2,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 - - zlib - - zstd >=1.5.7,<1.6.0a0 + - attrs >=22.2.0 + - python >=3.10 + - rpds-py >=0.7.0 + - typing_extensions >=4.4.0 + - python license: MIT license_family: MIT - purls: [] - size: 727390 - timestamp: 1754055079231 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.2-nompi_h2d3d5cf_118.conda - sha256: e7ca7726e94ef56e96ef7e5a89b23971188b2b54e1b660ed1c200593cc0ae055 - md5: ed5b74ff627e6cb6d7ab1c3ef7e3baf8 + purls: + - pkg:pypi/referencing?source=hash-mapping + size: 51788 + timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda + sha256: 61c9a2a7b4decaa300e5076ccdea874aad77ff13a55b7fd9b95744dba060eec9 + md5: c89e6c0a10804fb87536a3980685990d + depends: + - charset-normalizer + - pillow >=9 + - python >=3.9,<4 + - rlpycairo >=0.2.0,<1 + - freetype-py >=2.3.0,<2.4 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/reportlab?source=hash-mapping + size: 1718577 + timestamp: 1771171345253 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda + sha256: 7813c38b79ae549504b2c57b3f33394cea4f2ad083f0994d2045c2e24cb538c5 + md5: c65df89a0b2e321045a9e01d1337b182 depends: - - __osx >=11.0 - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - hdf4 >=4.2.15,<4.2.16.0a0 - - hdf5 >=1.14.6,<1.14.7.0a0 - - libaec >=1.1.4,<2.0a0 - - libcurl >=8.14.1,<9.0a0 - - libcxx >=19 - - libxml2 >=2.13.8,<2.14.0a0 - - libzip >=1.11.2,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 - - zlib - - zstd >=1.5.7,<1.6.0a0 + - python >=3.10 + - certifi >=2017.4.17 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.21.1,<3 + - python + constrains: + - chardet >=3.0.2,<6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + size: 63602 + timestamp: 1766926974520 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 + md5: 36de09a8d3e5d5e6f4ee63af49e59706 + depends: + - python >=3.9 + - six license: MIT license_family: MIT - purls: [] - size: 683396 - timestamp: 1754055262589 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 - md5: b499ce4b026493a13774bcf0f4c33849 + purls: + - pkg:pypi/rfc3339-validator?source=hash-mapping + size: 10209 + timestamp: 1733600040800 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 + md5: 912a71cc01012ee38e6b90ddd561e36f depends: - - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.5,<2.0a0 - - libev >=4.33,<4.34.0a0 - - libev >=4.33,<5.0a0 - - libgcc >=14 - - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - python license: MIT license_family: MIT - purls: [] - size: 666600 - timestamp: 1756834976695 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - sha256: c48d7e1cc927aef83ff9c48ae34dd1d7495c6ccc1edc4a3a6ba6aff1624be9ac - md5: e7630cef881b1174d40f3e69a883e55f + purls: + - pkg:pypi/rfc3986-validator?source=hash-mapping + size: 7818 + timestamp: 1598024297745 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 + md5: 7234f99325263a5af6d4cd195035e8f2 depends: - - __osx >=10.13 - - c-ares >=1.34.5,<2.0a0 - - libcxx >=19 - - libev >=4.33,<4.34.0a0 - - libev >=4.33,<5.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - python >=3.9 + - lark >=1.2.2 + - python license: MIT license_family: MIT - purls: [] - size: 605680 - timestamp: 1756835898134 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - sha256: a07cb53b5ffa2d5a18afc6fd5a526a5a53dd9523fbc022148bd2f9395697c46d - md5: a4b4dd73c67df470d091312ab87bf6ae + purls: + - pkg:pypi/rfc3987-syntax?source=hash-mapping + size: 22913 + timestamp: 1752876729969 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda + sha256: b06ce84d6a10c266811a7d3adbfa1c11f13393b91cc6f8a5b468277d90be9590 + md5: 7a6289c50631d620652f5045a63eb573 depends: - - __osx >=11.0 - - c-ares >=1.34.5,<2.0a0 - - libcxx >=19 - - libev >=4.33,<4.34.0a0 - - libev >=4.33,<5.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - markdown-it-py >=2.2.0 + - pygments >=2.13.0,<3.0.0 + - python >=3.10 + - typing_extensions >=4.0.0,<5.0.0 + - python license: MIT license_family: MIT - purls: [] - size: 575454 - timestamp: 1756835746393 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 - md5: d864d34357c3b65a4b731f78c0801dc4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-only - license_family: GPL - purls: [] - size: 33731 - timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda - sha256: 3b3f19ced060013c2dd99d9d46403be6d319d4601814c772a3472fe2955612b0 - md5: 7c7927b404672409d9917d49bff5f2d6 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-or-later - purls: [] - size: 33418 - timestamp: 1734670021371 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda - sha256: 2ab918f7cc00852d70088e0b9e49fda4ef95229126cf3c52a8297686938385f2 - md5: 23d706dbe90b54059ad86ff826677f39 - depends: - - __osx >=10.13 - license: LGPL-2.1-or-later - purls: [] - size: 33742 - timestamp: 1734670081910 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda - sha256: ea8c680924d957e12270dca549620327d5e986f23c4bd5f45627167ca6ef7a3b - md5: c90c1d3bd778f5ec0d4bb4ef36cbd5b6 - depends: - - __osx >=11.0 - license: LGPL-2.1-or-later - purls: [] - size: 31099 - timestamp: 1734670168822 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - sha256: ba642353f7f41ab2d2eb6410fbe522238f0f4483bcd07df30b3222b4454ee7cd - md5: 9241a65e6e9605e4581a2a8005d7f789 + purls: + - pkg:pypi/rich?source=hash-mapping + size: 208472 + timestamp: 1771572730357 +- conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda + sha256: 8d993b1a7d869855a1f6358dcc3de08dbeda9263d8c852d44bfc3900701c1e6c + md5: cc70086eaf08be7f62fd44842c013916 depends: - - __osx >=10.13 - - libgfortran - - libgfortran5 >=14.3.0 - - llvm-openmp >=19.1.7 - constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - freetype-py >=2.3 + - pycairo >=1.20.0 + - python >=3.10 license: BSD-3-Clause license_family: BSD - purls: [] - size: 6268795 - timestamp: 1763117623665 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda - sha256: ebbbc089b70bcde87c4121a083c724330f02a690fb9d7c6cd18c30f1b12504fa - md5: a6f6d3a31bb29e48d37ce65de54e2df0 + purls: + - pkg:pypi/rlpycairo?source=hash-mapping + size: 15558 + timestamp: 1756864268077 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + sha256: 8fc024bf1a7b99fc833b131ceef4bef8c235ad61ecb95a71a6108be2ccda63e8 + md5: b70e2d44e6aa2beb69ba64206a16e4c6 depends: - - __osx >=11.0 - - libgfortran - - libgfortran5 >=14.3.0 - - llvm-openmp >=19.1.7 - constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - __osx + - pyobjc-framework-cocoa + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD - purls: [] - size: 4284132 - timestamp: 1768547079205 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda - sha256: 36ade759122cdf0f16e2a2562a19746d96cf9c863ffaa812f2f5071ebbe9c03c - md5: 5f13ffc7d30ffec87864e678df9957b4 - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 - license: zlib-acknowledgement - purls: [] - size: 317669 - timestamp: 1770691470744 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda - sha256: 75755fa305f7c944d911bf00593e283ebb83dac1e9c54dc1e016cf591e57d808 - md5: 4fc7ed44d55aaf1d72b8fbc18774b90c + purls: + - pkg:pypi/send2trash?source=hash-mapping + size: 22519 + timestamp: 1770937603551 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + sha256: 59656f6b2db07229351dfb3a859c35e57cc8e8bcbc86d4e501bff881a6f771f1 + md5: 28eb91468df04f655a57bcfbb35fc5c5 depends: - - __osx >=10.13 - - libzlib >=1.3.1,<2.0a0 - license: zlib-acknowledgement - purls: [] - size: 298943 - timestamp: 1770691469850 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda - sha256: 7a4fd29a6ee2d7f7a6e610754dfdf7410ed08f40d8d8b488a27bc0f9981d5abb - md5: 871dc88b0192ac49b6a5509932c31377 + - __linux + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + size: 24108 + timestamp: 1770937597662 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 + md5: 8e194e7b992f99a5015edbd4ebd38efd depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: zlib-acknowledgement - purls: [] - size: 288950 - timestamp: 1770691485950 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda - sha256: bbab2c3e6f650f2bd1bc84d88e6a20fefa6a401fa445bb4b97c509c1b3a89fa8 - md5: a8ac9a6342569d1714ae1b53ae2fcadb + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=hash-mapping + size: 639697 + timestamp: 1773074868565 +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 depends: - - __glibc >=2.17,<3.0.a0 - - icu >=75.1,<76.0a0 - - krb5 >=1.21.3,<1.22.0a0 - - libgcc >=14 - - openldap >=2.6.10,<2.7.0a0 - - openssl >=3.5.4,<4.0a0 - license: PostgreSQL - purls: [] - size: 2711480 - timestamp: 1764345810429 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda - sha256: abaf961d69039e1a8f377e02c1f0e48173c347c3bb0d2d99508a1efdba9430c2 - md5: 5084757a93eb76dd26cbc85a4f38b0a3 + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + size: 18455 + timestamp: 1753199211006 +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 depends: - - __osx >=10.13 - - icu >=75.1,<76.0a0 - - krb5 >=1.21.3,<1.22.0a0 - - openldap >=2.6.10,<2.7.0a0 - - openssl >=3.5.4,<4.0a0 - license: PostgreSQL - purls: [] - size: 2703473 - timestamp: 1764346703796 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.1-h944245b_2.conda - sha256: 69373dee28ad3a5baeaf96ad1d62ea3580e54405d6aca07409f1f9fa18bb6885 - md5: 0ec602b45be7781667d92fb8e5373494 + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/sniffio?source=hash-mapping + size: 15698 + timestamp: 1762941572482 +- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + sha256: 23b71ecf089967d2900126920e7f9ff18cdcef82dbff3e2f54ffa360243a17ac + md5: 18de09b20462742fe093ba39185d9bac depends: - - __osx >=11.0 - - icu >=75.1,<76.0a0 - - krb5 >=1.21.3,<1.22.0a0 - - openldap >=2.6.10,<2.7.0a0 - - openssl >=3.5.4,<4.0a0 - license: PostgreSQL - purls: [] - size: 2706308 - timestamp: 1764346615183 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda - sha256: 0ef142ac31e6fd59b4af89ac800acb6deb3fbd9cc4ccf070c03cc2c784dc7296 - md5: 07479fc04ba3ddd5d9f760ef1635cfa7 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/soupsieve?source=hash-mapping + size: 38187 + timestamp: 1769034509657 +- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 + md5: b1b505328da7a6b246787df4b5a49fbc depends: - - __glibc >=2.17,<3.0.a0 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libgcc >=14 - - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 4372578 - timestamp: 1766316228461 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda - sha256: 2058eb9748a6e29a1821fea8aeea48e87d73c83be47b0504ac03914fee944d0e - md5: f22705f9ebb3f79832d635c4c2919b15 + - asttokens + - executing + - pure_eval + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/stack-data?source=hash-mapping + size: 26988 + timestamp: 1733569565672 +- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + sha256: 1c8057e6875eba958aa8b3c1a072dc9a75d013f209c26fd8125a5ebd3abbec0c + md5: 32d866e43b25275f61566b9391ccb7b5 depends: - - __osx >=11.0 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libcxx >=19 - - libzlib >=1.3.1,<2.0a0 + - __unix + - cpython + - gmpy2 >=2.0.8 + - mpmath >=1.1.0,<1.5 + - python >=3.10 license: BSD-3-Clause license_family: BSD - purls: [] - size: 3079808 - timestamp: 1766315644973 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda - sha256: 505d62fb2a487aff594a30f6c419f8e861fb3a47e25e407dae2779ac4a585b18 - md5: 8a6b4281c176f1695ae0015f420e6aa9 + purls: + - pkg:pypi/sympy?source=hash-mapping + size: 4661767 + timestamp: 1771952371059 +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + sha256: 6b6727a13d1ca6a23de5e6686500d0669081a117736a87c8abf444d60c1e40eb + md5: 17b43cee5cc84969529d5d0b0309b2cb depends: - - __osx >=11.0 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libcxx >=19 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause + - __unix + - ptyprocess + - python >=3.10 + - tornado >=6.1.0 + - python + license: BSD-2-Clause license_family: BSD - purls: [] - size: 3131502 - timestamp: 1766315339805 -- conda: https://conda.anaconda.org/conda-forge/linux-64/librdkit-2025.09.5-h3c5c181_0.conda - sha256: 06f20f6ce9dbc77000bc3eb3e597e5b4228e1bcdbf2debab949fca3c996a45a2 - md5: 716755a81e23ba9065cb6d5dba07231a + purls: + - pkg:pypi/terminado?source=hash-mapping + size: 24749 + timestamp: 1766513766867 +- conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + sha256: 6016672e0e72c4cf23c0cf7b1986283bd86a9c17e8d319212d78d8e9ae42fdfd + md5: 9d64911b31d57ca443e9f1e36b04385f depends: - - __glibc >=2.17,<3.0.a0 - - cairo >=1.18.4,<2.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libgcc >=14 - - libstdcxx >=14 + - python >=3.9 license: BSD-3-Clause license_family: BSD - purls: [] - size: 10130647 - timestamp: 1770019736513 -- conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda - sha256: 4ac74d00766232c6dd121c578ea56e1a27baefcca016cdcd38732727ba49281e - md5: d31cc6094c2041bec7aafd6ef08f7074 + purls: + - pkg:pypi/threadpoolctl?source=hash-mapping + run_exports: {} + size: 23869 + timestamp: 1741878358548 +- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 + md5: f1acf5fdefa8300de697982bcb1761c9 depends: - - __osx >=10.13 - - cairo >=1.18.4,<2.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - python >=3.5 + - webencodings >=0.4 license: BSD-3-Clause license_family: BSD - purls: [] - size: 7473122 - timestamp: 1770018948329 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda - sha256: 60c117d66605417c59aa9888cc0cc67171edf59c763ae52bc10996866b679dc5 - md5: 1788df09e1f77cea1d40890503174d57 + purls: + - pkg:pypi/tinycss2?source=hash-mapping + size: 28285 + timestamp: 1729802975370 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + sha256: 62940c563de45790ba0f076b9f2085a842a65662268b02dd136a8e9b1eaf47a8 + md5: 72e780e9aa2d0a3295f59b1874e3768b depends: - - __osx >=11.0 - - cairo >=1.18.4,<2.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 21453 + timestamp: 1768146676791 +- conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda + sha256: d3171f8d6689db5208e36f1acab7bcaa38fe4ce0cbbd0cfa59e100813754ceab + md5: 5d6e1558a945d0333ae7dc557add7201 + depends: + - lightning-utilities >=0.8.0 + - numpy >1.20.0 + - packaging >17.1 + - python >=3.9 + - pytorch >=2.0.0 + - setuptools + - typing_extensions + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/torchmetrics?source=hash-mapping + size: 396413 + timestamp: 1756932778045 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 + md5: e5ce43272193b38c2e9037446c1d9206 + depends: + - python >=3.10 + - __unix + - python + license: MPL-2.0 and MIT + purls: + - pkg:pypi/tqdm?source=hash-mapping + size: 94132 + timestamp: 1770153424136 +- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 + md5: 019a7385be9af33791c989871317e1ed + depends: + - python >=3.9 license: BSD-3-Clause license_family: BSD - purls: [] - size: 6989824 - timestamp: 1770019047093 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 - md5: a587892d3c13b6621a6091be690dbca2 + purls: + - pkg:pypi/traitlets?source=hash-mapping + size: 110051 + timestamp: 1733367480074 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 depends: - - libgcc-ng >=12 - license: ISC + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF purls: [] - size: 205978 - timestamp: 1716828628198 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - sha256: d3975cfe60e81072666da8c76b993af018cf2e73fe55acba2b5ba0928efaccf5 - md5: 6af4b059e26492da6013e79cbcb4d069 + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 + md5: a0a4a3035667fc34f29bfbd5c190baa6 depends: - - __osx >=10.13 - license: ISC - purls: [] - size: 210249 - timestamp: 1716828641383 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - sha256: fade8223e1e1004367d7101dd17261003b60aa576df6d7802191f8972f7470b1 - md5: a7ce36e284c5faaf93c220dfc39e3abd + - python >=3.10 + - typing_extensions >=4.12.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/typing-inspection?source=hash-mapping + size: 18923 + timestamp: 1764158430324 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d depends: - - __osx >=11.0 - license: ISC - purls: [] - size: 164972 - timestamp: 1716828607917 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-h0c1763c_0.conda - sha256: 1daeb5187efcdbe3bdf2dc66f1161e09cb8dfd01618015d2106feae13cf3390d - md5: a7bda2babcbb004443cb1c0be9a8c353 + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c + md5: f6d7aa696c67756a650e91e15e88223c depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - license: blessing + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/typing-utils?source=hash-mapping + size: 15183 + timestamp: 1733331395943 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain purls: [] - size: 949843 - timestamp: 1772818873928 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - sha256: f500d1cd50cfcd288d02b8fc3c3b7ecf8de6fec7b86e57ea058def02908e4231 - md5: d553eb96758e038b04027b30fe314b2d + size: 119135 + timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 + md5: e7cb0f5745e4c5035a460248334af7eb depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: blessing - purls: [] - size: 996526 - timestamp: 1772819669038 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1b79a29_0.conda - sha256: 8d8591fbf27e3a23f80a24e582f79d4772d885f69015401f5ed88d0283d943d8 - md5: 9f9e824999ef9090e214b32eea825f6d + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/uri-template?source=hash-mapping + size: 23990 + timestamp: 1733323714454 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a + md5: 9272daa869e03efe68833e3dc7a02130 depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: blessing - purls: [] - size: 914411 - timestamp: 1772819335058 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 - md5: eecce068c7e4eddeb169591baac20ac4 + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 103172 + timestamp: 1767817860341 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + sha256: e298b508b2473c4227206800dfb14c39e4b14fd79d4636132e9e1e4244cdf4aa + md5: c3197f8c0d5b955c904616b716aca093 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 304790 - timestamp: 1745608545575 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - sha256: 00654ba9e5f73aa1f75c1f69db34a19029e970a4aeb0fa8615934d8e9c369c3c - md5: a6cb15db1c2dc4d3a5f6cf3772e09e81 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wcwidth?source=hash-mapping + size: 71550 + timestamp: 1770634638503 +- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + sha256: 21f6c8a20fe050d09bfda3fb0a9c3493936ce7d6e1b3b5f8b01319ee46d6c6f6 + md5: 6639b6b0d8b5a284f027a2003669aa65 depends: - - __osx >=10.13 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - python >=3.10 license: BSD-3-Clause license_family: BSD - purls: [] - size: 284216 - timestamp: 1745608575796 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - sha256: 8bfe837221390ffc6f111ecca24fa12d4a6325da0c8d131333d63d6c37f27e0a - md5: b68e8f66b94b44aaa8de4583d3d4cc40 + purls: + - pkg:pypi/webcolors?source=hash-mapping + size: 18987 + timestamp: 1761899393153 +- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 + md5: 2841eb5bfc75ce15e9a0054b98dcd64d depends: - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - python >=3.9 license: BSD-3-Clause license_family: BSD - purls: [] - size: 279193 - timestamp: 1745608793272 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e - md5: 1b08cd684f34175e4514474793d44bcb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 he0feb66_18 - constrains: - - libstdcxx-ng ==15.2.0=*_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 5852330 - timestamp: 1771378262446 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - sha256: 3c902ffd673cb3c6ddde624cdb80f870b6c835f8bf28384b0016e7d444dd0145 - md5: 6235adb93d064ecdf3d44faee6f468de - depends: - - libstdcxx 15.2.0 h934c35e_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 27575 - timestamp: 1771378314494 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda - sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 - md5: cd5a90476766d53e901500df9215e927 - depends: - - __glibc >=2.17,<3.0.a0 - - lerc >=4.0.0,<5.0a0 - - libdeflate >=1.25,<1.26.0a0 - - libgcc >=14 - - libjpeg-turbo >=3.1.0,<4.0a0 - - liblzma >=5.8.1,<6.0a0 - - libstdcxx >=14 - - libwebp-base >=1.6.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: HPND - purls: [] - size: 435273 - timestamp: 1762022005702 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda - sha256: e53424c34147301beae2cd9223ebf593720d94c038b3f03cacd0535e12c9668e - md5: 9d4344f94de4ab1330cdc41c40152ea6 + purls: + - pkg:pypi/webencodings?source=hash-mapping + size: 15496 + timestamp: 1733236131358 +- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 + md5: 2f1ed718fcd829c184a6d4f0f2e07409 depends: - - __osx >=10.13 - - lerc >=4.0.0,<5.0a0 - - libcxx >=19 - - libdeflate >=1.25,<1.26.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 - - liblzma >=5.8.1,<6.0a0 - - libwebp-base >=1.6.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: HPND - purls: [] - size: 404591 - timestamp: 1762022511178 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - sha256: e9248077b3fa63db94caca42c8dbc6949c6f32f94d1cafad127f9005d9b1507f - md5: e2a72ab2fa54ecb6abab2b26cde93500 + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/websocket-client?source=hash-mapping + size: 61391 + timestamp: 1759928175142 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda + sha256: 9e156ffaefb8463437144326ada4b85d1de17961b9997ac5f1cbbaf747bd8bed + md5: d0e3b2f0030cf4fca58bde71d246e94c depends: - - __osx >=11.0 - - lerc >=4.0.0,<5.0a0 - - libcxx >=19 - - libdeflate >=1.25,<1.26.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 - - liblzma >=5.8.1,<6.0a0 - - libwebp-base >=1.6.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: HPND - purls: [] - size: 373892 - timestamp: 1762022345545 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.8.0-cpu_mkl_h09b866c_102.conda - sha256: 7033538481a66ed4f59f3b88a952f55b85596429271643678fd14a4e0a9666b9 - md5: 0194f4ea9e74964548ddb220b61d4712 + - packaging >=24.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wheel?source=hash-mapping + run_exports: {} + size: 33491 + timestamp: 1776878563806 +- conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + sha256: 826af5e2c09e5e45361fa19168f46ff524e7a766022615678c3a670c45895d9a + md5: dc257b7e7cad9b79c1dfba194e92297b depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex * *_llvm - - _openmp_mutex >=4.5 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libblas * *mkl - - libcblas >=3.9.0,<4.0a0 - - libgcc >=14 - - libprotobuf >=6.31.1,<6.31.2.0a0 - - libstdcxx >=14 - - libuv >=1.51.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=21.1.4 - - mkl >=2025.3.0,<2026.0a0 - - pybind11-abi 4 - - sleef >=3.9.0,<4.0a0 - constrains: - - pytorch-cpu 2.8.0 - - pytorch-gpu <0.0a0 - - pytorch 2.8.0 cpu_mkl_*_102 + - python >=3.10 license: BSD-3-Clause license_family: BSD - purls: [] - size: 58954930 - timestamp: 1762096008648 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda - sha256: b92f9fc7777b56e02d1d39392b2dd87cb2399b46ddc52627e00b5b11ffd05869 - md5: ff483076adc20d9fc66c90e73f117443 + purls: + - pkg:pypi/widgetsnbextension?source=hash-mapping + size: 889195 + timestamp: 1762040404362 +- conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda + sha256: 5001a8b0a26810f5b8e20a05bca8cd458b0f6dcfbe00bf648aec017b1fd341f1 + md5: e91301ac8b2ad640df6165db24914332 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/xmltodict?source=hash-mapping + size: 20026 + timestamp: 1758191083015 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae + md5: 30cd29cb87d819caead4d55184c1d115 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/zipp?source=hash-mapping + size: 24194 + timestamp: 1764460141901 +- conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: 30006902a9274de8abdad5a9f02ef7c8bb3d69a503486af0c1faee30b023e5b7 + md5: eaac87c21aff3ed21ad9656697bb8326 depends: - - __osx >=11.0 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libcxx >=19 - - liblapack >=3.9.0,<4.0a0 - - libprotobuf >=6.31.1,<6.31.2.0a0 - - libuv >=1.51.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - numpy >=1.23,<3 - - pybind11-abi 4 - - python_abi 3.12.* *_cp312 - - sleef >=3.9.0,<4.0a0 - constrains: - - pytorch 2.8.0 cpu_generic_*_2 - - pytorch-cpu 2.8.0 - - pytorch-gpu <0.0a0 - - openblas * openmp_* - - libopenblas * openmp_* + - llvm-openmp >=9.0.1 license: BSD-3-Clause license_family: BSD purls: [] - size: 49162152 - timestamp: 1762106123487 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda - sha256: e895a1d1140ce945cbe11da980e5f1e6226f742efdd0fd70b635958485e79806 - md5: cca2b6c3fe58d0b289349dcab482596d + size: 8328 + timestamp: 1764092562779 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ambertools-24.8-cuda_None_nompi_py312h5c36eea_103.conda + sha256: f0027340e462a199aab73d3aa7504b766b46260692a74c151934f16140381b68 + md5: aac7e8ded12aa928b39cb3af2f770fd4 depends: - __osx >=11.0 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 + - arpack >=3.9.1,<3.10.0a0 nompi_* + - bzip2 >=1.0.8,<2.0a0 + - fftw >=3.3.11,<4.0a0 + - joblib - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 + - libboost >=1.86.0,<1.87.0a0 - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 - liblapack >=3.9.0,<4.0a0 - - libprotobuf >=6.31.1,<6.31.2.0a0 - - libuv >=1.51.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 + - libnetcdf >=4.10.0,<4.10.1.0a0 + - libzlib >=1.3.2,<2.0a0 - llvm-openmp >=19.1.7 - - numpy >=1.23,<3 - - pybind11-abi 4 - - python >=3.12,<3.13.0a0 *_cpython + - matplotlib-base + - netcdf-fortran >=4.6.3,<4.7.0a0 + - numpy >=1.25,<3 + - openssl >=3.5.7,<4.0a0 + - parmed >=4.0.0,<5.0.0a0 + - perl + - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - sleef >=3.9.0,<4.0a0 + - readline >=8.3,<9.0a0 + - scipy + - setuptools + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxt >=1.3.1,<2.0a0 constrains: - - openblas * openmp_* - - pytorch 2.8.0 cpu_generic_*_2 - - libopenblas * openmp_* - - pytorch-cpu 2.8.0 - - pytorch-gpu <0.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 29866556 - timestamp: 1762101159180 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee - md5: db409b7c1720428638e7c0d509d3e1b5 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 40311 - timestamp: 1766271528534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b - md5: 0f03292cc56bf91a077a134ea8747118 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 895108 - timestamp: 1753948278280 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - sha256: d90dd0eee6f195a5bd14edab4c5b33be3635b674b0b6c010fb942b956aa2254c - md5: fbfc6cf607ae1e1e498734e256561dc3 + - ambermini ==9999999999 + - packmol ==9999999999 + license: GPL-3.0-or-later AND LGPL-3.0-or-later AND BSD-3-Clause AND MIT + purls: + - pkg:pypi/amberutils?source=hash-mapping + - pkg:pypi/edgembar?source=hash-mapping + - pkg:pypi/mmpbsa-py?source=hash-mapping + - pkg:pypi/ndfes?source=hash-mapping + - pkg:pypi/packmol-memgen?source=hash-mapping + - pkg:pypi/pdb4amber?source=hash-mapping + - pkg:pypi/pymsmt?source=hash-mapping + - pkg:pypi/pytraj?source=hash-mapping + - pkg:pypi/sander?source=hash-mapping + run_exports: {} + size: 96622776 + timestamp: 1783526371513 +- conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py312h80b0991_2.conda + sha256: b18ea88c1a3e8c9d6a05f1aa71928856cfdcb5fd4ad0353638f4bac3f0b9b9a2 + md5: 66f6b81d4bf42e3da028763e9d873bff depends: - __osx >=10.13 + - cffi >=1.0.1 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT - purls: [] - size: 422612 - timestamp: 1753948458902 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - sha256: 042c7488ad97a5629ec0a991a8b2a3345599401ecc75ad6a5af73b60e6db9689 - md5: c0d87c3c8e075daf1daf6c31b53e8083 - depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 421195 - timestamp: 1753948426421 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b - md5: aea31d2e5b1091feca96fcfe945c3cf9 + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 33431 + timestamp: 1762509769660 +- conda: https://conda.anaconda.org/conda-forge/osx-64/arpack-3.9.1-nompi_hdfe9103_102.conda + sha256: fbb8ab189e4700bbb856abdee6f71172628004c009cc276e44ffa3fdd2599985 + md5: 4fbf1ddfe3784263c01b7c33e6a6a3f7 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - libwebp 1.6.0 + - __osx >=10.13 + - libblas >=3.9.0,<4.0a0 + - libcxx >=18 + - libgfortran >=5 + - libgfortran5 >=13.2.0 + - liblapack >=3.9.0,<4.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 429011 - timestamp: 1752159441324 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda - sha256: 00dbfe574b5d9b9b2b519acb07545380a6bc98d1f76a02695be4995d4ec91391 - md5: 7bb6608cf1f83578587297a158a6630b + size: 125644 + timestamp: 1736084029191 +- conda: https://conda.anaconda.org/conda-forge/osx-64/backports.zstd-1.3.0-py312h6917036_0.conda + sha256: 96eefe04e072e8c31fcac7d5e89c9d4a558d2565eef629cfc691a755b2fa6e59 + md5: c8b7d0fb5ff6087760dde8f5f388b135 depends: + - python - __osx >=10.13 - constrains: - - libwebp 1.6.0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 365086 - timestamp: 1752159528504 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda - sha256: a4de3f371bb7ada325e1f27a4ef7bcc81b2b6a330e46fac9c2f78ac0755ea3dd - md5: e5e7d467f80da752be17796b87fe6385 + - python_abi 3.12.* *_cp312 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 238093 + timestamp: 1767044989890 +- conda: https://conda.anaconda.org/conda-forge/osx-64/blosc-1.21.6-hd145fbb_1.conda + sha256: 876bdb1947644b4408f498ac91c61f1f4987d2c57eb47c0aba0d5ee822cd7da9 + md5: 717852102c68a082992ce13a53403f9d depends: - - __osx >=11.0 - constrains: - - libwebp 1.6.0 + - __osx >=10.13 + - libcxx >=18 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - snappy >=1.2.1,<1.3.0a0 + - zstd >=1.5.6,<1.6.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 294974 - timestamp: 1752159906788 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa - md5: 92ed62436b625154323d40d5f2f11dd7 + size: 46990 + timestamp: 1733513422834 +- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.2.0-hf139dec_1.conda + sha256: c838c71ded28ada251589f6462fc0f7c09132396799eea2701277566a1a863bf + md5: 149d8ee7d6541a02a6117d8814fd9413 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - pthread-stubs - - xorg-libxau >=1.0.11,<2.0a0 - - xorg-libxdmcp + - __osx >=10.13 + - brotli-bin 1.2.0 h8616949_1 + - libbrotlidec 1.2.0 h8616949_1 + - libbrotlienc 1.2.0 h8616949_1 license: MIT license_family: MIT purls: [] - size: 395888 - timestamp: 1727278577118 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda - sha256: 8896cd5deff6f57d102734f3e672bc17120613647288f9122bec69098e839af7 - md5: bbeca862892e2898bdb45792a61c4afc + size: 20194 + timestamp: 1764017661405 +- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.2.0-h8616949_1.conda + sha256: dcb5a2b29244b82af2545efad13dfdf8dddb86f88ce64ff415be9e7a10cc0383 + md5: 34803b20dfec7af32ba675c5ccdbedbf depends: - __osx >=10.13 - - pthread-stubs - - xorg-libxau >=1.0.11,<2.0a0 - - xorg-libxdmcp + - libbrotlidec 1.2.0 h8616949_1 + - libbrotlienc 1.2.0 h8616949_1 license: MIT license_family: MIT purls: [] - size: 323770 - timestamp: 1727278927545 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda - sha256: bd3816218924b1e43b275863e21a3e13a5db4a6da74cca8e60bc3c213eb62f71 - md5: af523aae2eca6dfa1c8eec693f5b9a79 + size: 18589 + timestamp: 1764017635544 +- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.2.0-py312h4b46afd_1.conda + sha256: 8854a80360128157e8d05eb57c1c7e7c1cb10977e4c4557a77d29c859d1f104b + md5: 01fdbccc39e0a7698e9556e8036599b7 depends: - - __osx >=11.0 - - pthread-stubs - - xorg-libxau >=1.0.11,<2.0a0 - - xorg-libxdmcp + - __osx >=10.13 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - libbrotlicommon 1.2.0 h8616949_1 license: MIT license_family: MIT - purls: [] - size: 323658 - timestamp: 1727278733917 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c - md5: 5aa797f8787fe7a17d1b0821485b5adc - depends: - - libgcc-ng >=12 - license: LGPL-2.1-or-later - purls: [] - size: 100393 - timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.9-h04c0eec_0.conda - sha256: 5d12e993894cb8e9f209e2e6bef9c90fa2b7a339a1f2ab133014b71db81f5d88 - md5: 35eeb0a2add53b1e50218ed230fa6a02 + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 389534 + timestamp: 1764017976737 +- conda: https://conda.anaconda.org/conda-forge/osx-64/bun-1.3.11-h74e7fe7_5.conda + sha256: aad9956217747e3b4aebcfeba0e820f2d78a8754f081e0c3bc69b39717c3c4dd + md5: 06e45d2f306ae4b478ec28b0ac5217e1 depends: - - __glibc >=2.17,<3.0.a0 - - icu >=75.1,<76.0a0 - - libgcc >=14 - - libiconv >=1.18,<2.0a0 - - liblzma >=5.8.1,<6.0a0 - - libzlib >=1.3.1,<2.0a0 + - libcxx >=21 + - __osx >=13.0 + - hdrhistogram-c >=0.11.9,<0.11.10.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libzlib >=1.3.2,<2.0a0 + - lol-html >=3.0.1,<3.0.2.0a0 + - libhwy >=1.4.0,<1.5.0a0 + - libarchive >=3.8.9,<3.9.0a0 + - ls-hpack >=2.3.5,<2.3.6.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - c-ares >=1.34.8,<2.0a0 + - libsqlite >=3.53.4,<4.0a0 license: MIT license_family: MIT purls: [] - size: 697033 - timestamp: 1761766011241 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.13.9-he1bc88e_0.conda - sha256: 151e653e72b9de48bdeb54ae0664b490d679d724e618649997530a582a67a5fb - md5: af41ebf4621373c4eeeda69cc703f19c + run_exports: {} + size: 18803743 + timestamp: 1785504082443 +- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + sha256: 9f242f13537ef1ce195f93f0cc162965d6cc79da578568d6d8e50f70dd025c42 + md5: 4173ac3b19ec0a4f400b4f782910368b depends: - __osx >=10.13 - - icu >=75.1,<76.0a0 - - libiconv >=1.18,<2.0a0 - - liblzma >=5.8.1,<6.0a0 - - libzlib >=1.3.1,<2.0a0 - license: MIT - license_family: MIT + license: bzip2-1.0.6 + license_family: BSD purls: [] - size: 609937 - timestamp: 1761766325697 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.13.9-h4a9ca0c_0.conda - sha256: 7ab9b3033f29ac262cd3c846887e5b512f5916c3074d10f298627d67b7a32334 - md5: 763c7e76295bf142145d5821f251b884 + size: 133427 + timestamp: 1771350680709 +- conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.8-ha3d0635_1.conda + sha256: 319144659c1f7bd01bfe8a0fb2e94ac67be75ebe65c1c6c7f580f656c14890e6 + md5: 0daa2a2fdb1246f13453642c459d0dcf depends: - __osx >=11.0 - - icu >=75.1,<76.0a0 - - libiconv >=1.18,<2.0a0 - - liblzma >=5.8.1,<6.0a0 - - libzlib >=1.3.1,<2.0a0 + constrains: + - c-ares-static <0a0 license: MIT license_family: MIT purls: [] - size: 581379 - timestamp: 1761766437117 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda - sha256: 991e7348b0f650d495fb6d8aa9f8c727bdf52dabf5853c0cc671439b160dce48 - md5: a7b27c075c9b7f459f1c022090697cba + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 205386 + timestamp: 1786116708158 +- conda: https://conda.anaconda.org/conda-forge/osx-64/c-blosc2-2.23.1-h548f922_0.conda + sha256: 4766ee318fd57b956875064a390d277755ad4dfeb5ef6cd7f592c31058cd7a79 + md5: ea2963a308e8fe5eba56405e7820db6c depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - libgcc >=13 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.3.2,<4.0a0 + - __osx >=10.13 + - libcxx >=19 + - lz4-c >=1.10.0,<1.11.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 109043 - timestamp: 1730442108429 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda - sha256: 434a4d1ad23c1c8deb7ec2da94aca05e22bc29dee445b4f7642e1c2f20fc0b0b - md5: 3cf12c97a18312c9243a895580bf5be6 + size: 288387 + timestamp: 1772621206480 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda + sha256: d4297c3a9bcff9add3c5a46c6e793b88567354828bcfdb6fc9f6b1ab34aa4913 + md5: 32403b4ef529a2018e4d8c4f2a719f16 depends: - __osx >=10.13 - - bzip2 >=1.0.8,<2.0a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - freetype >=2.12.1,<3.0a0 + - icu >=75.1,<76.0a0 + - libcxx >=18 + - libexpat >=2.6.4,<3.0a0 + - libglib >=2.82.2,<3.0a0 + - libpng >=1.6.47,<1.7.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.3.2,<4.0a0 - license: BSD-3-Clause - license_family: BSD + - pixman >=0.44.2,<1.0a0 + license: LGPL-2.1-only or MPL-1.1 purls: [] - size: 129542 - timestamp: 1730442392952 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda - sha256: 507599a77c1ce823c2d3acaefaae4ead0686f183f3980467a4c4b8ba209eff40 - md5: 7177414f275db66735a17d316b0a81d6 + size: 893252 + timestamp: 1741554808521 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312he90777b_1.conda + sha256: e2888785e50ef99c63c29fb3cfbfb44cdd50b3bb7cd5f8225155e362c391936f + md5: cf70c8244e7ceda7e00b1881ad7697a9 depends: - - __osx >=11.0 - - bzip2 >=1.0.8,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.3.2,<4.0a0 + - __osx >=10.13 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 288241 + timestamp: 1761203170357 +- conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hb0c38da_4.conda + sha256: 6c03943009b07c6deb3a64afa094b6ca694062b58127a4da6f656a13d508c340 + md5: 625f08687ba33cc9e57865e7bf8e8123 + depends: + - numpy >=1.25 + - python + - __osx >=10.13 + - libcxx >=19 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD - purls: [] - size: 125507 - timestamp: 1730442214849 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 - md5: edb0dca6bc32e4f4789199455a1dbeb8 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - constrains: - - zlib 1.3.1 *_2 - license: Zlib - license_family: Other - purls: [] - size: 60963 - timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09 - md5: 003a54a4e32b02f7355b50a837e699da + purls: + - pkg:pypi/contourpy?source=hash-mapping + size: 298198 + timestamp: 1769156053873 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cyrus-sasl-2.1.28-h610c526_0.conda + sha256: beee5d279d48d67ba39f1b8f64bc050238d3d465fb9a53098eba2a85e9286949 + md5: 314cd5e4aefc50fec5ffd80621cfb4f8 depends: - __osx >=10.13 - constrains: - - zlib 1.3.1 *_2 - license: Zlib - license_family: Other - purls: [] - size: 57133 - timestamp: 1727963183990 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b - md5: 369964e85dc26bfe78f41399b366c435 - depends: - - __osx >=11.0 - constrains: - - zlib 1.3.1 *_2 - license: Zlib - license_family: Other + - krb5 >=1.21.3,<1.22.0a0 + - libcxx >=18 + - libntlm >=1.8,<2.0a0 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause-Attribution + license_family: BSD purls: [] - size: 46438 - timestamp: 1727963202283 -- conda: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.15.3-pyhd8ed1ab_0.conda - sha256: 97dcff433510cfb92f775f6c5da4e18d19ff70d8921187dae0725158946fb3e5 - md5: 237d294ca3acd678f590f1754e090802 + size: 197689 + timestamp: 1750239254864 +- conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.20-py312h29de90a_0.conda + sha256: 310f737be38bd4a53b2c13c6387b880031fc0995a13194511cc08a48d3160462 + md5: 4e508cd9d5d630c7db0bdebb24a3be90 depends: - - packaging >=17.1 - - python >=3.10 - - setuptools - - typing_extensions - license: Apache-2.0 - license_family: APACHE + - python + - libcxx >=19 + - __osx >=10.13 + - python_abi 3.12.* *_cp312 + license: MIT + license_family: MIT purls: - - pkg:pypi/lightning-utilities?source=hash-mapping - size: 32379 - timestamp: 1772138325789 -- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.0-h4922eb0_0.conda - sha256: 543c9f17cf6ee6d7b635823fb9009df421d510c36739534df6ae43eadaf6ff4e - md5: 5e7da5333653c631d27732893b934351 + - pkg:pypi/debugpy?source=hash-mapping + size: 2764546 + timestamp: 1769744989784 +- conda: https://conda.anaconda.org/conda-forge/osx-64/fftw-3.3.11-nompi_h54214ab_100.conda + sha256: c234b8f1be5b630236675a006172edd768ca2685fbf0713ec007f3d373f5ee27 + md5: 5174eb59171c77781c90fbff9eaf5c89 depends: - - __glibc >=2.17,<3.0.a0 - constrains: - - intel-openmp <0.0a0 - - openmp 22.1.0|22.1.0.* - license: Apache-2.0 WITH LLVM-exception - license_family: APACHE + - __osx >=11.0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - llvm-openmp >=19.1.7 + license: GPL-2.0-or-later + license_family: GPL purls: [] - size: 6136884 - timestamp: 1772024545 -- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - sha256: b63df4e592b3362e7d13e3d1cf8e55ce932ff4f17611c8514b5d36368ec2094c - md5: 3921780bab286f2439ba483c22b90345 + run_exports: + weak: + - fftw >=3.3.11,<4.0a0 + size: 1810437 + timestamp: 1776783050946 +- conda: https://conda.anaconda.org/conda-forge/osx-64/fontconfig-2.17.1-h7a4440b_0.conda + sha256: a972a114e618891bb50e50d8b13f5accb0085847f3aab1cf208e4552c1ab9c24 + md5: 4646a20e8bbb54903d6b8e631ceb550d depends: - __osx >=11.0 - constrains: - - openmp 22.1.0|22.1.0.* - - intel-openmp <0.0a0 - license: Apache-2.0 WITH LLVM-exception - license_family: APACHE + - libexpat >=2.7.4,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT purls: [] - size: 311938 - timestamp: 1772024731611 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - sha256: 0daeedb3872ad0fdd6f0d7e7165c63488e8a315d7057907434145fba0c1e7b3d - md5: ff0820b5588b20be3b858552ecf8ffae + size: 237866 + timestamp: 1771382969241 +- conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.62.0-py312heb39f77_0.conda + sha256: 0e46ae1958c436e74d05a3295fde62894ffbd0ab37149965924e4aad1fa089df + md5: 62be3aaedc2839c381b94014db9aee25 depends: - __osx >=11.0 - constrains: - - openmp 22.1.0|22.1.0.* - - intel-openmp <0.0a0 - license: Apache-2.0 WITH LLVM-exception - license_family: APACHE - purls: [] - size: 285558 - timestamp: 1772028716784 -- pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - name: loguru - version: 0.7.3 - sha256: 31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c - requires_dist: - - colorama>=0.3.4 ; sys_platform == 'win32' - - aiocontextvars>=0.2.0 ; python_full_version < '3.7' - - win32-setctime>=1.0.0 ; sys_platform == 'win32' - - pre-commit==4.0.1 ; python_full_version >= '3.9' and extra == 'dev' - - tox==3.27.1 ; python_full_version < '3.8' and extra == 'dev' - - tox==4.23.2 ; python_full_version >= '3.8' and extra == 'dev' - - pytest==6.1.2 ; python_full_version < '3.8' and extra == 'dev' - - pytest==8.3.2 ; python_full_version >= '3.8' and extra == 'dev' - - pytest-cov==2.12.1 ; python_full_version < '3.8' and extra == 'dev' - - pytest-cov==5.0.0 ; python_full_version == '3.8.*' and extra == 'dev' - - pytest-cov==6.0.0 ; python_full_version >= '3.9' and extra == 'dev' - - pytest-mypy-plugins==1.9.3 ; python_full_version >= '3.6' and python_full_version < '3.8' and extra == 'dev' - - pytest-mypy-plugins==3.1.0 ; python_full_version >= '3.8' and extra == 'dev' - - colorama==0.4.5 ; python_full_version < '3.8' and extra == 'dev' - - colorama==0.4.6 ; python_full_version >= '3.8' and extra == 'dev' - - freezegun==1.1.0 ; python_full_version < '3.8' and extra == 'dev' - - freezegun==1.5.0 ; python_full_version >= '3.8' and extra == 'dev' - - exceptiongroup==1.1.3 ; python_full_version >= '3.7' and python_full_version < '3.11' and extra == 'dev' - - mypy==0.910 ; python_full_version < '3.6' and extra == 'dev' - - mypy==0.971 ; python_full_version == '3.6.*' and extra == 'dev' - - mypy==1.4.1 ; python_full_version == '3.7.*' and extra == 'dev' - - mypy==1.13.0 ; python_full_version >= '3.8' and extra == 'dev' - - sphinx==8.1.3 ; python_full_version >= '3.11' and extra == 'dev' - - sphinx-rtd-theme==3.0.2 ; python_full_version >= '3.11' and extra == 'dev' - - myst-parser==4.0.0 ; python_full_version >= '3.11' and extra == 'dev' - - build==1.2.2 ; python_full_version >= '3.11' and extra == 'dev' - - twine==6.0.1 ; python_full_version >= '3.11' and extra == 'dev' - requires_python: '>=3.5,<4.0' -- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 - md5: 9de5350a85c4a20c685259b889aa6393 + - brotli + - munkres + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - unicodedata2 >=15.1.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/fonttools?source=hash-mapping + size: 2941308 + timestamp: 1773150230279 +- conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.2-h694c41f_0.conda + sha256: f71ca0e10a15ae2325d2957239c2273551cc2d21a7ab9501114a85a07f149aaa + md5: 0959d7034baef0a8274387fde505c347 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - license: BSD-2-Clause - license_family: BSD + - libfreetype 2.14.2 h694c41f_0 + - libfreetype6 2.14.2 h58fbd8d_0 + license: GPL-2.0-only OR FTL purls: [] - size: 167055 - timestamp: 1733741040117 -- conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda - sha256: 8da3c9d4b596e481750440c0250a7e18521e7f69a47e1c8415d568c847c08a1c - md5: d6b9bd7e356abd7e3a633d59b753495a + size: 174265 + timestamp: 1772756344793 +- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda + sha256: 75aa5e7a875afdcf4903b7dc98577672a3dc17b528ac217b915f9528f93c85fc + md5: 427101d13f19c4974552a4e5b072eef1 depends: - __osx >=10.13 - - libcxx >=18 - license: BSD-2-Clause - license_family: BSD + - libcxx >=16 + license: GPL-2.0-or-later OR LGPL-3.0-or-later purls: [] - size: 159500 - timestamp: 1733741074747 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - sha256: 94d3e2a485dab8bdfdd4837880bde3dd0d701e2b97d6134b8806b7c8e69c8652 - md5: 01511afc6cc1909c5303cf31be17b44f + size: 428919 + timestamp: 1718981041839 +- conda: https://conda.anaconda.org/conda-forge/osx-64/gmpy2-2.3.0-py312he58dab6_0.conda + sha256: b6e68690f17c78f5ea070d89675c5e2b119159e5e7a4a0f98a432d1450844776 + md5: c1301858e6d7ea06bc310982c1f670b9 depends: - __osx >=11.0 - - libcxx >=18 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 148824 - timestamp: 1733741047892 -- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - sha256: 7b1da4b5c40385791dbc3cc85ceea9fad5da680a27d5d3cb8bfaa185e304a89e - md5: 5b5203189eb668f042ac2b0826244964 + - gmp >=6.3.0,<7.0a0 + - mpc >=1.3.1,<2.0a0 + - mpfr >=4.2.1,<5.0a0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/gmpy2?source=hash-mapping + size: 200623 + timestamp: 1773103275478 +- conda: https://conda.anaconda.org/conda-forge/osx-64/greenlet-3.3.2-py312h4075484_0.conda + sha256: db9f45ec272e76f66ff845351656a60ce0e750cab33e4b67fcea0e86ae6736bd + md5: f7560a95e10c7547ddd4cccb00847fff depends: - - mdurl >=0.1,<1 - - python >=3.10 + - python + - libcxx >=19 + - __osx >=11.0 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/markdown-it-py?source=hash-mapping - size: 64736 - timestamp: 1754951288511 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda - sha256: 5f3aad1f3a685ed0b591faad335957dbdb1b73abfd6fc731a0d42718e0653b33 - md5: 93a4752d42b12943a355b682ee43285b + - pkg:pypi/greenlet?source=hash-mapping + size: 249071 + timestamp: 1771658485183 +- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf4-4.2.15-h8138101_7.conda + sha256: 8c767cc71226e9eb62649c903c68ba73c5f5e7e3696ec0319d1f90586cebec7d + md5: 7ce543bf38dbfae0de9af112ee178af2 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - constrains: - - jinja2 >=3.0.0 + - libcxx >=15.0.7 + - libjpeg-turbo >=3.0.0,<4.0a0 + - libzlib >=1.2.13,<2.0.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/markupsafe?source=compressed-mapping - size: 26057 - timestamp: 1772445297924 -- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda - sha256: 0eb418d4776a1a54c1869b11a5c4ae096ef9a46c8d7e481e32fa814561c5cfed - md5: d596f9d03043acd4ec711c844060da59 + purls: [] + size: 724103 + timestamp: 1695661907511 +- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hf563b80_106.conda + sha256: 4bcc7d54a011f1d515da2fb3406659574bae5f284bced126c756ed9ef151459f + md5: b74e900265ad3808337cd542cfad6733 depends: - - __osx >=11.0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - constrains: - - jinja2 >=3.0.0 + - __osx >=10.13 + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.18.0,<9.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/markupsafe?source=compressed-mapping - size: 25095 - timestamp: 1772445399364 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda - sha256: 330394fb9140995b29ae215a19fad46fcc6691bdd1b7654513d55a19aaa091c1 - md5: 11d95ab83ef0a82cc2de12c1e0b47fe4 + purls: [] + size: 3526365 + timestamp: 1770391694712 +- conda: https://conda.anaconda.org/conda-forge/osx-64/hdrhistogram-c-0.11.9-h07817ec_1.conda + sha256: ee1d9706561410563295db10ed8406eec9a45f93a9789cc65fc98607b95b4eaa + md5: 9de3dd73b3c1355a94b78ed1b62509ab + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-2-Clause OR CC0-1.0 + purls: [] + run_exports: + weak: + - hdrhistogram-c >=0.11.9,<0.11.10.0a0 + size: 38394 + timestamp: 1769816319912 +- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda + sha256: 2e64307532f482a0929412976c8450c719d558ba20c0962832132fd0d07ba7a7 + md5: d68d48a3060eb5abdc1cdc8e2a3a5966 depends: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 11761697 + timestamp: 1720853679409 +- conda: https://conda.anaconda.org/conda-forge/osx-64/khronos-opencl-icd-loader-2024.10.24-h6e16a3a_1.conda + sha256: 1cd9ca9d99acbe449df1590325cbae1e97d0a8a6979ea91ee61fca863b3a5764 + md5: 0c458c33cd3b609c6a64180af46201c9 + depends: + - __osx >=10.13 + - opencl-headers >=2024.10.24 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 34262 + timestamp: 1732916591723 +- conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.5.0-py312hb1dc2e7_0.conda + sha256: 6ab69d441b3400cdf773f67e20f9fae7c37f076d32c31d06843f12d2099e70ce + md5: 4a38b6e74b5a7ea22f1840226e5103a8 + depends: + - python + - libcxx >=19 - __osx >=11.0 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - constrains: - - jinja2 >=3.0.0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping - size: 25564 - timestamp: 1772445846939 -- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py312he3d6523_0.conda - sha256: 70cf0e7bfd50ef50eb712a6ca1eef0ef0d63b7884292acc81353327b434b548c - md5: b8dc157bbbb69c1407478feede8b7b42 + - pkg:pypi/kiwisolver?source=hash-mapping + size: 69432 + timestamp: 1773067281295 +- conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda + sha256: 83b52685a4ce542772f0892a0f05764ac69d57187975579a0835ff255ae3ef9c + md5: d4765c524b1d91567886bde656fb514b depends: - - __glibc >=2.17,<3.0.a0 - - contourpy >=1.0.1 - - cycler >=0.10 - - fonttools >=4.22.0 - - freetype - - kiwisolver >=1.3.1 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libgcc >=14 - - libstdcxx >=14 - - numpy >=1.23 - - numpy >=1.23,<3 - - packaging >=20.0 - - pillow >=8 - - pyparsing >=2.3.1 - - python >=3.12,<3.13.0a0 - - python-dateutil >=2.7 - - python_abi 3.12.* *_cp312 - - qhull >=2020.2,<2020.3.0a0 - - tk >=8.6.13,<8.7.0a0 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/matplotlib?source=hash-mapping - size: 8442149 - timestamp: 1763055517581 -- conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda - sha256: 2ce31cad23d5d5fc16ca9d25f47dcfc52e93f2a0c6e1dc6db28e583c42f88bdc - md5: 853618b60fdd11a6c3dbaadaa413407c + - __osx >=10.13 + - libcxx >=16 + - libedit >=3.1.20191231,<3.2.0a0 + - libedit >=3.1.20191231,<4.0a0 + - openssl >=3.3.1,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1185323 + timestamp: 1719463492984 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.18-h90db99b_0.conda + sha256: 3ec16c491425999a8461e1b7c98558060a4645a20cf4c9ac966103c724008cc2 + md5: 753acc10c7277f953f168890e5397c80 depends: - __osx >=10.13 - - contourpy >=1.0.1 - - cycler >=0.10 - - fonttools >=4.22.0 - - freetype - - kiwisolver >=1.3.1 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: MIT + license_family: MIT + purls: [] + size: 226870 + timestamp: 1768184917403 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.1.0-h35c7297_0.conda + sha256: f918716c71c8bebbc0c40e1050878aa512fea92c1d17c363ca35650bc60f6c35 + md5: d2fe7e177d1c97c985140bd54e2a5e33 + depends: + - __osx >=11.0 - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - numpy >=1.23 - - numpy >=1.23,<3 - - packaging >=20.0 - - pillow >=8 - - pyparsing >=2.3.1 - - python >=3.12,<3.13.0a0 - - python-dateutil >=2.7 - - python_abi 3.12.* *_cp312 - - qhull >=2020.2,<2020.3.0a0 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/matplotlib?source=hash-mapping - size: 8295843 - timestamp: 1763055621386 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda - sha256: 3c96c85dd723a4c16fce4446d1f0dc7d64e46b6ae4629c66d65984b8593ee999 - md5: fbc4f90b3d63ea4e6c30f7733a0b5bfd + license: Apache-2.0 + license_family: Apache + purls: [] + size: 215089 + timestamp: 1773114468701 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + sha256: a878efebf62f039a1f1733c1e150a75a99c7029ece24e34efdf23d56256585b1 + md5: ddf1acaed2276c7eb9d3c76b49699a11 + depends: + - __osx >=10.13 + - libcxx >=18 + constrains: + - abseil-cpp =20250512.1 + - libabseil-static =20250512.1=cxx17* + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1162435 + timestamp: 1750194293086 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.5-he7c3a48_0.conda + sha256: b42ac9c684c730cb97cb3931a0a97aaf791da38bace4f6944eca10de609e5946 + md5: 975f98248cde8d54884c6d1eb5184e13 depends: - - __osx >=11.0 - - contourpy >=1.0.1 - - cycler >=0.10 - - fonttools >=4.22.0 - - freetype - - kiwisolver >=1.3.1 + - __osx >=10.13 - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - numpy >=1.23 - - numpy >=1.23,<3 - - packaging >=20.0 - - pillow >=8 - - pyparsing >=2.3.1 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python-dateutil >=2.7 - - python_abi 3.12.* *_cp312 - - qhull >=2020.2,<2020.3.0a0 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/matplotlib?source=hash-mapping - size: 8243636 - timestamp: 1763060482877 -- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 - md5: 00e120ce3e40bad7bfc78861ce3c4a25 - depends: - - python >=3.10 - - traitlets - license: BSD-3-Clause + license: BSD-2-Clause license_family: BSD - purls: - - pkg:pypi/matplotlib-inline?source=hash-mapping - size: 15175 - timestamp: 1761214578417 -- conda: https://conda.anaconda.org/conda-forge/noarch/mda-xdrlib-0.2.0-pyhd8ed1ab_1.conda - sha256: d35c53a76385e92ca2534af47372dc4116ab7be7c4f769e00c0876561d951395 - md5: 1d086e68846dadf85ee208c9459bc8bc + purls: [] + size: 30555 + timestamp: 1769222189944 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libarchive-3.8.9-gpl_h2bf6321_100.conda + sha256: d37f7df3d61bedc18947a9dfd6fbe1aa5fbb7fd47c52578393bf1a5637f88fff + md5: a3b6318303f1707e52706f3420209d26 depends: - - python >=3.9 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/mda-xdrlib?source=hash-mapping - size: 16535 - timestamp: 1734100847370 -- pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl - name: mdanalysis - version: 2.10.0 - sha256: 100a3beb970ff296eade14d491533997eea8e6cffce067f453cdc41df9a7eb78 - requires_dist: - - numpy>=1.26.0 - - griddataformats>=0.4.0 - - mmtf-python>=1.0.0 - - joblib>=0.12 - - scipy>=1.5.0 - - matplotlib>=1.5.1 - - tqdm>=4.43.0 - - threadpoolctl - - packaging - - filelock - - mda-xdrlib - - netcdf4>=1.0 ; extra == 'extra-formats' - - h5py>=2.10 ; extra == 'extra-formats' - - chemfiles>=0.10 ; extra == 'extra-formats' - - parmed ; extra == 'extra-formats' - - pyedr>=0.7.0 ; extra == 'extra-formats' - - pytng>=0.2.3 ; extra == 'extra-formats' - - gsd>3.0.0 ; extra == 'extra-formats' - - rdkit>=2022.9.1 ; extra == 'extra-formats' - - imdclient>=0.2.2 ; extra == 'extra-formats' - - biopython>=1.80 ; extra == 'analysis' - - seaborn ; extra == 'analysis' - - scikit-learn ; extra == 'analysis' - - tidynamics>=1.0.0 ; extra == 'analysis' - - networkx>=2.0 ; extra == 'analysis' - - waterdynamics ; extra == 'analysis' - - pathsimanalysis ; extra == 'analysis' - - mdahole2 ; extra == 'analysis' - - sphinx ; extra == 'doc' - - sphinx-sitemap ; extra == 'doc' - - mdanalysis-sphinx-theme>=1.3.0 ; extra == 'doc' - - sphinxcontrib-bibtex ; extra == 'doc' - - pybtex ; extra == 'doc' - - pybtex-docutils ; extra == 'doc' - - dask ; extra == 'parallel' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: mdanalysis - version: 2.10.0 - sha256: c1403b319f5b9054d3135984948ecd485702d3960a867c491283f836451dc2bd - requires_dist: - - numpy>=1.26.0 - - griddataformats>=0.4.0 - - mmtf-python>=1.0.0 - - joblib>=0.12 - - scipy>=1.5.0 - - matplotlib>=1.5.1 - - tqdm>=4.43.0 - - threadpoolctl - - packaging - - filelock - - mda-xdrlib - - netcdf4>=1.0 ; extra == 'extra-formats' - - h5py>=2.10 ; extra == 'extra-formats' - - chemfiles>=0.10 ; extra == 'extra-formats' - - parmed ; extra == 'extra-formats' - - pyedr>=0.7.0 ; extra == 'extra-formats' - - pytng>=0.2.3 ; extra == 'extra-formats' - - gsd>3.0.0 ; extra == 'extra-formats' - - rdkit>=2022.9.1 ; extra == 'extra-formats' - - imdclient>=0.2.2 ; extra == 'extra-formats' - - biopython>=1.80 ; extra == 'analysis' - - seaborn ; extra == 'analysis' - - scikit-learn ; extra == 'analysis' - - tidynamics>=1.0.0 ; extra == 'analysis' - - networkx>=2.0 ; extra == 'analysis' - - waterdynamics ; extra == 'analysis' - - pathsimanalysis ; extra == 'analysis' - - mdahole2 ; extra == 'analysis' - - sphinx ; extra == 'doc' - - sphinx-sitemap ; extra == 'doc' - - mdanalysis-sphinx-theme>=1.3.0 ; extra == 'doc' - - sphinxcontrib-bibtex ; extra == 'doc' - - pybtex ; extra == 'doc' - - pybtex-docutils ; extra == 'doc' - - dask ; extra == 'parallel' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl - name: mdanalysis - version: 2.10.0 - sha256: 97ef2e20916516db9553e7e5ff509d8be5fbb21f9bac87d74cc634e99508d3d1 - requires_dist: - - numpy>=1.26.0 - - griddataformats>=0.4.0 - - mmtf-python>=1.0.0 - - joblib>=0.12 - - scipy>=1.5.0 - - matplotlib>=1.5.1 - - tqdm>=4.43.0 - - threadpoolctl - - packaging - - filelock - - mda-xdrlib - - netcdf4>=1.0 ; extra == 'extra-formats' - - h5py>=2.10 ; extra == 'extra-formats' - - chemfiles>=0.10 ; extra == 'extra-formats' - - parmed ; extra == 'extra-formats' - - pyedr>=0.7.0 ; extra == 'extra-formats' - - pytng>=0.2.3 ; extra == 'extra-formats' - - gsd>3.0.0 ; extra == 'extra-formats' - - rdkit>=2022.9.1 ; extra == 'extra-formats' - - imdclient>=0.2.2 ; extra == 'extra-formats' - - biopython>=1.80 ; extra == 'analysis' - - seaborn ; extra == 'analysis' - - scikit-learn ; extra == 'analysis' - - tidynamics>=1.0.0 ; extra == 'analysis' - - networkx>=2.0 ; extra == 'analysis' - - waterdynamics ; extra == 'analysis' - - pathsimanalysis ; extra == 'analysis' - - mdahole2 ; extra == 'analysis' - - sphinx ; extra == 'doc' - - sphinx-sitemap ; extra == 'doc' - - mdanalysis-sphinx-theme>=1.3.0 ; extra == 'doc' - - sphinxcontrib-bibtex ; extra == 'doc' - - pybtex ; extra == 'doc' - - pybtex-docutils ; extra == 'doc' - - dask ; extra == 'parallel' - requires_python: '>=3.11' -- pypi: ./ - name: mdfactory - version: 0.1.0 - sha256: af8931e0eb980b726a9ea87c3ac824699c1e83493882853bb4fff006fed99425 - requires_dist: - - cyclopts - - loguru - - matplotlib - - mdanalysis>=2.0 - - networkx - - numpy - - pandas - - platformdirs - - pyarrow - - pydantic>=2.0 - - pyyaml - - questionary - - scipy - - bump-my-version ; extra == 'dev' - - hatch ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pyinstrument ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-mock ; extra == 'dev' - - ruff ; extra == 'dev' - - tqdm ; extra == 'dev' - - foundry-dev-tools ; extra == 'foundry' - - submitit>=1.5.1 ; extra == 'submitit' - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/linux-64/mdtraj-1.11.1-np2py312h8baca0b_1.conda - sha256: 66f32f08a8769482f1fae72051dccd74beaf4916cc33bffc9ad5b13db2d49066 - md5: a4c60c0060137d103145b33cdd8001c2 + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 769415 + timestamp: 1785251058988 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + build_number: 5 + sha256: 4754de83feafa6c0b41385f8dab1b13f13476232e16f524564a340871a9fc3bc + md5: 36d2e68a156692cbae776b75d6ca6eae depends: - - python - - setuptools - - packaging - - numpy >=1.25.0 - - scipy - - pandas <3 - - pyparsing - - pytables - - snappy - - zlib - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - numpy >=1.23,<3 - - python_abi 3.12.* *_cp312 - - libzlib >=1.3.1,<2.0a0 + - libopenblas >=0.3.30,<0.3.31.0a0 + - libopenblas >=0.3.30,<1.0a0 constrains: - - gsd !=3.0.0 - license: LGPL-2.1-or-later - purls: - - pkg:pypi/mdtraj?source=hash-mapping - size: 1534947 - timestamp: 1769108693196 -- conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda - sha256: 8523de7f24927a42f6f4b6fdb4f31ae2e3dc9835db8e29f467acda05d0952830 - md5: 4a75dbb7cc27d229f97e9a1525384ea1 + - liblapack 3.11.0 5*_openblas + - blas 2.305 openblas + - libcblas 3.11.0 5*_openblas + - mkl <2026 + - liblapacke 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18476 + timestamp: 1765819054657 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-1.86.0-hb2bbd1d_4.conda + sha256: 6d80e4e92fcf8a6bbd8424b43e6ec5b66d8b334e2bdbf2a8ef3f89bfc24c1bb3 + md5: 84262b75468880fe2fa6858ea0ce8e0e depends: - - python - - setuptools - - packaging - - numpy >=1.25.0 - - scipy - - pandas <3 - - pyparsing - - pytables - - snappy - - zlib - - libcxx >=19 - __osx >=10.13 - - numpy >=1.23,<3 + - bzip2 >=1.0.8,<2.0a0 + - icu >=75.1,<76.0a0 + - libcxx >=19 + - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 - - python_abi 3.12.* *_cp312 + - zstd >=1.5.7,<1.6.0a0 constrains: - - gsd !=3.0.0 - license: LGPL-2.1-or-later - purls: - - pkg:pypi/mdtraj?source=hash-mapping - size: 1261738 - timestamp: 1769108753148 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda - sha256: bd36d146eb12964b89ebe3fb8b93ea35631e16d44b35ce061a551e44e7ca4030 - md5: 9e9a9796f7ec84ab4aeac2c5dd92c933 + - boost-cpp <0.0a0 + license: BSL-1.0 + purls: [] + size: 2122047 + timestamp: 1756549807422 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libboost-python-1.86.0-py312h3a26c12_5.conda + sha256: 861bb5393b15e98bfac3b1f02237e7a8c3da24e3e584cff5ba08c12cf76cbc9c + md5: 84650ec666b21b2a0bf4218f0b94d1e8 depends: - - python - - setuptools - - packaging - - numpy >=1.25.0 - - scipy - - pandas <3 - - pyparsing - - pytables - - snappy - - zlib - - __osx >=11.0 + - __osx >=10.13 - libcxx >=19 - - python 3.12.* *_cpython - - libzlib >=1.3.1,<2.0a0 - numpy >=1.23,<3 + - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 constrains: - - gsd !=3.0.0 - license: LGPL-2.1-or-later - purls: - - pkg:pypi/mdtraj?source=hash-mapping - size: 1224239 - timestamp: 1769108736695 -- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 - md5: 592132998493b3ff25fd7479396e8351 + - boost <0.0a0 + - py-boost <0.0a0 + license: BSL-1.0 + purls: [] + size: 105794 + timestamp: 1766350642066 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda + sha256: 4c19b211b3095f541426d5a9abac63e96a5045e509b3d11d4f9482de53efe43b + md5: f157c098841474579569c85a60ece586 depends: - - python >=3.9 + - __osx >=10.13 license: MIT license_family: MIT - purls: - - pkg:pypi/mdurl?source=hash-mapping - size: 14465 - timestamp: 1733255681319 -- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - sha256: d3fb4beb5e0a52b6cc33852c558e077e1bfe44df1159eb98332d69a264b14bae - md5: b11e360fc4de2b0035fc8aaa74f17fd6 + purls: [] + size: 78854 + timestamp: 1764017554982 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda + sha256: 729158be90ae655a4e0427fe4079767734af1f9b69ff58cf94ca6e8d4b3eb4b7 + md5: 63186ac7a8a24b3528b4b14f21c03f54 depends: - - python >=3.10 - - typing_extensions - - python + - __osx >=10.13 + - libbrotlicommon 1.2.0 h8616949_1 + license: MIT + license_family: MIT + purls: [] + size: 30835 + timestamp: 1764017584474 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda + sha256: 8ece7b41b6548d6601ac2c2cd605cf2261268fc4443227cc284477ed23fbd401 + md5: 12a58fd3fc285ce20cf20edf21a0ff8f + depends: + - __osx >=10.13 + - libbrotlicommon 1.2.0 h8616949_1 + license: MIT + license_family: MIT + purls: [] + size: 310355 + timestamp: 1764017609985 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda + build_number: 5 + sha256: 8077c29ea720bd152be6e6859a3765228cde51301fe62a3b3f505b377c2cb48c + md5: b31d771cbccff686e01a687708a7ca41 + depends: + - libblas 3.11.0 5_he492b99_openblas + constrains: + - liblapack 3.11.0 5*_openblas + - blas 2.305 openblas + - liblapacke 3.11.0 5*_openblas license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/mistune?source=hash-mapping - size: 74250 - timestamp: 1766504456031 -- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda - sha256: 659d79976f06d2b796a0836414573a737a0856b05facfa77e5cc114081a8b3d4 - md5: f121ddfc96e6a93a26d85906adf06208 + purls: [] + size: 18484 + timestamp: 1765819073006 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.18.0-h9348e2b_0.conda + sha256: 1a0af3b7929af3c5893ebf50161978f54ae0256abb9532d4efba2735a0688325 + md5: de1910529f64ba4a9ac9005e0be78601 depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex * *_llvm - - _openmp_mutex >=4.5 - - libgcc >=14 - - libstdcxx >=14 - - llvm-openmp >=21.1.8 - - tbb >=2022.3.0 - license: LicenseRef-IntelSimplifiedSoftwareOct2022 - license_family: Proprietary + - __osx >=10.13 + - krb5 >=1.21.3,<1.22.0a0 + - libnghttp2 >=1.67.0,<2.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.4,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT purls: [] - size: 125728406 - timestamp: 1767634121080 -- pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl - name: mmtf-python - version: 1.1.3 - sha256: 502031c509a8a6d73e042781abbd88b84c1afffe65097eb0c1b70f329ffd1e6e - requires_dist: - - msgpack>=1.0.0 - - check-manifest ; extra == 'dev' - - coverage ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl - name: more-itertools - version: 10.8.0 - sha256: 52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - sha256: 1bf794ddf2c8b3a3e14ae182577c624fa92dea975537accff4bc7e5fea085212 - md5: aa14b9a5196a6d8dd364164b7ce56acf + size: 419089 + timestamp: 1767822218800 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + sha256: fa002b43752fe5860e588435525195324fe250287105ebd472ac138e97de45e6 + md5: 836389b6b9ae58f3fbcf7cafebd5c7f2 depends: - - __glibc >=2.17,<3.0.a0 - - gmp >=6.3.0,<7.0a0 - - libgcc >=13 - - mpfr >=4.2.1,<5.0a0 - license: LGPL-3.0-or-later - license_family: LGPL + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache purls: [] - size: 116777 - timestamp: 1725629179524 -- conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda - sha256: dcf91571da6c2f0db96d43a1b639047def05a0e1b6436d42c9129ab14af47b10 - md5: 0520855aaae268ea413d6bc913f1384c + size: 570141 + timestamp: 1772001147762 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.25-h517ebb2_0.conda + sha256: 025f8b1e85dd8254e0ca65f011919fb1753070eb507f03bca317871a884d24de + md5: 31aa65919a729dc48180893f62c25221 depends: - __osx >=10.13 - - gmp >=6.3.0,<7.0a0 - - mpfr >=4.2.1,<5.0a0 - license: LGPL-3.0-or-later - license_family: LGPL + license: MIT + license_family: MIT purls: [] - size: 107774 - timestamp: 1725629348601 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda - sha256: 2700899ad03302a1751dbf2bca135407e470dd83ac897ab91dd8675d4300f158 - md5: a5635df796b71f6ca400fc7026f50701 + size: 70840 + timestamp: 1761980008502 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + sha256: 6cc49785940a99e6a6b8c6edbb15f44c2dd6c789d9c283e5ee7bdfedd50b4cd6 + md5: 1f4ed31220402fcddc083b4bff406868 depends: - - __osx >=11.0 - - gmp >=6.3.0,<7.0a0 - - mpfr >=4.2.1,<5.0a0 - license: LGPL-3.0-or-later - license_family: LGPL + - ncurses + - __osx >=10.13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD purls: [] - size: 104766 - timestamp: 1725629165420 -- conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - sha256: f25d2474dd557ca66c6231c8f5ace5af312efde1ba8290a6ea5e1732a4e669c0 - md5: 2eeb50cab6652538eee8fc0bc3340c81 + size: 115563 + timestamp: 1738479554273 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda + sha256: 0d238488564a7992942aa165ff994eca540f687753b4f0998b29b4e4d030ff43 + md5: 899db79329439820b7e8f8de41bca902 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 106663 + timestamp: 1702146352558 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda + sha256: 8d9d79b2de7d6f335692391f5281607221bf5d040e6724dad4c4d77cd603ce43 + md5: a684eb8a19b2aa68fde0267df172a1e3 depends: - - __glibc >=2.17,<3.0.a0 - - gmp >=6.3.0,<7.0a0 - - libgcc >=13 - license: LGPL-3.0-only - license_family: LGPL + - __osx >=10.13 + constrains: + - expat 2.7.4.* + license: MIT + license_family: MIT purls: [] - size: 634751 - timestamp: 1725746740014 -- conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - sha256: dddb6721dff05b8dfb654c532725330231fcb81ff1e27d885ee0cdcc9fccf1c4 - md5: d511e58aaaabfc23136880d9956fa7a6 + size: 74578 + timestamp: 1771260142624 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + sha256: 951958d1792238006fdc6fce7f71f1b559534743b26cc1333497d46e5903a2d6 + md5: 66a0dc7464927d0853b590b6f53ba3ea depends: - __osx >=10.13 - - gmp >=6.3.0,<7.0a0 - license: LGPL-3.0-only - license_family: LGPL + license: MIT + license_family: MIT purls: [] - size: 373396 - timestamp: 1725746891597 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda - sha256: 4463e4e2aba7668e37a1b8532859191b4477a6f3602a5d6b4d64ad4c4baaeac5 - md5: 4e4ea852d54cc2b869842de5044662fb + size: 53583 + timestamp: 1769456300951 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.2-h694c41f_0.conda + sha256: d24d0a404a2e999c1b7bac519fedf1f36acf6c76e77d74ddd9ed809f104b4a8c + md5: bf29ee73174c610d7cad0b081b500df7 depends: - - __osx >=11.0 - - gmp >=6.3.0,<7.0a0 - license: LGPL-3.0-only - license_family: LGPL + - libfreetype6 >=2.14.2 + license: GPL-2.0-only OR FTL purls: [] - size: 345517 - timestamp: 1725746730583 -- conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.0-pyhd8ed1ab_0.conda - sha256: 0f6f76159c40af2c7ae483934513e27a9e4137364ec717c1d69511dd4bbc6a68 - md5: 74149a38e723261f9f61bddd69e90022 + size: 8085 + timestamp: 1772756328684 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.2-h58fbd8d_0.conda + sha256: 75dcab3b5c2c1fe3b2d5a4b97230bc04d9c11151739d9644ec9fa2728886cc1d + md5: 2e6760656fde7df787fdef045d0fc65d depends: - - python >=3.10 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/mpmath?source=compressed-mapping - size: 464419 - timestamp: 1771870721583 -- pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl - name: mrcfile - version: 1.5.4 - sha256: 195370a13db5ce19600499f9bdf90fd9888979bf39de19a17c19024436c6f4c2 - requires_dist: - - numpy>=1.16.0 -- pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl - name: msgpack - version: 1.1.2 - sha256: 446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: msgpack - version: 1.1.2 - sha256: 372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl - name: msgpack - version: 1.1.2 - sha256: 70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - sha256: d09c47c2cf456de5c09fa66d2c3c5035aa1fa228a1983a433c47b876aa16ce90 - md5: 37293a85a0f4f77bbd9cf7aaefc62609 + - __osx >=11.0 + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - freetype >=2.14.2 + license: GPL-2.0-only OR FTL + purls: [] + size: 364817 + timestamp: 1772756327104 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + sha256: 83366f11615ab234aa1e0797393f9e07b78124b5a24c4a9f8af0113d02df818e + md5: 9a5cb96e43f5c2296690186e15b3296f depends: - - python >=3.9 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/munkres?source=hash-mapping - size: 15851 - timestamp: 1749895533014 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - sha256: 1b66960ee06874ddceeebe375d5f17fb5f393d025a09e15b830ad0c4fffb585b - md5: 00f5b8dafa842e0c27c1cd7296aa4875 + - _openmp_mutex + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 423025 + timestamp: 1771378225170 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + sha256: fb06c2a2ef06716a0f2a6550f5d13cdd1d89365993068512b7ae3c34e6e665d9 + md5: 34a9f67498721abcfef00178bcf4b190 depends: - - jupyter_client >=6.1.12 - - jupyter_core >=4.12,!=5.0.* - - nbformat >=5.1 - - python >=3.8 - - traitlets >=5.4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbclient?source=compressed-mapping - size: 28473 - timestamp: 1766485646962 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda - sha256: 628fea99108df8e33396bb0b88658ec3d58edf245df224f57c0dce09615cbed2 - md5: b14079a39ae60ac7ad2ec3d9eab075ca + - libgfortran5 15.2.0 hd16e46c_18 + constrains: + - libgfortran-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 139761 + timestamp: 1771378423828 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + sha256: ddaf9dcf008c031b10987991aa78643e03c24a534ad420925cbd5851b31faa11 + md5: ca52daf58cea766656266c8771d8be81 depends: - - beautifulsoup4 - - bleach-with-css !=5.0.0 - - defusedxml - - importlib-metadata >=3.6 - - jinja2 >=3.0 - - jupyter_core >=4.7 - - jupyterlab_pygments - - markupsafe >=2.0 - - mistune >=2.0.3,<4 - - nbclient >=0.5.0 - - nbformat >=5.7 - - packaging - - pandocfilters >=1.4.1 - - pygments >=2.4.1 - - python >=3.10 - - traitlets >=5.1 - - python + - libgcc >=15.2.0 constrains: - - pandoc >=2.9.2,<4.0.0 - - nbconvert ==7.17.0 *_0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbconvert?source=compressed-mapping - size: 202284 - timestamp: 1769709543555 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 - md5: bbe1963f1e47f594070ffe87cdf612ea + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1062274 + timestamp: 1771378232014 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libglib-2.86.4-hec30fc1_1.conda + sha256: d45fd67e18e793aeb2485a7efe3e882df594601ed6136ed1863c56109e4ad9e3 + md5: b8437d8dc24f46da3565d7f0c5a96d45 depends: - - jsonschema >=2.6 - - jupyter_core >=4.12,!=5.0.* - - python >=3.9 - - python-fastjsonschema >=2.15 - - traitlets >=5.1 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbformat?source=hash-mapping - size: 100945 - timestamp: 1733402844974 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 - md5: 47e340acb35de30501a76c7c799c41d7 + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - libiconv >=1.18,<2.0a0 + - libintl >=0.25.1,<1.0a0 + - libzlib >=1.3.1,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + constrains: + - glib 2.86.4 *_1 + license: LGPL-2.1-or-later + purls: [] + size: 4186085 + timestamp: 1771863964173 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libhwy-1.4.0-h7747b51_0.conda + sha256: b028ef59ae5a84d40bbf5ad2d0dd2378808afd895ddd4b6a313c42aa7e850f90 + md5: 907c5677af8696394f85800a2599cb4f depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: X11 AND BSD-3-Clause + - __osx >=11.0 + - libcxx >=19 + license: Apache-2.0 OR BSD-3-Clause purls: [] - size: 891641 - timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - sha256: ea4a5d27ded18443749aefa49dc79f6356da8506d508b5296f60b8d51e0c4bd9 - md5: ced34dd9929f491ca6dab6a2927aff25 + run_exports: + weak: + - libhwy >=1.4.0,<1.5.0a0 + size: 1002721 + timestamp: 1784326121662 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda + sha256: a1c8cecdf9966921e13f0ae921309a1f415dfbd2b791f2117cf7e8f5e61a48b6 + md5: 210a85a1119f97ea7887188d176db135 depends: - __osx >=10.13 - license: X11 AND BSD-3-Clause + license: LGPL-2.1-only purls: [] - size: 822259 - timestamp: 1738196181298 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 - md5: 068d497125e4bf8a66bf707254fff5ae + size: 737846 + timestamp: 1754908900138 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libintl-0.25.1-h3184127_1.conda + sha256: 8c352744517bc62d24539d1ecc813b9fdc8a785c780197c5f0b84ec5b0dfe122 + md5: a8e54eefc65645193c46e8b180f62d22 depends: - - __osx >=11.0 - license: X11 AND BSD-3-Clause + - __osx >=10.13 + - libiconv >=1.18,<2.0a0 + license: LGPL-2.1-or-later purls: [] - size: 797030 - timestamp: 1738196177597 -- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 - md5: 598fd7d4d0de2455fb74f56063969a97 + size: 96909 + timestamp: 1753343977382 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.2-h8616949_0.conda + sha256: ebe2877abc046688d6ea299e80d8322d10c69763f13a102010f90f7168cc5f54 + md5: 48dda187f169f5a8f1e5e07701d5cdd9 depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/nest-asyncio?source=hash-mapping - size: 11543 - timestamp: 1733325673691 -- conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.2-nompi_h5aa5643_101.conda - sha256: 95ec8da51b4bc6939863fdfff63cd0f868e47160f79eb8a053f308a474e1819f - md5: 2b2619e62617d167b4379eb5c764ca99 + - __osx >=10.13 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib + purls: [] + size: 586189 + timestamp: 1762095332781 +- conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-5_h859234e_openblas.conda + build_number: 5 + sha256: 2c915fe2b3d806d4b82776c882ba66ba3e095e9e2c41cc5c3375bffec6bddfdc + md5: eb5b1c25d4ac30813a6ca950a58710d6 depends: - - __glibc >=2.17,<3.0.a0 - - hdf5 * nompi_* - - hdf5 >=1.14.6,<1.14.7.0a0 - - libgcc >=14 - - libgfortran - - libgfortran5 >=14.3.0 - - libnetcdf * nompi_* - - libnetcdf >=4.9.2,<4.9.3.0a0 - license: NetCDF + - libblas 3.11.0 5_he492b99_openblas + constrains: + - libcblas 3.11.0 5*_openblas + - blas 2.305 openblas + - liblapacke 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD purls: [] - size: 451379 - timestamp: 1753422517485 -- conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.2-nompi_h015bb1b_101.conda - sha256: d35907e4367ab7cbef8862b7dc9259247d57bac0983d04128cb368b795c77364 - md5: 3b65a36dc115d2a0f8e6b0f972751bee + size: 18491 + timestamp: 1765819090240 +- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_1.conda + sha256: 7915dac7c71c208e40e716e2f6d3eff41a8d5584e0e7c2d46f9bf9bd5f9aa739 + md5: daf49067580fbfeae3ac7f9535400494 depends: - - __osx >=10.13 - - hdf5 * nompi_* - - hdf5 >=1.14.6,<1.14.7.0a0 - - libgfortran - - libgfortran5 >=14.3.0 - - libgfortran5 >=15.1.0 - - libnetcdf * nompi_* - - libnetcdf >=4.9.2,<4.9.3.0a0 - license: NetCDF + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD purls: [] - size: 419142 - timestamp: 1753422684206 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.2-nompi_h170865b_101.conda - sha256: a5876dc20dc4bb4e04998ce3e998e5ac535bd49831ef70e6dfca2e6ce13e29c8 - md5: 464e1c17dec0f2db0d0b4713cb0307d1 + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 104919 + timestamp: 1786349094608 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libnetcdf-4.10.0-nompi_h2d2ed95_100.conda + sha256: 9a0f90d7b785c1af5836f339579a7106f1c8f1af7bc2b74a555f387153efee9d + md5: 239332ad5f360c9692e90d34f916cd97 depends: - __osx >=11.0 - - hdf5 * nompi_* + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - hdf4 >=4.2.15,<4.2.16.0a0 - hdf5 >=1.14.6,<1.14.7.0a0 - - libgfortran - - libgfortran5 >=14.3.0 - - libgfortran5 >=15.1.0 - - libnetcdf * nompi_* - - libnetcdf >=4.9.2,<4.9.3.0a0 - license: NetCDF + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.18.0,<9.0a0 + - libcxx >=19 + - libxml2 + - libxml2-16 >=2.14.6 + - libzip >=1.11.2,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: MIT + license_family: MIT purls: [] - size: 471316 - timestamp: 1753422676317 -- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 - md5: a2c1eeadae7a309daed9d62c96012a2b + run_exports: + weak: + - libnetcdf >=4.10.0,<4.10.1.0a0 + size: 725520 + timestamp: 1772191252343 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda + sha256: c48d7e1cc927aef83ff9c48ae34dd1d7495c6ccc1edc4a3a6ba6aff1624be9ac + md5: e7630cef881b1174d40f3e69a883e55f depends: - - python >=3.11 - - python - constrains: - - numpy >=1.25 - - scipy >=1.11.2 - - matplotlib-base >=3.8 - - pandas >=2.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/networkx?source=compressed-mapping - size: 1587439 - timestamp: 1765215107045 -- pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - name: nodeenv - version: 1.10.0 - sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - sha256: d38542a151a90417065c1a234866f97fd1ea82a81de75ecb725955ab78f88b4b - md5: 9a66894dfd07c4510beb6b3f9672ccc0 - constrains: - - mkl <0.a0 - license: BSD-3-Clause - license_family: BSD + - __osx >=10.13 + - c-ares >=1.34.5,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT purls: [] - size: 3843 - timestamp: 1582593857545 -- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.5.4-pyhcf101f3_0.conda - sha256: c25d4c77747af7598c843b626c051e93ea3d8d0a7782bae1c48ba8f4dd1961d4 - md5: ad642622e20bd5dbe4157d35ddf1db47 + size: 605680 + timestamp: 1756835898134 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libntlm-1.8-h6e16a3a_0.conda + sha256: 2ab918f7cc00852d70088e0b9e49fda4ef95229126cf3c52a8297686938385f2 + md5: 23d706dbe90b54059ad86ff826677f39 depends: - - importlib_resources >=5.0 - - jupyter_server >=2.4.0,<3 - - jupyterlab >=4.5.5,<4.6 - - jupyterlab_server >=2.28.0,<3 - - notebook-shim >=0.2,<0.3 - - python >=3.10 - - tornado >=6.2.0 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/notebook?source=compressed-mapping - size: 10111074 - timestamp: 1771943769401 -- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 - md5: e7f89ea5f7ea9401642758ff50a2d9c1 + - __osx >=10.13 + license: LGPL-2.1-or-later + purls: [] + size: 33742 + timestamp: 1734670081910 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + sha256: ba642353f7f41ab2d2eb6410fbe522238f0f4483bcd07df30b3222b4454ee7cd + md5: 9241a65e6e9605e4581a2a8005d7f789 depends: - - jupyter_server >=1.8,<3 - - python >=3.9 + - __osx >=10.13 + - libgfortran + - libgfortran5 >=14.3.0 + - llvm-openmp >=19.1.7 + constrains: + - openblas >=0.3.30,<0.3.31.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/notebook-shim?source=hash-mapping - size: 16817 - timestamp: 1733408419340 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-mkl_py312h4fb851f_1.conda - sha256: 39c82aed18b66039a6f1bcc7673a1e4240243fc3cca48a0be12d0c8d1cba4cf9 - md5: 6c2d17edc3f919957556873840ddf363 + purls: [] + size: 6268795 + timestamp: 1763117623665 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.55-h07817ec_0.conda + sha256: 75755fa305f7c944d911bf00593e283ebb83dac1e9c54dc1e016cf591e57d808 + md5: 4fc7ed44d55aaf1d72b8fbc18774b90c depends: - - __glibc >=2.17,<3.0.a0 - - libblas * *mkl - - libgcc >=14 - - libstdcxx >=14 - - mkl >=2025.3.0,<2026.0a0 - - numpy >=1.23,<3 - - numpy >=1.23.0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/numexpr?source=hash-mapping - size: 217775 - timestamp: 1764766736997 -- conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda - sha256: b229e0012bfe0ca9e1ebb25bb1435bf68f2009831e4fa0f5672be00d3b069c37 - md5: 42ca06bfb47a5aa48940404b96082f31 + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: zlib-acknowledgement + purls: [] + size: 298943 + timestamp: 1770691469850 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libpq-18.1-h1e038c5_2.conda + sha256: abaf961d69039e1a8f377e02c1f0e48173c347c3bb0d2d99508a1efdba9430c2 + md5: 5084757a93eb76dd26cbc85a4f38b0a3 depends: - __osx >=10.13 - - libcxx >=19 - - numpy >=1.23,<3 - - numpy >=1.23.0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/numexpr?source=hash-mapping - size: 205532 - timestamp: 1762595210249 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda - sha256: e6bac966c7aa4207a2cc2de020041d3b729e37c68279fc73774e34f688e96b1c - md5: 0d4d77d16b5032ab0b4ca3f66f14672d + - icu >=75.1,<76.0a0 + - krb5 >=1.21.3,<1.22.0a0 + - openldap >=2.6.10,<2.7.0a0 + - openssl >=3.5.4,<4.0a0 + license: PostgreSQL + purls: [] + size: 2703473 + timestamp: 1764346703796 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-6.31.1-hcc66ac3_4.conda + sha256: 2058eb9748a6e29a1821fea8aeea48e87d73c83be47b0504ac03914fee944d0e + md5: f22705f9ebb3f79832d635c4c2919b15 depends: - __osx >=11.0 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 - libcxx >=19 - - numpy >=1.23,<3 - - numpy >=1.23.0 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/numexpr?source=hash-mapping - size: 197663 - timestamp: 1762595288926 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda - sha256: fec4d37e1a7c677ddc07bb968255df74902733398b77acc1d05f9dc599e879df - md5: 3569a8fca2dd3202e4ab08f42499f6d3 - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - liblapack >=3.9.0,<4.0a0 - - python_abi 3.12.* *_cp312 - - libcblas >=3.9.0,<4.0a0 - - libblas >=3.9.0,<4.0a0 - constrains: - - numpy-base <0a0 + - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/numpy?source=hash-mapping - size: 8757566 - timestamp: 1770098484112 -- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda - sha256: e9acaaafe6a0a698d4d759d860fc8a617724a3031ae1918f761e69297e543a3e - md5: c06b511affcf74a79b1852ae7b722035 + purls: [] + size: 3079808 + timestamp: 1766315644973 +- conda: https://conda.anaconda.org/conda-forge/osx-64/librdkit-2025.09.5-ha1af57f_0.conda + sha256: 4ac74d00766232c6dd121c578ea56e1a27baefcca016cdcd38732727ba49281e + md5: d31cc6094c2041bec7aafd6ef08f7074 depends: - - python - __osx >=10.13 + - cairo >=1.18.4,<2.0a0 + - libboost >=1.86.0,<1.87.0a0 - libcxx >=19 - - liblapack >=3.9.0,<4.0a0 - - python_abi 3.12.* *_cp312 - - libcblas >=3.9.0,<4.0a0 - - libblas >=3.9.0,<4.0a0 - constrains: - - numpy-base <0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/numpy?source=hash-mapping - size: 7978246 - timestamp: 1770098377108 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda - sha256: 7fd2f1a33b244129dcc2163304d103a7062fc38f01fe13945c9ea95cef12b954 - md5: 4afbe6ffff0335d25f3c5cc78b1350a4 + purls: [] + size: 7473122 + timestamp: 1770018948329 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda + sha256: d3975cfe60e81072666da8c76b993af018cf2e73fe55acba2b5ba0928efaccf5 + md5: 6af4b059e26492da6013e79cbcb4d069 + depends: + - __osx >=10.13 + license: ISC + purls: [] + size: 210249 + timestamp: 1716828641383 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.4-h77d7759_0.conda + sha256: 5725d44a17d196adba9798a5fd9f692b7039a827cd6145556a072a80e1931c49 + md5: 993009426e1d3aa90eed155171bd59d8 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 1008531 + timestamp: 1785016345740 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda + sha256: 00654ba9e5f73aa1f75c1f69db34a19029e970a4aeb0fa8615934d8e9c369c3c + md5: a6cb15db1c2dc4d3a5f6cf3772e09e81 + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 284216 + timestamp: 1745608575796 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-ha0a348c_1.conda + sha256: e53424c34147301beae2cd9223ebf593720d94c038b3f03cacd0535e12c9668e + md5: 9d4344f94de4ab1330cdc41c40152ea6 depends: - - python + - __osx >=10.13 + - lerc >=4.0.0,<5.0a0 - libcxx >=19 + - libdeflate >=1.25,<1.26.0a0 + - libjpeg-turbo >=3.1.0,<4.0a0 + - liblzma >=5.8.1,<6.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + size: 404591 + timestamp: 1762022511178 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libtorch-2.8.0-cpu_generic_h4c1adde_2.conda + sha256: b92f9fc7777b56e02d1d39392b2dd87cb2399b46ddc52627e00b5b11ffd05869 + md5: ff483076adc20d9fc66c90e73f117443 + depends: - __osx >=11.0 - - python 3.12.* *_cpython + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 - libblas >=3.9.0,<4.0a0 - - python_abi 3.12.* *_cp312 - - liblapack >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=19.1.7 + - numpy >=1.23,<3 + - pybind11-abi 4 + - python_abi 3.12.* *_cp312 + - sleef >=3.9.0,<4.0a0 constrains: - - numpy-base <0a0 + - pytorch 2.8.0 cpu_generic_*_2 + - pytorch-cpu 2.8.0 + - pytorch-gpu <0.0a0 + - openblas * openmp_* + - libopenblas * openmp_* license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/numpy?source=hash-mapping - size: 6840961 - timestamp: 1770098400654 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda - sha256: 2254dae821b286fb57c61895f2b40e3571a070910fdab79a948ff703e1ea807b - md5: 56f8947aa9d5cf37b0b3d43b83f34192 + purls: [] + size: 49162152 + timestamp: 1762106123487 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda + sha256: d90dd0eee6f195a5bd14edab4c5b33be3635b674b0b6c010fb942b956aa2254c + md5: fbfc6cf607ae1e1e498734e256561dc3 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - opencl-headers >=2024.10.24 - license: BSD-2-Clause - license_family: BSD + - __osx >=10.13 + license: MIT + license_family: MIT purls: [] - size: 106742 - timestamp: 1743700382939 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-system-1.0.0-1.tar.bz2 - sha256: cb0ce5ce5ede1be2fd9edf88abd3ce3e6c2b7397c0283d623e4d8ccf96a1ed09 - md5: 577a4bd049737b11a24524e39a16a1f3 + size: 422612 + timestamp: 1753948458902 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.6.0-hb807250_0.conda + sha256: 00dbfe574b5d9b9b2b519acb07545380a6bc98d1f76a02695be4995d4ec91391 + md5: 7bb6608cf1f83578587297a158a6630b depends: - - ocl-icd - license: BSD 3-Clause + - __osx >=10.13 + constrains: + - libwebp 1.6.0 + license: BSD-3-Clause license_family: BSD purls: [] - size: 4253 - timestamp: 1575483836797 -- conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 - sha256: 88b7a787ab206d19387f68ee4c2e4b2256271afc5afc5cb5a7dd4be1da95697a - md5: 9f53a64ef2725dfdcf3168c93aa26032 + size: 365086 + timestamp: 1752159528504 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.17.0-hf1f96e2_0.conda + sha256: 8896cd5deff6f57d102734f3e672bc17120613647288f9122bec69098e839af7 + md5: bbeca862892e2898bdb45792a61c4afc depends: - - khronos-opencl-icd-loader >=2020.6.16 - license: BSD-2-Clause - license_family: BSD + - __osx >=10.13 + - pthread-stubs + - xorg-libxau >=1.0.11,<2.0a0 + - xorg-libxdmcp + license: MIT + license_family: MIT purls: [] - size: 18351 - timestamp: 1605242257051 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 - sha256: dd04e58c813d46c41f65561f92922d25053d2049a10759e679e26f1c785678e7 - md5: 9104716a5fb54f62431752dc863cd909 + size: 323770 + timestamp: 1727278927545 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-16-2.15.1-ha1d9b0f_0.conda + sha256: e23c5ac1da7b9b65bd18bf32b68717cd9da0387941178cb4d8cc5513eb69a0a9 + md5: 453807a4b94005e7148f89f9327eb1b7 depends: - - khronos-opencl-icd-loader >=2020.6.16 - license: BSD-2-Clause - license_family: BSD + - __osx >=10.13 + - icu >=75.1,<76.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - libxml2 2.15.1 + license: MIT + license_family: MIT purls: [] - size: 18631 - timestamp: 1605242282408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda - sha256: 2b6ce54174ec19110e1b3c37455f7cd138d0e228a75727a9bba443427da30a36 - md5: 45c3d2c224002d6d0d7769142b29f986 + run_exports: {} + size: 494318 + timestamp: 1761015899881 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.1-h7b7ecba_0.conda + sha256: ddf87bf05955d7870a41ca6f0e9fbd7b896b5a26ec1a98cd990883ac0b4f99bb + md5: e7ed73b34f9d43d80b7e80eba9bce9f3 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - license: Apache-2.0 - license_family: APACHE + - __osx >=10.13 + - icu >=75.1,<76.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libxml2-16 2.15.1 ha1d9b0f_0 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT purls: [] - size: 55357 - timestamp: 1749853464518 -- conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda - sha256: 31bfb14d3d269f21c7449df065eca024a89ca98ec69bf12c9c3f2cd6913c7e04 - md5: 520120c68d69c548981ea92668f428a5 + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.1 + size: 39985 + timestamp: 1761015935429 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libzip-1.11.2-h31df5bb_0.conda + sha256: 434a4d1ad23c1c8deb7ec2da94aca05e22bc29dee445b4f7642e1c2f20fc0b0b + md5: 3cf12c97a18312c9243a895580bf5be6 depends: - __osx >=10.13 - - libcxx >=18 - license: Apache-2.0 - license_family: APACHE + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.3.2,<4.0a0 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 55624 - timestamp: 1749853573437 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda - sha256: 5c6269546780749ad5f4e780f0c7d7dcaaf6c20961ebd4588d82fdcda6cdf386 - md5: f4d4d97714ae85265e4299b8e56ca6eb + size: 129542 + timestamp: 1730442392952 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + sha256: b2dba286dd6632292b12296e761193b5ef9fb0eaeecaa481f5ba9af72c0c18e1 + md5: 7d3fa28263bb7f8ea32db11f570a5bb6 depends: - __osx >=11.0 - - libcxx >=18 - license: Apache-2.0 - license_family: APACHE + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other purls: [] - size: 55476 - timestamp: 1749853726041 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-amber-ff-ports-2025.09.0-pyhd8ed1ab_0.conda - sha256: a437b074cb53a94275ea2d8294776115780a9b9177acdf0dac658887e61e69b0 - md5: 577353bf1c9f4dca81e99400cc7ca571 + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 58993 + timestamp: 1785276808631 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + sha256: b63df4e592b3362e7d13e3d1cf8e55ce932ff4f17611c8514b5d36368ec2094c + md5: 3921780bab286f2439ba483c22b90345 depends: - - python >=3.11 - - setuptools - license: CC-BY-4.0 - license_family: CC - purls: - - pkg:pypi/openff-amber-ff-ports?source=hash-mapping - size: 222475 - timestamp: 1758297599388 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-forcefields-2026.01.0-pyhd8ed1ab_0.conda - sha256: 0ddbef322b893062565e91218689a9ffff1380f6742df12d783ce63b49325c57 - md5: a58c407277d3c04a21cea9eaeea03618 + - __osx >=11.0 + constrains: + - openmp 22.1.0|22.1.0.* + - intel-openmp <0.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + size: 311938 + timestamp: 1772024731611 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lol-html-3.0.1-h19f9e61_0.conda + sha256: 5d383b45c5419787b12a71f7488a66212cb60f773289b8ea925fd192609e4cd6 + md5: c0fc27b7907de6598e78833fed0d5ad5 depends: - - python >=3.11 - - setuptools + - __osx >=11.0 constrains: - - openforcefields ==9999999999 - license: CC-BY-4.0 - license_family: CC - purls: - - pkg:pypi/openforcefields?source=hash-mapping - size: 118532 - timestamp: 1768284405101 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-0.5.2-pyhd8ed1ab_0.conda - sha256: b3e15d74848dc27a9f3f4a62bb19a7fbad7c15beb7b7f8d830542123b5916690 - md5: b667da0d26a7e9f5234028e7265bf524 + - __osx >=11.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - lol-html >=3.0.1,<3.0.2.0a0 + size: 470387 + timestamp: 1785365158559 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ls-hpack-2.3.5-ha3d0635_0.conda + sha256: 97b447b45347287bde060655b6c641e0522efed95e09fcc4570cdd2b12a66673 + md5: 5305bb28af282ba7034d46d80b87aa1f depends: - - openff-interchange-base 0.5.2 pyhd8ed1ab_0 - - openff-toolkit >=0.17.0,<0.19 - - openmm 8.* - - pyedr - - python >=3.11,<3.14.0a0 + - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 8500 - timestamp: 1772496921112 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-interchange-base-0.5.2-pyhd8ed1ab_0.conda - sha256: 47fe4b3b12c11540d17cbb20f96359541a0c0e8b9f681fa71a78eb2e915a292b - md5: 5f8bce63ca0ef1faa52fbcaf7db51607 + run_exports: + weak: + - ls-hpack >=2.3.5,<2.3.6.0a0 + size: 104903 + timestamp: 1779602241583 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.10.0-h240833e_1.conda + sha256: 8da3c9d4b596e481750440c0250a7e18521e7f69a47e1c8415d568c847c08a1c + md5: d6b9bd7e356abd7e3a633d59b753495a depends: - - numpy <2.5 - - openff-toolkit-base >=0.16.0,<0.19 - - openff-units >=0.2.0,<0.4.0 - - openff-utilities >=0.1.5 - - pydantic >=2,<3,<2.12 - - python >=3.11,<3.14.0a0 - constrains: - - jax >=0.3 - - smirnoff-plugins >=2024.07.0 - - jaxlib >=0.3 - - foyer =1 - - lammps >=2023.08.02 - license: MIT - license_family: MIT - purls: - - pkg:pypi/openff-interchange?source=hash-mapping - size: 700362 - timestamp: 1772496904797 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-0.5.4-pyhd8ed1ab_1.conda - sha256: 607df80ceff8320dc9b9f30cdc403d91b9fd6f686b98aa2c065c04beb4ca9583 - md5: 9be58005f9ec3010dc9bb94dc22a46e5 + - __osx >=10.13 + - libcxx >=18 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 159500 + timestamp: 1733741074747 +- conda: https://conda.anaconda.org/conda-forge/osx-64/lzo-2.10-h4132b18_1002.conda + sha256: bb5fe07123a7d573af281d04b75e1e77e87e62c5c4eb66d9781aa919450510d1 + md5: 5a047b9aa4be1dcdb62bd561d9eb6ceb depends: - - openff-nagl-base 0.5.4 pyhd8ed1ab_1 - - openff-nagl-models >=0.1.0 - - python >=3.11 - license: MIT - license_family: MIT + - __osx >=10.13 + license: GPL-2.0-or-later + license_family: GPL purls: [] - size: 7436 - timestamp: 1761911874786 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-base-0.5.4-pyhd8ed1ab_1.conda - sha256: 6046497a5c336c30ecb05b8c6770b532999c01812ff5702a79fe692e0c4bf8d6 - md5: 4f28458d1282a127010650bff68bfb7c + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 174634 + timestamp: 1753889269889 +- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.3-py312heb39f77_1.conda + sha256: 0eb418d4776a1a54c1869b11a5c4ae096ef9a46c8d7e481e32fa814561c5cfed + md5: d596f9d03043acd4ec711c844060da59 depends: - - click - - click-option-group - - openff-toolkit-base >=0.11.0 - - openff-units - - pydantic <3 - - python >=3.11 - - pytorch >=2.0,<=2.8 - - pytorch-lightning - - pyyaml - - rdkit - - rich - - tqdm + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 constrains: - - openeye-toolkits - - dgl >=1.0,<=3 - license: MIT - license_family: MIT + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/openff-nagl?source=hash-mapping - size: 119924 - timestamp: 1761845487531 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-nagl-models-2025.9.0-pyhd8ed1ab_0.conda - sha256: 824a8c18c03b52589ab1006b13c38511243d00acf3a165e22cd01be7555487e6 - md5: 6b73b0f2cf46c04e09c30bf062cff01b + - pkg:pypi/markupsafe?source=hash-mapping + size: 25095 + timestamp: 1772445399364 +- conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.8-py312h7894933_0.conda + sha256: 2ce31cad23d5d5fc16ca9d25f47dcfc52e93f2a0c6e1dc6db28e583c42f88bdc + md5: 853618b60fdd11a6c3dbaadaa413407c depends: - - importlib_resources + - __osx >=10.13 + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.22.0 + - freetype + - kiwisolver >=1.3.1 + - libcxx >=19 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - numpy >=1.23 + - numpy >=1.23,<3 + - packaging >=20.0 + - pillow >=8 + - pyparsing >=2.3.1 + - python >=3.12,<3.13.0a0 + - python-dateutil >=2.7 + - python_abi 3.12.* *_cp312 + - qhull >=2020.2,<2020.3.0a0 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/matplotlib?source=hash-mapping + size: 8295843 + timestamp: 1763055621386 +- conda: https://conda.anaconda.org/conda-forge/osx-64/mdtraj-1.11.1-np2py312h6d257ad_1.conda + sha256: 8523de7f24927a42f6f4b6fdb4f31ae2e3dc9835db8e29f467acda05d0952830 + md5: 4a75dbb7cc27d229f97e9a1525384ea1 + depends: + - python + - setuptools - packaging - - platformdirs - - python >=3.11 + - numpy >=1.25.0 + - scipy + - pandas <3 + - pyparsing + - pytables + - snappy + - zlib + - libcxx >=19 + - __osx >=10.13 + - numpy >=1.23,<3 + - libzlib >=1.3.1,<2.0a0 + - python_abi 3.12.* *_cp312 constrains: - - openff-nagl >=0.4.0 - license: CC-BY-4.0 - license_family: CC + - gsd !=3.0.0 + license: LGPL-2.1-or-later purls: - - pkg:pypi/openff-nagl-models?source=hash-mapping - size: 53380163 - timestamp: 1757448697983 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-0.18.0-pyhd8ed1ab_3.conda - sha256: 4e7b9c31083727470518ddec9cf5edc569de22bf8e18c09e27b9905805ce8ca3 - md5: b71429982dee66c51f68e99eff602d3a + - pkg:pypi/mdtraj?source=hash-mapping + size: 1261738 + timestamp: 1769108753148 +- conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + sha256: dcf91571da6c2f0db96d43a1b639047def05a0e1b6436d42c9129ab14af47b10 + md5: 0520855aaae268ea413d6bc913f1384c depends: - - ambertools >=22 - - ipywidgets - - mdtraj - - notebook - - openff-interchange >=0.4,<0.6 - - openff-nagl >=0.4 - - openff-toolkit-base 0.18.0 pyhd8ed1ab_3 - - openmm >=7.6 - - python >=3.11,<3.14a0 - - rdkit !=2024.03.6 - license: MIT - license_family: MIT + - __osx >=10.13 + - gmp >=6.3.0,<7.0a0 + - mpfr >=4.2.1,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL purls: [] - size: 10159 - timestamp: 1771436993690 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-toolkit-base-0.18.0-pyhd8ed1ab_3.conda - sha256: 0c85e518e00677ebc8332da4f4b37f519b3eddcbc526962a8e47af8f42f5a11c - md5: f6d43b327ba02dcbddb1e138bf188c11 + size: 107774 + timestamp: 1725629348601 +- conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda + sha256: dddb6721dff05b8dfb654c532725330231fcb81ff1e27d885ee0cdcc9fccf1c4 + md5: d511e58aaaabfc23136880d9956fa7a6 depends: - - bson - - cachetools - - networkx >=2.5 - - numpy - - openff-amber-ff-ports >=2025.09.0 - - openff-forcefields - - openff-units - - openff-utilities - - packaging - - python >=3.11,<3.14a0 - - python-constraint - - typing_extensions - - xmltodict <1.0.3 - constrains: - - qcportal >=0.50.0 - - pydantic <3.0.0a0 - - openforcefield ==9999999999 - license: MIT - license_family: MIT - purls: - - pkg:pypi/openff-toolkit?source=hash-mapping - size: 8109584 - timestamp: 1771436972576 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-units-0.3.2-pyhd8ed1ab_0.conda - sha256: ce06098d58e12bbcdae73a5eb62556b59a0b39b01518ee61fd2713b7fb5948d4 - md5: 764a41caac8b44b289721573cf25e471 + - __osx >=10.13 + - gmp >=6.3.0,<7.0a0 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + size: 373396 + timestamp: 1725746891597 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + sha256: ea4a5d27ded18443749aefa49dc79f6356da8506d508b5296f60b8d51e0c4bd9 + md5: ced34dd9929f491ca6dab6a2927aff25 depends: - - numpy - - openff-utilities - - pint >=0.24.0,<0.26 - - python >=3.11,<3.15.0a0 - constrains: - - openmm >7.6 + - __osx >=10.13 + license: X11 AND BSD-3-Clause + purls: [] + size: 822259 + timestamp: 1738196181298 +- conda: https://conda.anaconda.org/conda-forge/osx-64/netcdf-fortran-4.6.4-nompi_hb028221_100.conda + sha256: 10ce9a4bd2c10787e9e44dfb50fa2c089a1794846c2c53eba60d5ccfffce835a + md5: 9dddc23e469c53b14776a77f7de14258 + depends: + - libnetcdf * nompi_* + - hdf5 * nompi_* + - __osx >=11.0 + - libgfortran + - libgfortran5 >=14.3.0 + - libzlib >=1.3.2,<2.0a0 + - hdf5 >=1.14.6,<1.14.7.0a0 + - libnetcdf >=4.10.0,<4.10.1.0a0 + license: NetCDF + purls: [] + run_exports: + weak: + - netcdf-fortran >=4.6.4,<4.7.0a0 + size: 453781 + timestamp: 1785419320520 +- conda: https://conda.anaconda.org/conda-forge/osx-64/numexpr-2.14.1-py312hd12f69b_1.conda + sha256: b229e0012bfe0ca9e1ebb25bb1435bf68f2009831e4fa0f5672be00d3b069c37 + md5: 42ca06bfb47a5aa48940404b96082f31 + depends: + - __osx >=10.13 + - libcxx >=19 + - numpy >=1.23,<3 + - numpy >=1.23.0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/openff-units?source=hash-mapping - size: 24811 - timestamp: 1767915235307 -- conda: https://conda.anaconda.org/conda-forge/noarch/openff-utilities-0.1.16-pyhd8ed1ab_1.conda - sha256: a8a4275c9c0ba101fe59ec65178958b29f453e83452b533c80f77401f42f9229 - md5: 77e1227050daa7fc8792bf1286a3e57c + - pkg:pypi/numexpr?source=hash-mapping + size: 205532 + timestamp: 1762595210249 +- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.2-py312hb34da66_1.conda + sha256: e9acaaafe6a0a698d4d759d860fc8a617724a3031ae1918f761e69297e543a3e + md5: c06b511affcf74a79b1852ae7b722035 depends: - - python >=3.11 - license: MIT - license_family: MIT + - python + - __osx >=10.13 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 + - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/openff-utilities?source=hash-mapping - size: 18866 - timestamp: 1765557239153 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d - md5: 11b3379b191f63139e29c0d19dee24cd + - pkg:pypi/numpy?source=hash-mapping + size: 7978246 + timestamp: 1770098377108 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ocl_icd_wrapper_apple-1.0.0-hbcb3906_0.tar.bz2 + sha256: 88b7a787ab206d19387f68ee4c2e4b2256271afc5afc5cb5a7dd4be1da95697a + md5: 9f53a64ef2725dfdcf3168c93aa26032 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libpng >=1.6.50,<1.7.0a0 - - libstdcxx >=14 - - libtiff >=4.7.1,<4.8.0a0 - - libzlib >=1.3.1,<2.0a0 + - khronos-opencl-icd-loader >=2020.6.16 license: BSD-2-Clause license_family: BSD purls: [] - size: 355400 - timestamp: 1758489294972 + size: 18351 + timestamp: 1605242257051 +- conda: https://conda.anaconda.org/conda-forge/osx-64/opencl-headers-2025.06.13-h240833e_0.conda + sha256: 31bfb14d3d269f21c7449df065eca024a89ca98ec69bf12c9c3f2cd6913c7e04 + md5: 520120c68d69c548981ea92668f428a5 + depends: + - __osx >=10.13 + - libcxx >=18 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 55624 + timestamp: 1749853573437 - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda sha256: 9a37ecf9c086f3a50d0132e6087dcbe7ea978d80e2da267fa3199c486529b311 md5: 46e628da6e796c948fa8ec9d6d10bda3 @@ -8047,35 +9417,6 @@ packages: purls: [] size: 335227 timestamp: 1772625294157 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - sha256: 60aca8b9f94d06b852b296c276b3cf0efba5a6eb9f25feb8708570d3a74f00e4 - md5: 4b5d3a91320976eec71678fad1e3569b - depends: - - __osx >=11.0 - - libcxx >=19 - - libpng >=1.6.55,<1.7.0a0 - - libtiff >=4.7.1,<4.8.0a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 319697 - timestamp: 1772625397692 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda - sha256: cb0b07db15e303e6f0a19646807715d28f1264c6350309a559702f4f34f37892 - md5: 2e5bf4f1da39c0b32778561c3c4e5878 - depends: - - __glibc >=2.17,<3.0.a0 - - cyrus-sasl >=2.1.27,<3.0a0 - - krb5 >=1.21.3,<1.22.0a0 - - libgcc >=13 - - libstdcxx >=13 - - openssl >=3.5.0,<4.0a0 - license: OLDAP-2.8 - license_family: BSD - purls: [] - size: 780253 - timestamp: 1748010165522 - conda: https://conda.anaconda.org/conda-forge/osx-64/openldap-2.6.10-hd8a590d_0.conda sha256: 70b8c1ffc06629c3ef824d337ab75df28c50a05293a4c544b03ff41d82c37c73 md5: 60bd9b6c1e5208ff2f4a39ab3eabdee8 @@ -8090,2826 +9431,3034 @@ packages: purls: [] size: 777643 timestamp: 1748010635431 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.10-hbe55e7a_0.conda - sha256: 08d859836b81296c16f74336c3a9a455b23d57ce1d7c2b0b3e1b7a07f984c677 - md5: 6fd5d73c63b5d37d9196efb4f044af76 - depends: - - __osx >=11.0 - - cyrus-sasl >=2.1.27,<3.0a0 - - krb5 >=1.21.3,<1.22.0a0 - - libcxx >=18 - - openssl >=3.5.0,<4.0a0 - license: OLDAP-2.8 - license_family: BSD - purls: [] - size: 843597 - timestamp: 1748010484231 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openmm-8.4.0-py312ha28768a_2.conda - sha256: a0a19178ad323074a4595af936c72647245b7b59d975a816fcf1c2c3af3a14db - md5: bd6e733023b03e2d681267a1e048f3c1 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda + build_number: 2 + sha256: 5360695d91fc99917f1f9db1cfb860de7fd902f07cd8482b46836d7a0bfa00fb + md5: 0ecc5c4b07dce1d052fadb76323c0744 depends: - - __glibc >=2.28,<3.0.a0 - - cuda-nvrtc >=13.0.88,<14.0a0 - - cuda-version >=13.0,<14 - - libcufft >=12.0.0.61,<13.0a0 - - libgcc >=14 - - libstdcxx >=14 + - __osx >=10.13 + - khronos-opencl-icd-loader >=2024.10.24 + - libcxx >=19 - numpy >=1.23,<3 - - ocl-icd >=2.3.3,<3.0a0 - - ocl-icd-system + - ocl_icd_wrapper_apple - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - rocm-core >=7.0.2,<7.1.0a0 - scipy license: LGPL-3.0-or-later license_family: LGPL purls: - pkg:pypi/openmm?source=hash-mapping - size: 14574920 - timestamp: 1765254789351 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openmm-8.4.0-py312hdde34bd_2_khronos.conda - build_number: 2 - sha256: 5360695d91fc99917f1f9db1cfb860de7fd902f07cd8482b46836d7a0bfa00fb - md5: 0ecc5c4b07dce1d052fadb76323c0744 + size: 11597285 + timestamp: 1765254908514 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + sha256: e02e5639b0e4d6d4fcf0f3b082642844fb5a37316f5b0a1126c6271347462e90 + md5: 30bb8d08b99b9a7600d39efb3559fff0 + depends: + - __osx >=10.13 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2777136 + timestamp: 1769557662405 +- conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda + sha256: 2d43c43b3e8e7a6a32dc203ad63884d8e9be46dc0afed1e2dc42dc5e23759ac6 + md5: 2718bc7708f21ae564088ae31b6e44ad + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - typing-extensions >=4.12 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/optree?source=hash-mapping + size: 453146 + timestamp: 1771868875323 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda + sha256: 112273ffd9572a4733c98b9d80a243f38db4d0fce5d34befaf9eb6f64ed39ba3 + md5: d7dfad2b9a142319cec4736fe88d8023 + depends: + - __osx >=10.13 + - libcxx >=19 + - numpy >=1.22.4 + - numpy >=1.23,<3 + - python >=3.12,<3.13.0a0 + - python-dateutil >=2.8.2 + - python-tzdata >=2022.7 + - python_abi 3.12.* *_cp312 + - pytz >=2020.1 + constrains: + - pyarrow >=10.0.1 + - tabulate >=0.9.0 + - html5lib >=1.1 + - s3fs >=2022.11.0 + - pandas-gbq >=0.19.0 + - matplotlib >=3.6.3 + - qtpy >=2.3.0 + - scipy >=1.10.0 + - zstandard >=0.19.0 + - bottleneck >=1.3.6 + - numexpr >=2.8.4 + - pyxlsb >=1.0.10 + - tzdata >=2022.7 + - psycopg2 >=2.9.6 + - pytables >=3.8.0 + - fsspec >=2022.11.0 + - python-calamine >=0.1.7 + - xarray >=2022.12.0 + - numba >=0.56.4 + - pyqt5 >=5.15.9 + - xlrd >=2.0.1 + - blosc >=1.21.3 + - odfpy >=1.4.1 + - openpyxl >=3.1.0 + - fastparquet >=2022.12.0 + - xlsxwriter >=3.0.5 + - pyreadstat >=1.2.0 + - sqlalchemy >=2.0.0 + - gcsfs >=2022.11.0 + - beautifulsoup4 >=4.11.2 + - lxml >=4.9.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 14008759 + timestamp: 1764615365220 +- conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda + sha256: 253779ebb5141c773f5b9778f7fd1b3723cf84818f90b1db720a984ceb07f949 + md5: 8b68981a00e4856c168e44a02c59da40 + depends: + - libcxx >=19 + - numpy >=1.14 + - pandas + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: LGPL-2.1-or-later + license_family: LGPL + purls: + - pkg:pypi/parmed?source=hash-mapping + size: 19253982 + timestamp: 1769768130965 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda + sha256: 8d64a9d36073346542e5ea042ef8207a45a0069a2e65ce3323ee3146db78134c + md5: 08f970fb2b75f5be27678e077ebedd46 + depends: + - __osx >=10.13 + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 1106584 + timestamp: 1763655837207 +- conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda + build_number: 7 + sha256: 8ebd35e2940055a93135b9fd11bef3662cecef72d6ee651f68d64a2f349863c7 + md5: dc442e0885c3a6b65e61c61558161a9e + license: GPL-1.0-or-later OR Artistic-1.0-Perl + purls: [] + size: 12334471 + timestamp: 1703311001432 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda + sha256: a2a48fdaa95921c4178ae16f4663bdaa74a2c93bdf45c8221f328692bb2e083b + md5: b1fcc4538203c9c747879d3e461f2f74 + depends: + - python + - __osx >=10.13 + - tk >=8.6.13,<8.7.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libxcb >=1.17.0,<2.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - python_abi 3.12.* *_cp312 + - zlib-ng >=2.3.3,<2.4.0a0 + - lcms2 >=2.18,<3.0a0 + - openjpeg >=2.5.4,<3.0a0 + license: HPND + purls: + - pkg:pypi/pillow?source=hash-mapping + size: 964529 + timestamp: 1770794115028 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda + sha256: ff8b679079df25aa3ed5daf3f4e3a9c7ee79e7d4b2bd8a21de0f8e7ec7207806 + md5: 742a8552e51029585a32b6024e9f57b4 + depends: + - __osx >=10.13 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + size: 390942 + timestamp: 1754665233989 +- conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda + sha256: 517c17b24349476535db4da7d1cd31538dadf2c77f9f7f7d8be6b7dc5dfbb636 + md5: 1fd947fae149960538fc941b8f122bc1 + depends: + - python + - __osx >=10.13 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 236338 + timestamp: 1769678402626 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda + sha256: 05944ca3445f31614f8c674c560bca02ff05cb51637a96f665cb2bbe496099e5 + md5: 8bcf980d2c6b17094961198284b8e862 + depends: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 8364 + timestamp: 1726802331537 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda + sha256: 98c0d1018a82a3fa32a0100b7a2183e30bdedbcc98a485e62cdc835fdec74ad7 + md5: eae555e47a6a7b966a0ffa46c5f5e075 + depends: + - __osx >=10.13 + - cairo >=1.18.4,<2.0a0 + - libexpat >=2.7.3,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: LGPL-2.1-only OR MPL-1.1 + purls: + - pkg:pypi/pycairo?source=hash-mapping + size: 105280 + timestamp: 1770726851881 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda + sha256: 2bd1ff91077790b93141f6a718840626c6fe12eddd6de8441da6d211aa74999a + md5: ef5b500de254557bd376a64ef2d76c9a depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 - __osx >=10.13 - - khronos-opencl-icd-loader >=2024.10.24 - - libcxx >=19 - - numpy >=1.23,<3 - - ocl_icd_wrapper_apple - - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - scipy - license: LGPL-3.0-or-later - license_family: LGPL + constrains: + - __osx >=10.13 + license: MIT + license_family: MIT purls: - - pkg:pypi/openmm?source=hash-mapping - size: 11597285 - timestamp: 1765254908514 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda - build_number: 2 - sha256: 76334121b6ede512d6c1f49e4a63aa8b1adcf97fd9a6481e556b02bf83c2c59f - md5: e768e0a1c90d00377e8531e27359c0aa + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1861583 + timestamp: 1746625308090 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda + sha256: ecf778f886aaf50db22c0971fb0873f0dbe25663f124bd714bc87b4d0925f534 + md5: 18a20cb8c3e19f0b3799a48eba5b44aa depends: - - __osx >=11.0 - - khronos-opencl-icd-loader >=2024.10.24 - - libcxx >=19 - - numpy >=1.23,<3 - - ocl_icd_wrapper_apple + - __osx >=10.13 + - libffi >=3.5.2,<3.6.0a0 - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - scipy - license: LGPL-3.0-or-later - license_family: LGPL + - setuptools + license: MIT + license_family: MIT purls: - - pkg:pypi/openmm?source=hash-mapping - size: 11119876 - timestamp: 1765254816298 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - sha256: 44c877f8af015332a5d12f5ff0fb20ca32f896526a7d0cdb30c769df1144fb5c - md5: f61eb8cd60ff9057122a3d338b99c00f - depends: - - __glibc >=2.17,<3.0.a0 - - ca-certificates - - libgcc >=14 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 3164551 - timestamp: 1769555830639 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - sha256: e02e5639b0e4d6d4fcf0f3b082642844fb5a37316f5b0a1126c6271347462e90 - md5: 30bb8d08b99b9a7600d39efb3559fff0 + - pkg:pypi/pyobjc-core?source=hash-mapping + size: 487397 + timestamp: 1763151480498 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda + sha256: 3a29ca3cc2044b408447ff86ae0c57ecc3ff805a8fc838525610921024c8521a + md5: b6881a919e1bfd66349e2260b163dc7c depends: - __osx >=10.13 - - ca-certificates - license: Apache-2.0 - license_family: Apache - purls: [] - size: 2777136 - timestamp: 1769557662405 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - sha256: 361f5c5e60052abc12bdd1b50d7a1a43e6a6653aab99a2263bf2288d709dcf67 - md5: f4f6ad63f98f64191c3e77c5f5f29d76 - depends: - - __osx >=11.0 - - ca-certificates - license: Apache-2.0 - license_family: Apache - purls: [] - size: 3104268 - timestamp: 1769556384749 -- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.19.0-py312hd9148b4_0.conda - sha256: e5cb326247948543cc2b0495e5da4d9b724ebd6e6b6907c9ed6adc1b969aeb2a - md5: 6103697b406b6605e744623d9f3d0e0b - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - typing-extensions >=4.12 - license: Apache-2.0 - license_family: Apache + license: MIT + license_family: MIT purls: - - pkg:pypi/optree?source=compressed-mapping - size: 482838 - timestamp: 1771868357488 -- conda: https://conda.anaconda.org/conda-forge/osx-64/optree-0.19.0-py312hdb80668_0.conda - sha256: 2d43c43b3e8e7a6a32dc203ad63884d8e9be46dc0afed1e2dc42dc5e23759ac6 - md5: 2718bc7708f21ae564088ae31b6e44ad + - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping + size: 375580 + timestamp: 1763160526695 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda + sha256: 867a6020921707c8d9ef89439c04708ec973a69961e0180c7a208dc814a181f6 + md5: 5dd692ed6d1dfa3dc6c6bf4166357468 depends: - __osx >=11.0 + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - c-blosc2 >=2.23.0,<2.24.0a0 + - hdf5 >=1.14.6,<1.14.7.0a0 - libcxx >=19 + - libzlib >=1.3.1,<2.0a0 + - numexpr + - numpy >=1.20.0 + - numpy >=1.23,<3 + - packaging + - py-cpuinfo - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - typing-extensions >=4.12 - license: Apache-2.0 - license_family: Apache + - typing-extensions >=4.4.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/optree?source=compressed-mapping - size: 453146 - timestamp: 1771868875323 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda - sha256: 76ea1f736255aeea8b917ea826a0c186ab267656b156709ca374df5dc8ff4d26 - md5: a98fa17883d7862ef3049f171a03b9dc + - pkg:pypi/tables?source=hash-mapping + size: 1521681 + timestamp: 1772372001607 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda + sha256: fb592ceb1bc247d19247d5535083da4a79721553e29e1290f5d81c07d4f086b5 + md5: ec05996c0d914a4e98ee3c7d789083f8 depends: - __osx >=11.0 - - libcxx >=19 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: - python_abi 3.12.* *_cp312 - - typing-extensions >=4.12 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/optree?source=compressed-mapping - size: 429640 - timestamp: 1771868574672 -- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c - md5: e51f1e4089cad105b6cac64bd8166587 - depends: - - python >=3.9 - - typing_utils - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/overrides?source=hash-mapping - size: 30139 - timestamp: 1734587755455 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 - md5: b76541e68fea4d511b1ac46a28dcd2c6 - depends: - - python >=3.8 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/packaging?source=compressed-mapping - size: 72010 - timestamp: 1769093650580 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda - sha256: f633d5f9b28e4a8f66a6ec9c89ef1b6743b880b0511330184b4ab9b7e2dda247 - md5: e597b3e812d9613f659b7d87ad252d18 + license: Python-2.0 + purls: [] + size: 13672169 + timestamp: 1772730464626 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda + sha256: a60026131d74959c0df4d4cdf97ec0ef09ee5cdfcdafaac384c069068dc14f89 + md5: c93b3e22a5b37490641d0f1690805e35 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - numpy >=1.22.4 + - __osx >=11.0 + - filelock + - fsspec + - jinja2 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libtorch 2.8.0.* *_2 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=19.1.7 + - networkx + - nomkl - numpy >=1.23,<3 + - optree >=0.13.0 + - pybind11 + - pybind11-abi 4 - python >=3.12,<3.13.0a0 - - python-dateutil >=2.8.2 - - python-tzdata >=2022.7 - python_abi 3.12.* *_cp312 - - pytz >=2020.1 + - setuptools + - sleef >=3.9.0,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.10.0 constrains: - - xarray >=2022.12.0 - - qtpy >=2.3.0 - - html5lib >=1.1 - - pandas-gbq >=0.19.0 - - tzdata >=2022.7 - - fsspec >=2022.11.0 - - fastparquet >=2022.12.0 - - odfpy >=1.4.1 - - pyxlsb >=1.0.10 - - scipy >=1.10.0 - - sqlalchemy >=2.0.0 - - pytables >=3.8.0 - - bottleneck >=1.3.6 - - pyarrow >=10.0.1 - - numexpr >=2.8.4 - - pyqt5 >=5.15.9 - - xlsxwriter >=3.0.5 - - openpyxl >=3.1.0 - - blosc >=1.21.3 - - matplotlib >=3.6.3 - - lxml >=4.9.2 - - numba >=0.56.4 - - s3fs >=2022.11.0 - - tabulate >=0.9.0 - - xlrd >=2.0.1 - - gcsfs >=2022.11.0 - - pyreadstat >=1.2.0 - - python-calamine >=0.1.7 - - zstandard >=0.19.0 - - psycopg2 >=2.9.6 - - beautifulsoup4 >=4.11.2 + - pytorch-cpu 2.8.0 + - pytorch-gpu <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/torch?source=hash-mapping + size: 23954998 + timestamp: 1762106725041 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda + sha256: d85e3be523b7173a194a66ae05a585ac1e14ccfbe81a9201b8047d6e45f2f7d9 + md5: 9029301bf8a667cf57d6e88f03a6726b + depends: + - __osx >=10.13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 190417 + timestamp: 1770223755226 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda + noarch: python + sha256: 475d5a751740eef86b4469b73759a42bcf82abb292fde7506081196378552cf3 + md5: 98bc7fb12f6efc9c08eeeac21008a199 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - _python_abi3_support 1.* + - cpython >=3.12 + - zeromq >=4.3.5,<4.4.0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pandas?source=hash-mapping - size: 15099922 - timestamp: 1759266031115 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.3.3-py312h86abcb1_2.conda - sha256: 112273ffd9572a4733c98b9d80a243f38db4d0fce5d34befaf9eb6f64ed39ba3 - md5: d7dfad2b9a142319cec4736fe88d8023 + - pkg:pypi/pyzmq?source=hash-mapping + size: 192884 + timestamp: 1771717048943 +- conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda + sha256: 79d804fa6af9c750e8b09482559814ae18cd8df549ecb80a4873537a5a31e06e + md5: dd1ea9ff27c93db7c01a7b7656bd4ad4 + depends: + - __osx >=10.13 + - libcxx >=16 + license: LicenseRef-Qhull + purls: [] + size: 528122 + timestamp: 1720814002588 +- conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda + sha256: 0bc438d858884ef4880bd611e6e6e1fd4b8cab6b5eb5c44dd1282056f4aa6cb7 + md5: cda1367cdd3d4e3a98ec7bfc5776ac3c depends: - __osx >=10.13 + - cairo >=1.18.4,<2.0a0 + - libboost >=1.86.0,<1.87.0a0 + - libboost-python >=1.86.0,<1.87.0a0 - libcxx >=19 - - numpy >=1.22.4 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libpq >=18.1,<19.0a0 + - librdkit 2025.09.5 ha1af57f_0 + - matplotlib-base - numpy >=1.23,<3 + - pandas + - pillow + - pycairo - python >=3.12,<3.13.0a0 - - python-dateutil >=2.8.2 - - python-tzdata >=2022.7 - python_abi 3.12.* *_cp312 - - pytz >=2020.1 - constrains: - - pyarrow >=10.0.1 - - tabulate >=0.9.0 - - html5lib >=1.1 - - s3fs >=2022.11.0 - - pandas-gbq >=0.19.0 - - matplotlib >=3.6.3 - - qtpy >=2.3.0 - - scipy >=1.10.0 - - zstandard >=0.19.0 - - bottleneck >=1.3.6 - - numexpr >=2.8.4 - - pyxlsb >=1.0.10 - - tzdata >=2022.7 - - psycopg2 >=2.9.6 - - pytables >=3.8.0 - - fsspec >=2022.11.0 - - python-calamine >=0.1.7 - - xarray >=2022.12.0 - - numba >=0.56.4 - - pyqt5 >=5.15.9 - - xlrd >=2.0.1 - - blosc >=1.21.3 - - odfpy >=1.4.1 - - openpyxl >=3.1.0 - - fastparquet >=2022.12.0 - - xlsxwriter >=3.0.5 - - pyreadstat >=1.2.0 - - sqlalchemy >=2.0.0 - - gcsfs >=2022.11.0 - - beautifulsoup4 >=4.11.2 - - lxml >=4.9.2 + - reportlab + - sqlalchemy license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pandas?source=hash-mapping - size: 14008759 - timestamp: 1764615365220 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda - sha256: 93aa5b02e2394080a32fee9fb151da3384d317a42472586850abb37b28f314db - md5: fcbba82205afa4956c39136c68929385 + - pkg:pypi/rdkit?source=hash-mapping + size: 19131272 + timestamp: 1770019208091 +- conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + sha256: 4614af680aa0920e82b953fece85a03007e0719c3399f13d7de64176874b80d5 + md5: eefd65452dfe7cce476a519bece46704 + depends: + - __osx >=10.13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 317819 + timestamp: 1765813692798 +- conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda + sha256: 3df6f3ad2697f5250d38c37c372b77cc2702b0c705d3d3a231aae9dc9f2eec62 + md5: 9adbe03b6d1b86cab37fb37709eb4e38 + depends: + - python + - __osx >=10.13 + - python_abi 3.12.* *_cp312 + constrains: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 370624 + timestamp: 1764543158734 +- conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda + sha256: 81842a4b39f700e122c8ba729034c7fc7b3a6bfd8d3ca41fe64e2bc8b0d1a4f4 + md5: 07c955303eea8d00535164eb5f63ee97 depends: - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 - libcxx >=19 - - numpy >=1.22.4 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 - numpy >=1.23,<3 + - numpy >=1.25.2 - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python-dateutil >=2.8.2 - - python-tzdata >=2022.7 - python_abi 3.12.* *_cp312 - - pytz >=2020.1 - constrains: - - xarray >=2022.12.0 - - scipy >=1.10.0 - - tabulate >=0.9.0 - - pytables >=3.8.0 - - xlsxwriter >=3.0.5 - - pyxlsb >=1.0.10 - - odfpy >=1.4.1 - - zstandard >=0.19.0 - - fastparquet >=2022.12.0 - - gcsfs >=2022.11.0 - - beautifulsoup4 >=4.11.2 - - qtpy >=2.3.0 - - xlrd >=2.0.1 - - pandas-gbq >=0.19.0 - - s3fs >=2022.11.0 - - pyreadstat >=1.2.0 - - tzdata >=2022.7 - - html5lib >=1.1 - - fsspec >=2022.11.0 - - lxml >=4.9.2 - - numexpr >=2.8.4 - - blosc >=1.21.3 - - openpyxl >=3.1.0 - - pyarrow >=10.0.1 - - python-calamine >=0.1.7 - - numba >=0.56.4 - - sqlalchemy >=2.0.0 - - pyqt5 >=5.15.9 - - psycopg2 >=2.9.6 - - bottleneck >=1.3.6 - - matplotlib >=3.6.3 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pandas?source=hash-mapping - size: 13893993 - timestamp: 1764615503244 -- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f - md5: 457c2c8c08e54905d6954e79cb5b5db9 + - pkg:pypi/scipy?source=hash-mapping + size: 15312767 + timestamp: 1771881124085 +- conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda + sha256: 7b6749d48be1cea8e4191141b35fe667f776e0b0972d7cf286b276c9bbb57d9d + md5: 97fc81ba1dc812fb37000fe39afa3bf8 depends: - - python !=3.0,!=3.1,!=3.2,!=3.3 + - __osx >=10.13 + - libcxx >=19 + - llvm-openmp >=19.1.7 + license: BSL-1.0 + purls: [] + size: 1484394 + timestamp: 1756274644799 +- conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda + sha256: 1525e6d8e2edf32dabfe2a8e2fc8bf2df81c5ef9f0b5374a3d4ccfa672bfd949 + md5: 2e993292ec18af5cd480932d448598cf + depends: + - libcxx >=19 + - __osx >=10.13 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/pandocfilters?source=hash-mapping - size: 11627 - timestamp: 1631603397334 -- conda: https://conda.anaconda.org/conda-forge/linux-64/parmed-4.3.1-py312h7ab193c_0.conda - sha256: 245ac71a689e9d4aed0afcfb408b8fcfc8707c7eead413bb39ece091c09c248a - md5: 5abf27d6d75333d890d1ff16bc087bf3 + purls: [] + size: 40023 + timestamp: 1762948053450 +- conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda + sha256: 36455c443ecc0ae2e6886c9fb3237e29d0f2378ab6f175e28822abca534c70fc + md5: c1cee82f5cdb8120930947da5e1e2675 depends: - - libgcc >=14 - - libstdcxx >=14 - - numpy >=1.14 - - pandas - - python >=3.12,<3.13.0a0 + - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - __osx >=11.0 - python_abi 3.12.* *_cp312 - license: LGPL-2.1-or-later - license_family: LGPL + license: MIT + license_family: MIT purls: - - pkg:pypi/parmed?source=hash-mapping - size: 19363186 - timestamp: 1769768006554 -- conda: https://conda.anaconda.org/conda-forge/osx-64/parmed-4.3.1-py312h26b8372_0.conda - sha256: 253779ebb5141c773f5b9778f7fd1b3723cf84818f90b1db720a984ceb07f949 - md5: 8b68981a00e4856c168e44a02c59da40 + - pkg:pypi/sqlalchemy?source=hash-mapping + size: 3691545 + timestamp: 1772644924605 +- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + sha256: 7f0d9c320288532873e2d8486c331ec6d87919c9028208d3f6ac91dc8f99a67b + md5: 6e6efb7463f8cef69dbcb4c2205bf60e depends: - - libcxx >=19 - - numpy >=1.14 - - pandas + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3282953 + timestamp: 1769460532442 +- conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda + sha256: 44ba44075b754a0da5a476d5cdc6783e290d3f26d355c9fc236abaaefa902d4d + md5: fc935f8c37abef2b3cc3b9f15b951c6d + depends: + - __osx >=11.0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - license: LGPL-2.1-or-later - license_family: LGPL + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/parmed?source=hash-mapping - size: 19253982 - timestamp: 1769768130965 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda - sha256: cfcfaf36fd9eae9f995e535b87f5762343589ce13e760b477128da7fdf1fd367 - md5: cb620cbf05811cd2b2ea088d3417b028 + - pkg:pypi/tornado?source=hash-mapping + size: 854453 + timestamp: 1765836802876 +- conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda + sha256: 29bdc3648a4b8011c572003234ebfaa13666aa71760c9f89f798758ccebd7448 + md5: 563dc18b746f873408b76e068e2b4baa depends: - - libcxx >=19 - - numpy >=1.14 - - pandas + - __osx >=10.13 - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: LGPL-2.1-or-later - license_family: LGPL + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/parmed?source=hash-mapping - size: 19381423 - timestamp: 1769768478779 -- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - sha256: 42b2d77ccea60752f3aa929a6413a7835aaacdbbde679f2f5870a744fa836b94 - md5: 97c1ce2fffa1209e7afb432810ec6e12 + - pkg:pypi/unicodedata2?source=hash-mapping + size: 406056 + timestamp: 1770909495553 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda + sha256: ab190f758a1d7cf2bdd3656e6eb90b7316cdd03a32214638f691e02ad798aaed + md5: d894608e2c18127545d67a096f1b4bab depends: - - python >=3.10 - - python + - __osx >=10.13 license: MIT license_family: MIT - purls: - - pkg:pypi/parso?source=hash-mapping - size: 82287 - timestamp: 1770676243987 -- pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - name: pathspec - version: 1.0.4 - sha256: fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 - requires_dist: - - hyperscan>=0.7 ; extra == 'hyperscan' - - typing-extensions>=4 ; extra == 'optional' - - google-re2>=1.1 ; extra == 're2' - - pytest>=9 ; extra == 'tests' - - typing-extensions>=4.15 ; extra == 'tests' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - sha256: 5e6f7d161356fefd981948bea5139c5aa0436767751a6930cb1ca801ebb113ff - md5: 7a3bff861a6583f1889021facefc08b1 + purls: [] + size: 50154 + timestamp: 1734227708757 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda + sha256: 9f0cb0a0a94a76f07ed449ee404c83fb91e77cd732cd0dcff395e90cc02338ef + md5: 267dc632a1c41345622c935bb6026dc4 depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD + - __osx >=10.13 + - xorg-libice >=1.1.2,<2.0a0 + license: MIT + license_family: MIT purls: [] - size: 1222481 - timestamp: 1763655398280 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda - sha256: 8d64a9d36073346542e5ea042ef8207a45a0069a2e65ce3323ee3146db78134c - md5: 08f970fb2b75f5be27678e077ebedd46 + size: 24556 + timestamp: 1741896589948 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda + sha256: 579c55d463309b4c1f13213ee64546b341c5ca8d4b21afed7c5c7c2475781e57 + md5: 186cc85e36539d096fd18a221053ec1f depends: - __osx >=10.13 - - bzip2 >=1.0.8,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT purls: [] - size: 1106584 - timestamp: 1763655837207 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda - sha256: 5e2e443f796f2fd92adf7978286a525fb768c34e12b1ee9ded4000a41b2894ba - md5: 9b4190c4055435ca3502070186eba53a + size: 785295 + timestamp: 1770819968527 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda + sha256: 928f28bd278c7da674b57d71b2e7f4ac4e7c7ce56b0bf0f60d6a074366a2e76d + md5: 47f1b8b4a76ebd0cd22bd7153e54a4dc depends: - - __osx >=11.0 - - bzip2 >=1.0.8,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD + - __osx >=10.13 + license: MIT + license_family: MIT purls: [] - size: 850231 - timestamp: 1763655726735 -- conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda - build_number: 7 - sha256: 9ec32b6936b0e37bcb0ed34f22ec3116e75b3c0964f9f50ecea5f58734ed6ce9 - md5: f2cfec9406850991f4e3d960cc9e3321 + size: 13810 + timestamp: 1762977180568 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda + sha256: b7b291cc5fd4e1223058542fca46f462221027779920dd433d68b98e858a4afc + md5: 435446d9d7db8e094d2c989766cfb146 depends: - - libgcc-ng >=12 - - libxcrypt >=4.4.36 - license: GPL-1.0-or-later OR Artistic-1.0-Perl + - __osx >=10.13 + license: MIT + license_family: MIT purls: [] - size: 13344463 - timestamp: 1703310653947 -- conda: https://conda.anaconda.org/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda - build_number: 7 - sha256: 8ebd35e2940055a93135b9fd11bef3662cecef72d6ee651f68d64a2f349863c7 - md5: dc442e0885c3a6b65e61c61558161a9e - license: GPL-1.0-or-later OR Artistic-1.0-Perl + size: 19067 + timestamp: 1762977101974 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda + sha256: 9dd99f57c23ecb0629e18155162c49b2a874d40f6014ae249722e767cb5168a9 + md5: be170f3b869f5cd0e517aac01898129c + depends: + - __osx >=10.13 + - xorg-libx11 >=1.8.12,<2.0a0 + license: MIT + license_family: MIT purls: [] - size: 12334471 - timestamp: 1703311001432 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda - build_number: 7 - sha256: b0c55040d2994fd6bf2f83786561d92f72306d982d6ea12889acad24a9bf43b8 - md5: ba3cbe93f99e896765422cc5f7c3a79e - license: GPL-1.0-or-later OR Artistic-1.0-Perl + size: 43673 + timestamp: 1769446004494 +- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda + sha256: c1bfd12f50739d4fbf3dc47bd8f0e584e2797a6214bf2c0a2eef0cc6eaed5c65 + md5: 31f0e8aa8a497df5461768a15898dbad + depends: + - __osx >=10.13 + - xorg-libice >=1.1.1,<2.0a0 + - xorg-libsm >=1.2.4,<2.0a0 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT purls: [] - size: 14439531 - timestamp: 1703311335652 -- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a - md5: d0d408b1f18883a944376da5cf8101ea + size: 199297 + timestamp: 1731860558365 +- conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda + sha256: a335161bfa57b64e6794c3c354e7d49449b28b8d8a7c4ed02bf04c3f009953f9 + md5: a645bb90997d3fc2aea0adf6517059bd depends: - - ptyprocess >=0.5 - - python >=3.9 - license: ISC - purls: - - pkg:pypi/pexpect?source=hash-mapping - size: 53561 - timestamp: 1733302019362 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py312h50c33e8_0.conda - sha256: 782b6b578a0e61f6ef5cca5be993d902db775a2eb3d0328a3c4ff515858e7f2c - md5: c5eff3ada1a829f0bdb780dc4b62bbae + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 79419 + timestamp: 1753484072608 +- conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda + sha256: 30aa5a2e9c7b8dbf6659a2ccd8b74a9994cdf6f87591fcc592970daa6e7d3f3c + md5: d940d809c42fbf85b05814c3290660f5 depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libjpeg-turbo >=3.1.2,<4.0a0 - - tk >=8.6.13,<8.7.0a0 - - libxcb >=1.17.0,<2.0a0 - - libwebp-base >=1.6.0,<2.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - lcms2 >=2.18,<3.0a0 - - python_abi 3.12.* *_cp312 - - zlib-ng >=2.3.3,<2.4.0a0 - - libtiff >=4.7.1,<4.8.0a0 - - openjpeg >=2.5.4,<3.0a0 - license: HPND - purls: - - pkg:pypi/pillow?source=compressed-mapping - size: 1029755 - timestamp: 1770794002406 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.1.1-py312h4985050_0.conda - sha256: a2a48fdaa95921c4178ae16f4663bdaa74a2c93bdf45c8221f328692bb2e083b - md5: b1fcc4538203c9c747879d3e461f2f74 + - __osx >=10.13 + - libcxx >=19 + - libsodium >=1.0.20,<1.0.21.0a0 + - krb5 >=1.21.3,<1.22.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 259628 + timestamp: 1757371000392 +- conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda + sha256: d46ba65a5ebcb4f682f9f0f21943c427eb56e7f9d15aeab8b823294c787dbb0b + md5: c67ad1f1d33a7de2dd34e55c01a21eca + depends: + - __osx >=11.0 + - libzlib 1.3.2 hbb4bfdb_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 93115 + timestamp: 1785276829908 +- conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda + sha256: 4a1beb656761c7d8c9a53474bfd3932c30d82af5d93a32b8ef626c01c059d981 + md5: b3ecb6480fd46194e3f7dd0ff4445dff depends: - - python - __osx >=10.13 - - tk >=8.6.13,<8.7.0a0 - - libwebp-base >=1.6.0,<2.0a0 - - libxcb >=1.17.0,<2.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libjpeg-turbo >=3.1.2,<4.0a0 - - libtiff >=4.7.1,<4.8.0a0 + - libcxx >=19 + license: Zlib + license_family: Other + purls: [] + size: 120464 + timestamp: 1770168263684 +- conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda + sha256: 47101a4055a70a4876ffc87b750ab2287b67eca793f21c8224be5e1ee6394d3f + md5: 727109b184d680772e3122f40136d5ca + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 528148 + timestamp: 1764777156963 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: 7acaa2e0782cad032bdaf756b536874346ac1375745fb250e9bdd6a48a7ab3cd + md5: a44032f282e7d2acdeb1c240308052dd + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 8325 + timestamp: 1764092507920 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ambertools-26.0-cuda_None_nompi_py312h52216ed_100.conda + sha256: a11237fe5f51e06c8b8167e2524ab7fc06e83b7b889cf8ebf0d5847c3ea65691 + md5: b1ece4272978a9c2e5e261fd57c36222 + depends: + - __osx >=11.0 + - arpack >=3.9.1,<3.10.0a0 nompi_* + - biopython + - bzip2 >=1.0.8,<2.0a0 + - fftw >=3.3.11,<4.0a0 + - freesasa + - gemmi >=0.7.5,<0.7.6.0a0 + - joblib + - libblas >=3.9.0,<4.0a0 + - libboost >=1.86.0,<1.87.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - libnetcdf >=4.10.1,<4.10.2.0a0 + - libzlib >=1.3.2,<2.0a0 + - llvm-openmp >=19.1.7 + - matplotlib-base + - netcdf-fortran >=4.6.3,<4.7.0a0 + - networkx + - nlopt >=2.11.0,<2.12.0a0 + - numba + - numpy >=1.23,<3 + - openssl >=3.5.7,<4.0a0 + - parmed >=4.0.0,<5.0.0a0 + - perl + - psutil + - pydantic + - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - zlib-ng >=2.3.3,<2.4.0a0 - - lcms2 >=2.18,<3.0a0 - - openjpeg >=2.5.4,<3.0a0 - license: HPND + - readline >=8.3,<9.0a0 + - rich + - scikit-learn + - scipy + - setuptools + - sympy + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxt >=1.3.1,<2.0a0 + constrains: + - ambermini ==9999999999 + - packmol ==9999999999 + license: GPL-3.0-or-later AND LGPL-3.0-or-later AND BSD-3-Clause AND MIT purls: - - pkg:pypi/pillow?source=compressed-mapping - size: 964529 - timestamp: 1770794115028 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda - sha256: 4729476631c025dfce555a5fd97f1b0b97e765e7c01aee5b7e59b880d8335006 - md5: 537e6079e50e219252d016254b0d2573 + - pkg:pypi/amberutils?source=hash-mapping + - pkg:pypi/edgembar?source=hash-mapping + - pkg:pypi/fetkutils?source=hash-mapping + - pkg:pypi/mmpbsa-py?source=hash-mapping + - pkg:pypi/ndfes?source=hash-mapping + - pkg:pypi/packmol-memgen?source=hash-mapping + - pkg:pypi/pdb4amber?source=hash-mapping + - pkg:pypi/proprep?source=hash-mapping + - pkg:pypi/pymsmt?source=hash-mapping + - pkg:pypi/pytraj?source=hash-mapping + - pkg:pypi/rismtools?source=hash-mapping + - pkg:pypi/sander?source=hash-mapping + run_exports: {} + size: 97296879 + timestamp: 1784083518523 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + sha256: 24c475f6f7abf03ef3cc2ac572b7a6d713bede00ef984591be92cdc439b09fbc + md5: 0a2a07b42db3f92b8dccf0f60b5ebee8 depends: - - python - __osx >=11.0 - - python 3.12.* *_cpython - - libwebp-base >=1.6.0,<2.0a0 - - openjpeg >=2.5.4,<3.0a0 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - cffi >=1.0.1 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - libjpeg-turbo >=3.1.2,<4.0a0 - - tk >=8.6.13,<8.7.0a0 - - libxcb >=1.17.0,<2.0a0 - - libtiff >=4.7.1,<4.8.0a0 - - zlib-ng >=2.3.3,<2.4.0a0 - - lcms2 >=2.18,<3.0a0 - license: HPND + license: MIT + license_family: MIT purls: - - pkg:pypi/pillow?source=compressed-mapping - size: 954096 - timestamp: 1770794152238 -- conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.2-pyhcf101f3_0.conda - sha256: 9fbaf42c68eeecd36e578cd39c16a9f8d4f2ecb6bf80d087bd08c88e48ccab4d - md5: e8d84977b2cab87277e1ac38173fe69c + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 34224 + timestamp: 1762509989973 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/arpack-3.9.1-nompi_h1f29f7c_102.conda + sha256: 314fb780a811d1bcd31b34266016d1d49e754e37a476f8b18d8ea81599865444 + md5: 7da333ca69131caf002baf360f5de7f9 depends: - - python >=3.11 - - platformdirs >=2.1.0 - - flexcache >=0.3 - - flexparser >=0.4 - - typing_extensions >=4.0.0 - - python - constrains: - - numpy >=1.23 + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcxx >=18 + - libgfortran >=5 + - libgfortran5 >=13.2.0 + - liblapack >=3.9.0,<4.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/pint?source=hash-mapping - size: 244993 - timestamp: 1762481838471 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - sha256: 43d37bc9ca3b257c5dd7bf76a8426addbdec381f6786ff441dc90b1a49143b6a - md5: c01af13bdc553d1a8fbfff6e8db075f0 + purls: [] + size: 120348 + timestamp: 1736084183877 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.4-h436dbe7_2.conda + sha256: bea3d014ebc03803cd18b1df1c7cbeb2fb7cf260126030fe7dc18a6bf674d131 + md5: b9494d6793762d9f9838420c8e73c8d9 depends: - - libgcc >=14 - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: MIT - license_family: MIT + - __osx >=11.0 + - aws-c-cal >=0.9.15,<0.9.16.0a0 + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + - aws-c-io >=0.27.5,<0.27.6.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + license: Apache-2.0 + license_family: APACHE purls: [] - size: 450960 - timestamp: 1754665235234 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda - sha256: ff8b679079df25aa3ed5daf3f4e3a9c7ee79e7d4b2bd8a21de0f8e7ec7207806 - md5: 742a8552e51029585a32b6024e9f57b4 + run_exports: + weak: + - aws-c-auth >=0.10.4,<0.10.5.0a0 + size: 117346 + timestamp: 1785199104682 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.15-hdbc54b2_1.conda + sha256: f60b9b4b9693ce86e335c268638c299804ca9cfd7ffb3b96415dcac52c1bd13b + md5: 70d1d0405910b78d06d74a8bba0013d2 depends: - - __osx >=10.13 - - libcxx >=19 - license: MIT - license_family: MIT + - __osx >=11.0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + license: Apache-2.0 + license_family: Apache purls: [] - size: 390942 - timestamp: 1754665233989 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda - sha256: 29c9b08a9b8b7810f9d4f159aecfd205fce051633169040005c0b7efad4bc718 - md5: 17c3d745db6ea72ae2fce17e7338547f + run_exports: + weak: + - aws-c-cal >=0.9.15,<0.9.16.0a0 + size: 46154 + timestamp: 1785158287452 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.14.3-h84a0fba_0.conda + sha256: e46cbb8c24626da9792f7eebae86b3e8fa611e039945be15a78a70015eeae17e + md5: 0097da38976713a53acb6332cc9de888 depends: - __osx >=11.0 - - libcxx >=19 - license: MIT - license_family: MIT + license: Apache-2.0 + license_family: Apache purls: [] - size: 248045 - timestamp: 1754665282033 -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - sha256: 7f263219cecf0ba6d74c751efa60c4676ce823157ca90aa43ebba5ac615ca0fa - md5: 4fefefb892ce9cc1539405bec2f1a6cd + run_exports: + weak: + - aws-c-common >=0.14.3,<0.14.4.0a0 + size: 228913 + timestamp: 1784596525019 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-he8604bc_5.conda + sha256: 857df6c5048c206e4c424100f1a649b7ef5ee3b01cf6207ff3ae35fe61642478 + md5: 139a1ac73d9e111cb3f73d07958a1130 depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/platformdirs?source=compressed-mapping - size: 25643 - timestamp: 1771233827084 -- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - name: pluggy - version: 1.6.0 - sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - requires_dist: - - pre-commit ; extra == 'dev' - - tox ; extra == 'dev' - - pytest ; extra == 'testing' - - pytest-benchmark ; extra == 'testing' - - coverage ; extra == 'testing' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - name: pre-commit - version: 4.5.1 - sha256: 3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 - requires_dist: - - cfgv>=2.0.0 - - identify>=1.0.0 - - nodeenv>=0.11.1 - - pyyaml>=5.1 - - virtualenv>=20.10.0 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda - sha256: 75b2589159d04b3fb92db16d9970b396b9124652c784ab05b66f584edc97f283 - md5: 7526d20621b53440b0aae45d4797847e + - __osx >=11.0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-compression >=0.3.2,<0.3.3.0a0 + size: 21777 + timestamp: 1785157595687 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.11.0-hc1b69ad_5.conda + sha256: 1d8dbd1cea250a1d2d932294bf7e9d5c9e5efc9e09993dc883885e0ff2447fd3 + md5: d7d04ddaf9dae601a9a77c547e7d09ab depends: - - python >=3.10 + - __osx >=11.0 + - aws-c-cal >=0.9.15,<0.9.16.0a0 + - aws-c-compression >=0.3.2,<0.3.3.0a0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + - aws-c-io >=0.27.5,<0.27.6.0a0 license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/prometheus-client?source=compressed-mapping - size: 56634 - timestamp: 1768476602855 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae - md5: edb16f14d920fb3faf17f5ce582942d6 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-http >=0.11.0,<0.11.1.0a0 + size: 177804 + timestamp: 1785186207384 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.27.5-h8fb7669_1.conda + sha256: 7b346b8bd32c1f013d231922a9413d45c152d5c533c994bb4fb76c5f4e1ad20b + md5: 4fa643897c9481edc41aca4a7597a653 depends: - - python >=3.10 - - wcwidth - constrains: - - prompt_toolkit 3.0.52 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/prompt-toolkit?source=hash-mapping - size: 273927 - timestamp: 1756321848365 -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda - sha256: d834fd656133c9e4eaf63ffe9a117c7d0917d86d89f7d64073f4e3a0020bd8a7 - md5: dd94c506b119130aef5a9382aed648e7 + - __osx >=11.0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + - s2n >=1.7.6,<1.7.7.0a0 + - aws-c-cal >=0.9.15,<0.9.16.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-io >=0.27.5,<0.27.6.0a0 + size: 190778 + timestamp: 1785170135436 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.13.1-h1fbe2f8_1.conda + sha256: fe906dcd9cfac79c61bf60927f9e9dad328c9fbb9a2204ffb0b3c955c33817f0 + md5: bbdbec4b0952af625b066672514c0eb7 + depends: + - __osx >=11.0 + - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-io >=0.27.5,<0.27.6.0a0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + - aws-c-cal >=0.9.15,<0.9.16.0a0 + - aws-c-auth >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-s3 >=0.13.1,<0.13.2.0a0 + size: 134318 + timestamp: 1785351362781 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.7-he8604bc_3.conda + sha256: 16b3cd17f3806866c74fe7fe7badaf511293766f1356b4f491f88eb98ff4da16 + md5: b87573f533751acc5de162eced05e3b1 depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=compressed-mapping - size: 225545 - timestamp: 1769678155334 -- conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.2-py312hf7082af_0.conda - sha256: 517c17b24349476535db4da7d1cd31538dadf2c77f9f7f7d8be6b7dc5dfbb636 - md5: 1fd947fae149960538fc941b8f122bc1 + - __osx >=11.0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + size: 60776 + timestamp: 1785159351292 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-he8604bc_5.conda + sha256: 1f301d04c67c63b5e31b3ac5deaa20749cb86b6eadff04fdf9c79599ce9dd3d0 + md5: 3864eab4cfa7f8d524d558c52db32708 + depends: + - __osx >=11.0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-checksums >=0.2.10,<0.2.11.0a0 + size: 92236 + timestamp: 1785159288972 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda + sha256: aee745bfca32f7073d3298157bbb2273d6d83383cb266840cf0a7862b3cd8efc + md5: c2d5961bfd98504b930e704426d16572 depends: - python - - __osx >=10.13 + - python 3.12.* *_cpython + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 - python_abi 3.12.* *_cp312 - license: BSD-3-Clause - license_family: BSD + license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/psutil?source=hash-mapping - size: 236338 - timestamp: 1769678402626 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda - sha256: 6d0e21c76436374635c074208cfeee62a94d3c37d0527ad67fd8a7615e546a05 - md5: fd856899666759403b3c16dcba2f56ff + - pkg:pypi/backports-zstd?source=hash-mapping + size: 241051 + timestamp: 1767045000787 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/biopython-1.88-py312h2bbb03f_0.conda + sha256: 3231e2e689cc05923d7621a93bf9d7d4764011e50f577d9ca49aa30412c6d28d + md5: da8cdacf620ff73180fd94c1ac9523da depends: - - python - __osx >=11.0 - - python 3.12.* *_cpython + - numpy + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 + license: LicenseRef-Biopython + purls: + - pkg:pypi/biopython?source=hash-mapping + run_exports: {} + size: 3224840 + timestamp: 1786019643818 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda + sha256: c3fe902114b9a3ac837e1a32408cc2142c147ec054c1038d37aec6814343f48a + md5: 925acfb50a750aa178f7a0aced77f351 + depends: + - __osx >=11.0 + - libcxx >=18 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - snappy >=1.2.1,<1.3.0a0 + - zstd >=1.5.6,<1.6.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 239031 - timestamp: 1769678393511 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 - md5: b3c17d95b5a10c6e64a21fa17573e70e - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: MIT - license_family: MIT purls: [] - size: 8252 - timestamp: 1726802366959 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-h00291cd_1002.conda - sha256: 05944ca3445f31614f8c674c560bca02ff05cb51637a96f665cb2bbe496099e5 - md5: 8bcf980d2c6b17094961198284b8e862 + size: 33602 + timestamp: 1733513285902 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + sha256: 422ac5c91f8ef07017c594d9135b7ae068157393d2a119b1908c7e350938579d + md5: 48ece20aa479be6ac9a284772827d00c depends: - - __osx >=10.13 + - __osx >=11.0 + - brotli-bin 1.2.0 hc919400_1 + - libbrotlidec 1.2.0 hc919400_1 + - libbrotlienc 1.2.0 hc919400_1 license: MIT license_family: MIT purls: [] - size: 8364 - timestamp: 1726802331537 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda - sha256: 8ed65e17fbb0ca944bfb8093b60086e3f9dd678c3448b5de212017394c247ee3 - md5: 415816daf82e0b23a736a069a75e9da7 + size: 20237 + timestamp: 1764018058424 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + sha256: e2d142052a83ff2e8eab3fe68b9079cad80d109696dc063a3f92275802341640 + md5: 377d015c103ad7f3371be1777f8b584c depends: - __osx >=11.0 + - libbrotlidec 1.2.0 hc919400_1 + - libbrotlienc 1.2.0 hc919400_1 license: MIT license_family: MIT purls: [] - size: 8381 - timestamp: 1726802424786 -- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 - md5: 7d9daffbb8d8e0af0f769dbbcd173a54 - depends: - - python >=3.9 - license: ISC - purls: - - pkg:pypi/ptyprocess?source=hash-mapping - size: 19457 - timestamp: 1733302371990 -- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 - md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 + size: 18628 + timestamp: 1764018033635 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + sha256: 6178775a86579d5e8eec6a7ab316c24f1355f6c6ccbe84bb341f342f1eda2440 + md5: 311fcf3f6a8c4eb70f912798035edd35 depends: - - python >=3.9 + - __osx >=11.0 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + constrains: + - libbrotlicommon 1.2.0 hc919400_1 license: MIT license_family: MIT purls: - - pkg:pypi/pure-eval?source=hash-mapping - size: 16668 - timestamp: 1733569518868 -- conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - sha256: 6d8f03c13d085a569fde931892cded813474acbef2e03381a1a87f420c7da035 - md5: 46830ee16925d5ed250850503b5dc3a8 - depends: - - python >=3.9 + - pkg:pypi/brotli?source=hash-mapping + size: 359503 + timestamp: 1764018572368 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bun-1.3.11-hf0d7872_5.conda + sha256: 9affdd3669b2b1d81d56c2e1b0ea9066fcfbce353b26b081607615c2c77fdfd9 + md5: fe5e62dfa40cf59871d3d7746772ac63 + depends: + - __osx >=13.0 + - libcxx >=21 + - libzlib >=1.3.2,<2.0a0 + - lol-html >=3.0.1,<3.0.2.0a0 + - ls-hpack >=2.3.5,<2.3.6.0a0 + - libarchive >=3.8.9,<3.9.0a0 + - hdrhistogram-c >=0.11.9,<0.11.10.0a0 + - c-ares >=1.34.8,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libhwy >=1.4.0,<1.5.0a0 + - libsqlite >=3.53.4,<4.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/py-cpuinfo?source=hash-mapping - size: 25766 - timestamp: 1733236452235 -- pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl - name: pyarrow - version: 23.0.1 - sha256: 813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl - name: pyarrow - version: 23.0.1 - sha256: f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl - name: pyarrow - version: 23.0.1 - sha256: 7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyhc790b64_3.conda - sha256: d429f6f255fbe49f09b9ae1377aa8cbc4d9285b8b220c17ae2ad9c4894c91317 - md5: 1594696beebf1ecb6d29a1136f859a74 + purls: [] + run_exports: {} + size: 17012825 + timestamp: 1785503872438 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df + md5: 620b85a3f45526a8bc4d23fd78fc22f0 depends: - - pybind11-global 2.13.6 *_3 - - python >=3.9 - constrains: - - pybind11-abi ==4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pybind11?source=hash-mapping - size: 186821 - timestamp: 1747935138653 -- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - sha256: d4fb485b79b11042a16dc6abfb0c44c4f557707c2653ac47c81e5d32b24a3bb0 - md5: 878f923dd6acc8aeb47a75da6c4098be - license: BSD-3-Clause + - __osx >=11.0 + license: bzip2-1.0.6 license_family: BSD purls: [] - size: 9906 - timestamp: 1610372835205 -- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh217bc35_3.conda - sha256: c044cfcbe6ef0062d0960e9f9f0de5f8818cec84ed901219ff9994b9a9e57237 - md5: 730a5284e26d6bdb73332dafb26aec82 + size: 124834 + timestamp: 1771350416561 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h1a92334_1.conda + sha256: 104b41473845649101ba8ebf8221c7431256465d34ce380b10e9a90558ed33ac + md5: 7d9390a4d4b43f91652823d870f68065 depends: - - __unix - - python >=3.9 + - __osx >=11.0 constrains: - - pybind11-abi ==4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pybind11-global?source=hash-mapping - size: 180116 - timestamp: 1747934418811 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py312h2596900_1.conda - sha256: 15b5392d2755b771cb6830ae0377ed71bf2bcfd33a347dbc3195df80568ce42c - md5: 7766c2ad93e65415f4289de684261550 - depends: - - __glibc >=2.17,<3.0.a0 - - cairo >=1.18.4,<2.0a0 - - libexpat >=2.7.3,<3.0a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: LGPL-2.1-only OR MPL-1.1 - purls: - - pkg:pypi/pycairo?source=hash-mapping - size: 119839 - timestamp: 1770726341594 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pycairo-1.29.0-py312h373e0d9_1.conda - sha256: 98c0d1018a82a3fa32a0100b7a2183e30bdedbcc98a485e62cdc835fdec74ad7 - md5: eae555e47a6a7b966a0ffa46c5f5e075 - depends: - - __osx >=10.13 - - cairo >=1.18.4,<2.0a0 - - libexpat >=2.7.3,<3.0a0 - - libzlib >=1.3.1,<2.0a0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: LGPL-2.1-only OR MPL-1.1 - purls: - - pkg:pypi/pycairo?source=hash-mapping - size: 105280 - timestamp: 1770726851881 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda - sha256: 343bdde9b0d80caed910fc6a071d86b81c7c7240be1d5fdbfa7eb9153c55136d - md5: 7a27a42db2d7cc92779abbc26e24d343 + - c-ares-static <0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 197274 + timestamp: 1786116660078 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-3.2.3-hf9886e1_0.conda + sha256: 3cfcfabdf337e7e152ec8ad1e1bbfae15919a1b6c37fc28af2e6d0288ac2d88a + md5: 061d37b39e269f541303c05bfa4ae166 depends: - __osx >=11.0 - - cairo >=1.18.4,<2.0a0 - - libexpat >=2.7.1,<3.0a0 - - libzlib >=1.3.1,<2.0a0 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - license: LGPL-2.1-only OR MPL-1.1 - purls: - - pkg:pypi/pycairo?source=hash-mapping - size: 105984 - timestamp: 1763047065752 -- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 - md5: 12c566707c80111f9799308d9e265aef - depends: - - python >=3.9 - - python + - libcxx >=19 + - lz4-c >=1.10.0,<1.11.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/pycparser?source=hash-mapping - size: 110100 - timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - sha256: 26779821ba83b896f319837d7c5301cc244dee41b311d2bd57cbd693ed9e43ef - md5: 918d9adfc81cb14ab4cced31d22c7711 - depends: - - annotated-types >=0.6.0 - - pydantic-core 2.33.2 - - python >=3.10 - - typing-extensions >=4.6.1 - - typing-inspection >=0.4.0 - - typing_extensions >=4.12.2 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic?source=hash-mapping - size: 307863 - timestamp: 1759584847417 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda - sha256: 4d14d7634c8f351ff1e63d733f6bb15cba9a0ec77e468b0de9102014a4ddc103 - md5: cfbd96e5a0182dfb4110fc42dda63e57 + purls: [] + run_exports: + weak: + - c-blosc2 >=3.2.3,<3.3.0a0 + size: 291985 + timestamp: 1784174620114 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + sha256: cde9b79ee206fe3ba6ca2dc5906593fb7a1350515f85b2a1135a4ce8ec1539e3 + md5: 36200ecfbbfbcb82063c87725434161f depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python_abi 3.12.* *_cp312 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1890081 - timestamp: 1746625309715 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.33.2-py312haba3716_0.conda - sha256: 2bd1ff91077790b93141f6a718840626c6fe12eddd6de8441da6d211aa74999a - md5: ef5b500de254557bd376a64ef2d76c9a + - __osx >=11.0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libcxx >=19 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + run_exports: + weak: + - cairo >=1.18.4,<2.0a0 + size: 900035 + timestamp: 1766416416791 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + sha256: 597e986ac1a1bd1c9b29d6850e1cdea4a075ce8292af55568952ec670e7dd358 + md5: 503ac138ad3cfc09459738c0f5750705 depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - __osx >=10.13 + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - constrains: - - __osx >=10.13 license: MIT license_family: MIT purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1861583 - timestamp: 1746625308090 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda - sha256: 4e583aab0854a3a9c88e3e5c55348f568a1fddce43952a74892e490537327522 - md5: affb6b478c21735be55304d47bfe1c63 + - pkg:pypi/cffi?source=hash-mapping + size: 288080 + timestamp: 1761203317419 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h3093aea_4.conda + sha256: fa1b3967c644c1ffaf8beba3d7aee2301a8db32c0e9a56649a0e496cf3abd27c + md5: f9cce0bc86b46533489a994a47d3c7d2 depends: + - numpy >=1.25 - python - - typing-extensions >=4.6.0,!=4.7.0 - python 3.12.* *_cpython - __osx >=11.0 + - libcxx >=19 - python_abi 3.12.* *_cp312 - constrains: + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/contourpy?source=hash-mapping + size: 286084 + timestamp: 1769156157865 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cyrus-sasl-2.1.28-hb961e35_1.conda + sha256: 2bb1a8cfc2534b05718c21ffacd806c5c3d5289c9e8be12270d9fc5606c859bf + md5: 784c64a42b083798c5acd2373df5b825 + depends: + - __osx >=11.0 + - krb5 >=1.22.2,<1.23.0a0 + - libcxx >=19 + - libntlm >=1.8,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: BSD-3-Clause-Attribution + license_family: BSD + purls: [] + run_exports: + weak: + - cyrus-sasl >=2.1.28,<3.0a0 + size: 194397 + timestamp: 1771943557428 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda + sha256: f0ca130b5ffd6949673d3c61d7b8562ab76ad8debafb83f8b3443d30c172f5eb + md5: da3b5efcb0caabcede61a6ce4e0a7669 + depends: + - python - __osx >=11.0 + - python 3.12.* *_cpython + - libcxx >=19 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1715338 - timestamp: 1746625327204 -- pypi: https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl - name: pydantic-settings - version: 2.13.1 - sha256: d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237 - requires_dist: - - pydantic>=2.7.0 - - python-dotenv>=0.21.0 - - typing-inspection>=0.4.0 - - boto3-stubs[secretsmanager] ; extra == 'aws-secrets-manager' - - boto3>=1.35.0 ; extra == 'aws-secrets-manager' - - azure-identity>=1.16.0 ; extra == 'azure-key-vault' - - azure-keyvault-secrets>=4.8.0 ; extra == 'azure-key-vault' - - google-cloud-secret-manager>=2.23.1 ; extra == 'gcp-secret-manager' - - tomli>=2.0.1 ; extra == 'toml' - - pyyaml>=6.0.1 ; extra == 'yaml' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/pyedr-0.8.0-pyhd8ed1ab_1.conda - sha256: de95768c61325511ff2b477398ba57ea513c4052faab905a02784bb1f35bb550 - md5: 96431184a69661798fc2e90495192d69 + - pkg:pypi/debugpy?source=hash-mapping + size: 2752978 + timestamp: 1769744996462 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fftw-3.3.11-nompi_haf1500d_100.conda + sha256: fc6c507d7c68db156d6c8c5f6a79ca6b34c2a4c0c6222d8d4ecd0e4b97d3fd5e + md5: 58628fdc0c614982f1dafd2116916288 depends: - - mda-xdrlib - - numpy - - python >=3.9 - - tqdm - license: LGPL-2.1-or-later - purls: - - pkg:pypi/pyedr?source=hash-mapping - size: 344942 - timestamp: 1734172243506 -- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a - md5: 6b6ece66ebcae2d5f326c77ef2c5a066 + - __osx >=11.0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - llvm-openmp >=19.1.7 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - fftw >=3.3.11,<4.0a0 + size: 746873 + timestamp: 1776782373231 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda + sha256: 851e9c778bfc54645dcab7038c0383445cbebf16f6bb2d3f62ce422b1605385a + md5: d06ae1a11b46cc4c74177ecd28de7c7a depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/pygments?source=hash-mapping - size: 889287 - timestamp: 1750615908735 -- pypi: https://files.pythonhosted.org/packages/26/d9/8fa5571ddd21b2b7189bd8b0bb4e90be1659a54dda5af51c7f6bf2b5666f/pyinstrument-5.1.2-cp312-cp312-macosx_10_13_universal2.whl - name: pyinstrument - version: 5.1.2 - sha256: 2519865d4bf58936f2506c1c46a82d29a20f3239aa50c941df1ca9618c7da5f0 - requires_dist: - - pytest ; extra == 'test' - - flaky ; extra == 'test' - - trio ; extra == 'test' - - cffi>=1.17.0 ; extra == 'test' - - greenlet>=3 ; extra == 'test' - - pytest-asyncio==0.23.8 ; extra == 'test' - - ipython ; extra == 'test' - - click ; extra == 'bin' - - nox ; extra == 'bin' - - sphinx==7.4.7 ; extra == 'docs' - - myst-parser==3.0.1 ; extra == 'docs' - - furo==2024.7.18 ; extra == 'docs' - - sphinxcontrib-programoutput==0.17 ; extra == 'docs' - - sphinx-autobuild==2024.4.16 ; extra == 'docs' - - numpy ; extra == 'examples' - - django ; extra == 'examples' - - litestar ; extra == 'examples' - - typing-extensions ; extra == 'types' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/6f/50/0512adb83cadfeaa1a215dc9784defff5043c5aa052d15015e3d8013af75/pyinstrument-5.1.2-cp312-cp312-macosx_11_0_arm64.whl - name: pyinstrument - version: 5.1.2 - sha256: 059442106b8b5de29ae5ac1bdc20d044fed4da534b8caba434b6ffb119037bf5 - requires_dist: - - pytest ; extra == 'test' - - flaky ; extra == 'test' - - trio ; extra == 'test' - - cffi>=1.17.0 ; extra == 'test' - - greenlet>=3 ; extra == 'test' - - pytest-asyncio==0.23.8 ; extra == 'test' - - ipython ; extra == 'test' - - click ; extra == 'bin' - - nox ; extra == 'bin' - - sphinx==7.4.7 ; extra == 'docs' - - myst-parser==3.0.1 ; extra == 'docs' - - furo==2024.7.18 ; extra == 'docs' - - sphinxcontrib-programoutput==0.17 ; extra == 'docs' - - sphinx-autobuild==2024.4.16 ; extra == 'docs' - - numpy ; extra == 'examples' - - django ; extra == 'examples' - - litestar ; extra == 'examples' - - typing-extensions ; extra == 'types' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/91/4d/2ca3ca9906ce6e05070f431c54d54ccbaf57a980cfa58032d35b0b0ac1f8/pyinstrument-5.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: pyinstrument - version: 5.1.2 - sha256: 12af1e83795b6c640d657d339014dd1ff718b182dec736d7d1f1d8a97534eb53 - requires_dist: - - pytest ; extra == 'test' - - flaky ; extra == 'test' - - trio ; extra == 'test' - - cffi>=1.17.0 ; extra == 'test' - - greenlet>=3 ; extra == 'test' - - pytest-asyncio==0.23.8 ; extra == 'test' - - ipython ; extra == 'test' - - click ; extra == 'bin' - - nox ; extra == 'bin' - - sphinx==7.4.7 ; extra == 'docs' - - myst-parser==3.0.1 ; extra == 'docs' - - furo==2024.7.18 ; extra == 'docs' - - sphinxcontrib-programoutput==0.17 ; extra == 'docs' - - sphinx-autobuild==2024.4.16 ; extra == 'docs' - - numpy ; extra == 'examples' - - django ; extra == 'examples' - - litestar ; extra == 'examples' - - typing-extensions ; extra == 'types' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-12.1-py312h4a480f0_0.conda - sha256: ecf778f886aaf50db22c0971fb0873f0dbe25663f124bd714bc87b4d0925f534 - md5: 18a20cb8c3e19f0b3799a48eba5b44aa + - __osx >=11.0 + - libexpat >=2.7.4,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libzlib >=1.3.1,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 237308 + timestamp: 1771382999247 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.1-py312h5748b74_0.conda + sha256: d87752e84621f90e9350262200fef55f054472f7779323f51717b557208e2a16 + md5: c14625bf00c41c00cea174f459287fc4 depends: - - __osx >=10.13 - - libffi >=3.5.2,<3.6.0a0 + - __osx >=11.0 + - brotli + - munkres - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - setuptools + - unicodedata2 >=15.1.0 license: MIT license_family: MIT purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 487397 - timestamp: 1763151480498 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda - sha256: b015f430fe9ea2c53e14be13639f1b781f68deaa5ae74cd8c1d07720890cd02a - md5: c65d7abdc9e60fd3af0ed852591adf1b + - pkg:pypi/fonttools?source=hash-mapping + size: 2859891 + timestamp: 1765633073562 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/freesasa-python-2.2.1-py312h163523d_1.conda + sha256: c677041d7761d9097002d695ffe3ea7233dd32e6a7e339a25c288c2ce27fde0e + md5: dcf3946399d4431aa3b51dba0b75a37a depends: - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - python >=3.12,<3.13.0a0 - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - setuptools license: MIT license_family: MIT purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 476750 - timestamp: 1763151865523 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-12.1-py312h1993040_0.conda - sha256: 3a29ca3cc2044b408447ff86ae0c57ecc3ff805a8fc838525610921024c8521a - md5: b6881a919e1bfd66349e2260b163dc7c + - pkg:pypi/freesasa?source=hash-mapping + run_exports: {} + size: 219638 + timestamp: 1756396889347 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.2-hce30654_0.conda + sha256: 3c02ecdbfd94d25721811f51d0f400bf705005a728011e19db9975a8985e1021 + md5: ca730d8e7d1de1f71013edfef0e08f13 depends: - - __osx >=10.13 - - libffi >=3.5.2,<3.6.0a0 - - pyobjc-core 12.1.* + - libfreetype 2.14.2 hce30654_0 + - libfreetype6 2.14.2 hdfa99f5_0 + license: GPL-2.0-only OR FTL + purls: [] + size: 173786 + timestamp: 1772756361577 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gemmi-0.7.5-py312h84d8aaf_0.conda + sha256: be520344273271f87ee3cf2496d744a0e1a7064e26d42de5bec796acc7e9d993 + md5: 5239d688634cc0207155a95f8630c49d + depends: + - __osx >=11.0 + - libcxx >=19 - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 375580 - timestamp: 1763160526695 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda - sha256: 3710f5ae09c2ea77ba4d82cc51e876d9fc009b878b197a40d3c6347c09ae7d7c - md5: f0bae1b67ece138378923e340b940051 + - zlib-ng >=2.3.3,<2.4.0a0 + license: MPL-2.0 + license_family: OTHER + purls: + - pkg:pypi/gemmi?source=hash-mapping + run_exports: + weak: + - gemmi >=0.7.5,<0.7.6.0a0 + size: 3485696 + timestamp: 1772447619392 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda + sha256: 76e222e072d61c840f64a44e0580c2503562b009090f55aa45053bf1ccb385dd + md5: eed7278dfbab727b56f2c0b64330814b depends: - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pyobjc-core 12.1.* + - libcxx >=16 + license: GPL-2.0-or-later OR LGPL-3.0-or-later + purls: [] + size: 365188 + timestamp: 1718981343258 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.0-py312he1ee7cc_0.conda + sha256: 88f51ca492ac2f44e460c2720b0896b114be0224a71679a3dadb8ada2ecf2bb4 + md5: 7c386b698b9cbcff65a00d4893cf48ae + depends: + - __osx >=11.0 + - gmp >=6.3.0,<7.0a0 + - mpc >=1.3.1,<2.0a0 + - mpfr >=4.2.1,<5.0a0 - python >=3.12,<3.13.0a0 - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT + license: LGPL-3.0-or-later + license_family: LGPL purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 377723 - timestamp: 1763160705325 -- conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - sha256: 417fba4783e528ee732afa82999300859b065dc59927344b4859c64aae7182de - md5: 3687cc0b82a8b4c17e1f0eb7e47163d5 + - pkg:pypi/gmpy2?source=hash-mapping + size: 193399 + timestamp: 1773103057948 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.3.2-py312h6510ced_0.conda + sha256: 4b11b05d11c024162eb3486f0cf5dc5878fc27915ec03108aaee8946f36deead + md5: 5ec9884fbb0d262e2ae35293fccbd263 depends: - - python >=3.10 - python + - python 3.12.* *_cpython + - __osx >=11.0 + - libcxx >=19 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/pyparsing?source=hash-mapping - size: 110893 - timestamp: 1769003998136 -- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - name: pyproject-hooks - version: 1.2.0 - sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 - md5: 461219d1a5bd61342293efa2c0c90eac + - pkg:pypi/greenlet?source=hash-mapping + size: 250518 + timestamp: 1771658526528 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda + sha256: c3b01e3c3fe4ca1c4d28c287eaa5168a4f2fd3ffd76690082ac919244c22fa90 + md5: ff5d749fd711dc7759e127db38005924 depends: - - __unix - - python >=3.9 + - libcxx >=15.0.7 + - libjpeg-turbo >=3.0.0,<4.0a0 + - libzlib >=1.2.13,<2.0.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21085 - timestamp: 1733217331982 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pytables-3.11.1-py312hefc0c3f_0.conda - sha256: 2c7efb2498ebaa96273fa7c6b3b51bf9f4408e7838eaa3d114ace6e0a4e93481 - md5: f76dda533faf9f9c79d1c2340da6573d + purls: [] + size: 762257 + timestamp: 1695661864625 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.2.0-nompi_h7eff4e6_100.conda + sha256: f4628ccb83c2e89baa55c4edbef4c6c8a1b6ff14e3661017796efaaf49c51c6c + md5: abd52e74327b5b8145c950326432d0b2 depends: - - __glibc >=2.17,<3.0.a0 - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - c-blosc2 >=2.23.0,<2.24.0a0 - - hdf5 >=1.14.6,<1.14.7.0a0 - - libgcc >=14 - - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - - numexpr - - numpy >=1.20.0 - - numpy >=1.23,<3 - - packaging - - py-cpuinfo - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - typing-extensions >=4.4.0 + - __osx >=11.0 + - aws-c-auth >=0.10.4,<0.10.5.0a0 + - aws-c-common >=0.14.3,<0.14.4.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + - aws-c-io >=0.27.5,<0.27.6.0a0 + - aws-c-s3 >=0.13.1,<0.13.2.0a0 + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.21.0,<9.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.4.0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/tables?source=hash-mapping - size: 1631211 - timestamp: 1772371744337 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pytables-3.11.1-py312h7063bee_0.conda - sha256: 867a6020921707c8d9ef89439c04708ec973a69961e0180c7a208dc814a181f6 - md5: 5dd692ed6d1dfa3dc6c6bf4166357468 + purls: [] + run_exports: + weak: + - hdf5 >=2.2.0,<3.0a0 + size: 3731030 + timestamp: 1786234076033 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdrhistogram-c-0.11.9-h132b30e_1.conda + sha256: bde94a98894bc8f9bd735aef0a1b8b805a4cc623bbbd1c53580c1acb24c22c7c + md5: 7938c4e113486aea6258375a41a5c1dd depends: - __osx >=11.0 - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - c-blosc2 >=2.23.0,<2.24.0a0 - - hdf5 >=1.14.6,<1.14.7.0a0 - - libcxx >=19 - libzlib >=1.3.1,<2.0a0 - - numexpr - - numpy >=1.20.0 - - numpy >=1.23,<3 - - packaging - - py-cpuinfo - - python >=3.12,<3.13.0a0 + license: BSD-2-Clause OR CC0-1.0 + purls: [] + run_exports: + weak: + - hdrhistogram-c >=0.11.9,<0.11.10.0a0 + size: 37701 + timestamp: 1769816279348 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hc7cc350_2.conda + sha256: f0b22bc30e4cc29e29ba3234cb38497fe8def2c2aae4b775d42fe5b378a018c9 + md5: 6133ddbb17ba2b50700dd88e9303ce27 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14070698 + timestamp: 1784916459058 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/khronos-opencl-icd-loader-2024.10.24-h5505292_1.conda + sha256: 74abb94591a42b0986b864cdd1c3b5b2e6e6b6851d5b8d0ecbe038fc14f036be + md5: fb5a6baf5df9abc027955a408c61ac74 + depends: + - __osx >=11.0 + - opencl-headers >=2024.10.24 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 35128 + timestamp: 1732916821283 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py312h3093aea_0.conda + sha256: 8de440f0e33ab6895e81f2c47c51e59d177349a832087a0367e8e259c97f4833 + md5: 58261af35f0d33fd28e2257b208a1be0 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - python 3.12.* *_cpython - python_abi 3.12.* *_cp312 - - typing-extensions >=4.4.0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/tables?source=hash-mapping - size: 1521681 - timestamp: 1772372001607 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h53cfef5_0.conda - sha256: 9fe2748ca3b9ca48bcd7e8c59d76bcc29d8784befb3776d2c02f4d5e4c3fb76b - md5: 9447d78283b69ed3e80ff1f62394f873 + - pkg:pypi/kiwisolver?source=hash-mapping + size: 68490 + timestamp: 1773067215781 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-hfd3d5f3_1.conda + sha256: c740e4a2e7247776a9883158fdab50ae0732c8f67f96d8f1db8ad9da5e0b5222 + md5: 8780f41b013d19219faef9c82260744b depends: - __osx >=11.0 - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - c-blosc2 >=2.23.0,<2.24.0a0 - - hdf5 >=1.14.6,<1.14.7.0a0 - libcxx >=19 - - libzlib >=1.3.1,<2.0a0 - - numexpr - - numpy >=1.20.0 - - numpy >=1.23,<3 - - packaging - - py-cpuinfo - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - - typing-extensions >=4.4.0 - license: BSD-3-Clause + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1159780 + timestamp: 1781859501654 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda + sha256: d768da024ab74a4b30642401877fa914a68bdc238667f16b1ec2e0e98b2451a6 + md5: 6631a7bd2335bb9699b1dbc234b19784 + depends: + - __osx >=11.0 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: MIT + license_family: MIT + purls: [] + size: 211756 + timestamp: 1768184994800 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda + sha256: 66e5ffd301a44da696f3efc2f25d6d94f42a9adc0db06c44ad753ab844148c51 + md5: 095e5749868adab9cae42d4b460e5443 + depends: + - __osx >=11.0 + - libcxx >=19 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 164222 + timestamp: 1773114244984 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + sha256: 7f0ee9ae7fa2cf7ac92b0acf8047c8bac965389e48be61bf1d463e057af2ea6a + md5: 360dbb413ee2c170a0a684a33c4fc6b8 + depends: + - __osx >=11.0 + - libcxx >=18 + constrains: + - libabseil-static =20250512.1=cxx17* + - abseil-cpp =20250512.1 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1174081 + timestamp: 1750194620012 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda + sha256: af9cd8db11eb719e38a3340c88bb4882cf19b5b4237d93845224489fc2a13b46 + md5: 13e6d9ae0efbc9d2e9a01a91f4372b41 + depends: + - __osx >=11.0 + - libcxx >=19 + license: BSD-2-Clause license_family: BSD - purls: - - pkg:pypi/tables?source=hash-mapping - size: 1725636 - timestamp: 1772372138444 -- pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - name: pytest - version: 9.0.2 - sha256: 711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b - requires_dist: - - colorama>=0.4 ; sys_platform == 'win32' - - exceptiongroup>=1 ; python_full_version < '3.11' - - iniconfig>=1.0.1 - - packaging>=22 - - pluggy>=1.5,<2 - - pygments>=2.7.2 - - tomli>=1 ; python_full_version < '3.11' - - argcomplete ; extra == 'dev' - - attrs>=19.2 ; extra == 'dev' - - hypothesis>=3.56 ; extra == 'dev' - - mock ; extra == 'dev' - - requests ; extra == 'dev' - - setuptools ; extra == 'dev' - - xmlschema ; extra == 'dev' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - name: pytest-cov - version: 7.0.0 - sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 - requires_dist: - - coverage[toml]>=7.10.6 - - pluggy>=1.2 - - pytest>=7 - - process-tests ; extra == 'testing' - - pytest-xdist ; extra == 'testing' - - virtualenv ; extra == 'testing' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl - name: pytest-mock - version: 3.15.1 - sha256: 0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d - requires_dist: - - pytest>=6.2.5 - - pre-commit ; extra == 'dev' - - pytest-asyncio ; extra == 'dev' - - tox ; extra == 'dev' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda - sha256: a44655c1c3e1d43ed8704890a91e12afd68130414ea2c0872e154e5633a13d7e - md5: 7eccb41177e15cc672e1babe9056018e + purls: [] + size: 30390 + timestamp: 1769222133373 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h6fbacd7_100.conda + sha256: b58cfffd0774142d308a25f1bdc610c46d71cb67489f1b1eec0891e78ede5b11 + md5: 4963589c8bed67dd0540d890ba690f53 depends: - - __glibc >=2.17,<3.0.a0 + - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - liblzma >=5.8.2,<6.0a0 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libuuid >=2.41.3,<3.0a0 - - libxcrypt >=4.4.36 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.5,<4.0a0 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - lzo >=2.10,<3.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libarchive >=3.8.9,<3.9.0a0 + size: 800281 + timestamp: 1785251408018 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + build_number: 5 + sha256: 620a6278f194dcabc7962277da6835b1e968e46ad0c8e757736255f5ddbfca8d + md5: bcc025e2bbaf8a92982d20863fe1fb69 + depends: + - libopenblas >=0.3.30,<0.3.31.0a0 + - libopenblas >=0.3.30,<1.0a0 constrains: - - python_abi 3.12.* *_cp312 - license: Python-2.0 + - libcblas 3.11.0 5*_openblas + - liblapack 3.11.0 5*_openblas + - liblapacke 3.11.0 5*_openblas + - blas 2.305 openblas + - mkl <2026 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 31608571 - timestamp: 1772730708989 -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda - sha256: fb592ceb1bc247d19247d5535083da4a79721553e29e1290f5d81c07d4f086b5 - md5: ec05996c0d914a4e98ee3c7d789083f8 + size: 18546 + timestamp: 1765819094137 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.86.0-h0419b56_5.conda + sha256: e5852889261db409a720c2c6a30930e0615c320184874b862c2f3141332ae701 + md5: 0b2bb618d1c6e4a43fc2a13f128f55a0 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libsqlite >=3.51.2,<4.0a0 + - icu >=78.1,<79.0a0 + - libcxx >=19 + - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.5,<4.0a0 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata + - zstd >=1.5.7,<1.6.0a0 constrains: + - boost-cpp <0.0a0 + license: BSL-1.0 + purls: [] + run_exports: {} + size: 1991540 + timestamp: 1766349001066 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-python-1.86.0-py312hdeff4eb_5.conda + sha256: ed62e2a4fa93de99dda3a79db77c7f595bd1377abe03b225ffd4aeefab38e108 + md5: a1bd6dbd8795a23cae468f59a0795883 + depends: + - __osx >=11.0 + - libcxx >=19 + - numpy >=1.23,<3 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: Python-2.0 + constrains: + - boost <0.0a0 + - py-boost <0.0a0 + license: BSL-1.0 purls: [] - size: 13672169 - timestamp: 1772730464626 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda - sha256: e658e647a4a15981573d6018928dec2c448b10c77c557c29872043ff23c0eb6a - md5: 8e7608172fa4d1b90de9a745c2fd2b81 + size: 104511 + timestamp: 1766349822546 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 + md5: 006e7ddd8a110771134fcc4e1e3a6ffa depends: - __osx >=11.0 - - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.5,<4.0a0 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata + license: MIT + license_family: MIT + purls: [] + size: 79443 + timestamp: 1764017945924 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf + md5: 079e88933963f3f149054eec2c487bc2 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: [] + size: 29452 + timestamp: 1764017979099 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 + md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: [] + size: 290754 + timestamp: 1764018009077 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda + build_number: 5 + sha256: 38809c361bbd165ecf83f7f05fae9b791e1baa11e4447367f38ae1327f402fc0 + md5: efd8bd15ca56e9d01748a3beab8404eb + depends: + - libblas 3.11.0 5_h51639a9_openblas constrains: - - python_abi 3.12.* *_cp312 - license: Python-2.0 + - liblapacke 3.11.0 5*_openblas + - liblapack 3.11.0 5*_openblas + - blas 2.305 openblas + license: BSD-3-Clause + license_family: BSD purls: [] - size: 12127424 - timestamp: 1772730755512 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-constraint-1.4.0-pyhff2d567_1.conda - sha256: 953ae7a61c363fb57076f141c9c8def2e3767f11884165f6c4a2cccfb4d8b159 - md5: 2848429a376fd2676af5b320a28fd3d9 + size: 18548 + timestamp: 1765819108956 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.21.0-hf618e03_4.conda + sha256: d25be36712d7f854a5f93513b9fbf1b0ee3219975b7fef4a3ee071b021b10167 + md5: 66cf9c5003ee81ecdb0dc9f9df17bbe5 depends: - - python >=3.9 + - __osx >=11.0 + - krb5 >=1.22.2,<1.23.0a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libpsl >=0.23.0,<0.24.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + purls: [] + run_exports: + weak: + - libcurl >=8.21.0,<9.0a0 + size: 411491 + timestamp: 1785500129743 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + sha256: ce1049fa6fda9cf08ff1c50fb39573b5b0ea6958375d8ea7ccd8456ab81a0bcb + md5: e9c56daea841013e7774b5cd46f41564 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 568910 + timestamp: 1772001095642 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + sha256: 5e0b6961be3304a5f027a8c00bd0967fc46ae162cffb7553ff45c70f51b8314c + md5: a6130c709305cd9828b4e1bd9ba0000c + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 55420 + timestamp: 1761980066242 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 + md5: 44083d2d2c2025afca315c7a172eab2b + depends: + - ncurses + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 license: BSD-2-Clause license_family: BSD - purls: - - pkg:pypi/python-constraint?source=hash-mapping - size: 26134 - timestamp: 1734363065821 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 - md5: 5b8d21249ff20967101ffa321cab24e8 + purls: [] + size: 107691 + timestamp: 1738479560845 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f + md5: 36d33e440c31857372a72137f78bacf5 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107458 + timestamp: 1702146414478 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + sha256: 03887d8080d6a8fe02d75b80929271b39697ecca7628f0657d7afaea87761edf + md5: a92e310ae8dfc206ff449f362fc4217f depends: - - python >=3.9 - - six >=1.5 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/python-dateutil?source=hash-mapping - size: 233310 - timestamp: 1751104122689 -- pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - name: python-discovery - version: 1.1.3 - sha256: 90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e - requires_dist: - - filelock>=3.15.4 - - platformdirs>=4.3.6,<5 - - furo>=2025.12.19 ; extra == 'docs' - - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' - - sphinx>=9.1 ; extra == 'docs' - - sphinxcontrib-mermaid>=2 ; extra == 'docs' - - covdefaults>=2.3 ; extra == 'testing' - - coverage>=7.5.4 ; extra == 'testing' - - pytest-mock>=3.14 ; extra == 'testing' - - pytest>=8.3.5 ; extra == 'testing' - - setuptools>=75.1 ; extra == 'testing' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - name: python-dotenv - version: 1.2.2 - sha256: 1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a - requires_dist: - - click>=5.0 ; extra == 'cli' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 - md5: 23029aae904a2ba587daba708208012f + - __osx >=11.0 + constrains: + - expat 2.7.4.* + license: MIT + license_family: MIT + purls: [] + size: 68199 + timestamp: 1771260020767 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 + md5: 43c04d9cb46ef176bb2a4c77e324d599 depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/fastjsonschema?source=hash-mapping - size: 244628 - timestamp: 1755304154927 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.13-hd8ed1ab_0.conda - sha256: 97327b9509ae3aae28d27217a5d7bd31aff0ab61a02041e9c6f98c11d8a53b29 - md5: 32780d6794b8056b78602103a04e90ef + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 40979 + timestamp: 1769456747661 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.2-hce30654_0.conda + sha256: 6061ef5321b8e697d5577d8dfe7a4c75bfe3e706c956d0d84bfec6bea3ed9f77 + md5: a3a53232936b55ffea76806aefe19e8b depends: - - cpython 3.12.13.* - - python_abi * *_cp312 - license: Python-2.0 + - libfreetype6 >=2.14.2 + license: GPL-2.0-only OR FTL + purls: [] + size: 8076 + timestamp: 1772756349852 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.2-hdfa99f5_0.conda + sha256: 24dd0e0bee56e87935f885929f67659f1d3b8a01e7546568de2919cffd9e2e36 + md5: e726e134a392ae5d7bafa6cc4a3d5725 + depends: + - __osx >=11.0 + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - freetype >=2.14.2 + license: GPL-2.0-only OR FTL purls: [] - size: 46449 - timestamp: 1772728979370 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca - md5: a61bf9ec79426938ff785eb69dbb1960 + size: 338032 + timestamp: 1772756347899 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + sha256: 1d9c4f35586adb71bcd23e31b68b7f3e4c4ab89914c26bed5f2859290be5560e + md5: 92df6107310b1fff92c4cc84f0de247b depends: - - python >=3.6 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/python-json-logger?source=hash-mapping - size: 13383 - timestamp: 1677079727691 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda - sha256: 467134ef39f0af2dbb57d78cb3e4821f01003488d331a8dd7119334f4f47bfbd - md5: 7ead57407430ba33f681738905278d03 + - _openmp_mutex + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 401974 + timestamp: 1771378877463 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + sha256: 63f89087c3f0c8621c5c89ecceec1e56e5e1c84f65fc9c5feca33a07c570a836 + md5: 26981599908ed2205366e8fc91b37fc6 depends: - - python >=3.10 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/tzdata?source=compressed-mapping - size: 143542 - timestamp: 1765719982349 -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - build_number: 8 - sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809 - md5: c3efd25ac4d74b1584d2f7a57195ddf1 + - libgfortran5 15.2.0 hdae7583_18 constrains: - - python 3.12.* *_cpython - license: BSD-3-Clause - license_family: BSD + - libgfortran-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL purls: [] - size: 6958 - timestamp: 1752805918820 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.8.0-cpu_mkl_py312_hb1fc07b_102.conda - sha256: 80c932a9b95fedaa08a03063adeb8807ecde2c57eb4756d386652c7f3ec0c0f7 - md5: b3e8517778ac3397ef5e3b2ad9ed4773 + size: 138973 + timestamp: 1771379054939 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + sha256: 91033978ba25e6a60fb86843cf7e1f7dc8ad513f9689f991c9ddabfaf0361e7e + md5: c4a6f7989cffb0544bfd9207b6789971 depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex * *_llvm - - _openmp_mutex >=4.5 - - filelock - - fsspec - - jinja2 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libblas * *mkl - - libcblas >=3.9.0,<4.0a0 - - libgcc >=14 - - libprotobuf >=6.31.1,<6.31.2.0a0 - - libstdcxx >=14 - - libtorch 2.8.0 cpu_mkl_h09b866c_102 - - libuv >=1.51.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=21.1.4 - - mkl >=2025.3.0,<2026.0a0 - - networkx - - numpy >=1.23,<3 - - optree >=0.13.0 - - pybind11 - - pybind11-abi 4 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.9.0,<4.0a0 - - sympy >=1.13.3 - - typing_extensions >=4.10.0 + - libgcc >=15.2.0 constrains: - - pytorch-cpu 2.8.0 - - pytorch-gpu <0.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torch?source=hash-mapping - size: 24721307 - timestamp: 1762097814781 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pytorch-2.8.0-cpu_generic_py312_heb096b9_2.conda - sha256: a60026131d74959c0df4d4cdf97ec0ef09ee5cdfcdafaac384c069068dc14f89 - md5: c93b3e22a5b37490641d0f1690805e35 + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 598634 + timestamp: 1771378886363 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.86.4-he378b5c_1.conda + sha256: a4254a241a96198e019ced2e0d2967e4c0ef64fac32077a45c065b32dc2b15d2 + md5: 673069f6725ed7b1073f9b96094294d1 depends: - __osx >=11.0 - - filelock - - fsspec - - jinja2 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libcblas >=3.9.0,<4.0a0 - - libcxx >=19 - - liblapack >=3.9.0,<4.0a0 - - libprotobuf >=6.31.1,<6.31.2.0a0 - - libtorch 2.8.0.* *_2 - - libuv >=1.51.0,<2.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libiconv >=1.18,<2.0a0 + - libintl >=0.25.1,<1.0a0 - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - networkx - - nomkl - - numpy >=1.23,<3 - - optree >=0.13.0 - - pybind11 - - pybind11-abi 4 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.9.0,<4.0a0 - - sympy >=1.13.3 - - typing_extensions >=4.10.0 + - pcre2 >=10.47,<10.48.0a0 constrains: - - pytorch-cpu 2.8.0 - - pytorch-gpu <0.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torch?source=hash-mapping - size: 23954998 - timestamp: 1762106725041 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda - sha256: c1638d808b837232a0912bd627fed4157078cc97ee7784bafde9284b38dfc77f - md5: 67c81fe3c3b188ef6beeb22f21db51ae + - glib 2.86.4 *_1 + license: LGPL-2.1-or-later + purls: [] + size: 4108927 + timestamp: 1771864169970 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.4.0-h493e8d7_0.conda + sha256: e2b0108325d360961750bd35d975ca59694f9c1efff6ec241470c68ca09cacc0 + md5: 5b102fdc40afa0b6030c7867d939c00e depends: - __osx >=11.0 - - filelock - - fsspec - - jinja2 - - libabseil * cxx17* - - libabseil >=20250512.1,<20250513.0a0 - - libcblas >=3.9.0,<4.0a0 - libcxx >=19 - - liblapack >=3.9.0,<4.0a0 - - libprotobuf >=6.31.1,<6.31.2.0a0 - - libtorch 2.8.0.* *_2 - - libuv >=1.51.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - networkx - - nomkl - - numpy >=1.23,<3 - - optree >=0.13.0 - - pybind11 - - pybind11-abi 4 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.9.0,<4.0a0 - - sympy >=1.13.3 - - typing_extensions >=4.10.0 - constrains: - - pytorch-cpu 2.8.0 - - pytorch-gpu <0.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torch?source=hash-mapping - size: 23288257 - timestamp: 1762101789 -- conda: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.6.1-pyhcf101f3_0.conda - sha256: 997bbca0f4578ba92159a4ac6f54f5cc9f60a1e5241c66aedbf774f2db025a04 - md5: e44e84a3b731b1a27cbc75d825db9463 + license: Apache-2.0 OR BSD-3-Clause + purls: [] + run_exports: + weak: + - libhwy >=1.4.0,<1.5.0a0 + size: 610044 + timestamp: 1784325884330 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + sha256: de0336e800b2af9a40bdd694b03870ac4a848161b35c8a2325704f123f185f03 + md5: 4d5a7445f0b25b6a3ddbb56e790f5251 depends: - - fsspec >=2022.5.0 - - python >=3.10 - - packaging >=23.0 - - pyyaml >5.4 - - pytorch >=2.1.0 - - tqdm >=4.57.0 - - torchmetrics >0.7.0 - - typing_extensions >4.5.0 - - lightning-utilities >=0.10.0 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/pytorch-lightning?source=hash-mapping - size: 500422 - timestamp: 1769802510053 -- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - sha256: 8d2a8bf110cc1fc3df6904091dead158ba3e614d8402a83e51ed3a8aa93cdeb0 - md5: bc8e3267d44011051f2eb14d22fb0960 + - __osx >=11.0 + license: LGPL-2.1-only + purls: [] + size: 750379 + timestamp: 1754909073836 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + sha256: 99d2cebcd8f84961b86784451b010f5f0a795ed1c08f1e7c76fbb3c22abf021a + md5: 5103f6a6b210a3912faf8d7db516918c depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pytz?source=hash-mapping - size: 189015 - timestamp: 1742920947249 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda - sha256: cb142bfd92f6e55749365ddc244294fa7b64db6d08c45b018ff1c658907bfcbf - md5: 15878599a87992e44c059731771591cb + - __osx >=11.0 + - libiconv >=1.18,<2.0a0 + license: LGPL-2.1-or-later + purls: [] + size: 90957 + timestamp: 1751558394144 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + sha256: 6c061c56058bb10374daaef50e81b39cf43e8aee21f0037022c0c39c4f31872f + md5: f0695fbecf1006f27f4395d64bd0c4b8 + depends: + - __osx >=11.0 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib + purls: [] + size: 551197 + timestamp: 1762095054358 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-5_hd9741b5_openblas.conda + build_number: 5 + sha256: 735a6e6f7d7da6f718b6690b7c0a8ae4815afb89138aa5793abe78128e951dbb + md5: ca9d752201b7fa1225bca036ee300f2b + depends: + - libblas 3.11.0 5_h51639a9_openblas + constrains: + - libcblas 3.11.0 5*_openblas + - blas 2.305 openblas + - liblapacke 3.11.0 5*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18551 + timestamp: 1765819121855 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + sha256: 23d0630046a3e8b164d8f80f2b74ed2605af2e7050ab9913018056402fae4311 + md5: 8ab10323068b107661a4b9a4af84f3b5 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=compressed-mapping - size: 198293 - timestamp: 1770223620706 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.3-py312h51361c1_1.conda - sha256: d85e3be523b7173a194a66ae05a585ac1e14ccfbe81a9201b8047d6e45f2f7d9 - md5: 9029301bf8a667cf57d6e88f03a6726b + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 91720 + timestamp: 1786348695846 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.10.1-nompi_ha00b61a_201.conda + build_number: 101 + sha256: 59a360bebc22ffc64dab3c7295e01c8ab994026fb32a330c3950eba2db21b2fc + md5: b907aad92c67f60cd8c82c7ed0df5ce8 depends: - - __osx >=10.13 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - yaml >=0.2.5,<0.3.0a0 + - libcxx >=19 + - __osx >=11.0 + - libzip >=1.11.2,<2.0a0 + - hdf4 >=4.2.15,<4.2.16.0a0 + - libaec >=1.1.5,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + - libcurl >=8.21.0,<9.0a0 + - zstd >=1.5.7,<1.6.0a0 + - blosc >=1.21.6,<2.0a0 + - hdf5 >=2.1.0,<3.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - bzip2 >=1.0.8,<2.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/pyyaml?source=compressed-mapping - size: 190417 - timestamp: 1770223755226 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda - sha256: 737959262d03c9c305618f2d48c7f1691fb996f14ae420bfd05932635c99f873 - md5: 95a5f0831b5e0b1075bbd80fcffc52ac + purls: [] + run_exports: + weak: + - libnetcdf >=4.10.1,<4.10.2.0a0 + size: 807359 + timestamp: 1783982087132 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a + md5: 6ea18834adbc3b33df9bd9fb45eaf95b depends: - __osx >=11.0 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - - python_abi 3.12.* *_cp312 - - yaml >=0.2.5,<0.3.0a0 + - c-ares >=1.34.6,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 187278 - timestamp: 1770223990452 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - noarch: python - sha256: be66c1f85c3b48137200d62c12d918f4f8ad329423daef04fed292818efd3c28 - md5: 082985717303dab433c976986c674b35 + purls: [] + run_exports: + weak: + - libnghttp2 >=1.68.1,<2.0a0 + size: 576526 + timestamp: 1773854624224 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libntlm-1.8-h5505292_0.conda + sha256: ea8c680924d957e12270dca549620327d5e986f23c4bd5f45627167ca6ef7a3b + md5: c90c1d3bd778f5ec0d4bb4ef36cbd5b6 depends: - - python - - libgcc >=14 - - libstdcxx >=14 - - __glibc >=2.17,<3.0.a0 - - zeromq >=4.3.5,<4.4.0a0 - - _python_abi3_support 1.* - - cpython >=3.12 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=compressed-mapping - size: 211567 - timestamp: 1771716961404 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312h2ac7433_2.conda - noarch: python - sha256: 475d5a751740eef86b4469b73759a42bcf82abb292fde7506081196378552cf3 - md5: 98bc7fb12f6efc9c08eeeac21008a199 + - __osx >=11.0 + license: LGPL-2.1-or-later + purls: [] + size: 31099 + timestamp: 1734670168822 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + sha256: ebbbc089b70bcde87c4121a083c724330f02a690fb9d7c6cd18c30f1b12504fa + md5: a6f6d3a31bb29e48d37ce65de54e2df0 depends: - - python - __osx >=11.0 - - libcxx >=19 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 + - libgfortran + - libgfortran5 >=14.3.0 + - llvm-openmp >=19.1.7 + constrains: + - openblas >=0.3.30,<0.3.31.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/pyzmq?source=compressed-mapping - size: 192884 - timestamp: 1771717048943 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - noarch: python - sha256: 2f31f799a46ed75518fae0be75ecc8a1b84360dbfd55096bc2fe8bd9c797e772 - md5: 2f6b79700452ef1e91f45a99ab8ffe5a + purls: [] + size: 4284132 + timestamp: 1768547079205 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.55-h132b30e_0.conda + sha256: 7a4fd29a6ee2d7f7a6e610754dfdf7410ed08f40d8d8b488a27bc0f9981d5abb + md5: 871dc88b0192ac49b6a5509932c31377 depends: - - python - - libcxx >=19 - __osx >=11.0 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=compressed-mapping - size: 191641 - timestamp: 1771717073430 -- conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda - sha256: 776363493bad83308ba30bcb88c2552632581b143e8ee25b1982c8c743e73abc - md5: 353823361b1d27eb3960efb076dfcaf6 + - libzlib >=1.3.1,<2.0a0 + license: zlib-acknowledgement + purls: [] + size: 288950 + timestamp: 1770691485950 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpq-18.4-ha770aab_0.conda + sha256: aed15f692ab1c050ed64e08e215b3a82f8d1efb87f6be54a052fa50292a88c82 + md5: aa54929e5de39fc4ddd538650cdc2c86 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 - license: LicenseRef-Qhull + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - openldap >=2.6.13,<2.7.0a0 + - openssl >=3.5.6,<4.0a0 + license: PostgreSQL purls: [] - size: 552937 - timestamp: 1720813982144 -- conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda - sha256: 79d804fa6af9c750e8b09482559814ae18cd8df549ecb80a4873537a5a31e06e - md5: dd1ea9ff27c93db7c01a7b7656bd4ad4 + run_exports: + weak: + - libpq >=18.4,<19.0a0 + size: 2626441 + timestamp: 1778787920554 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.31.1-h98f38fd_4.conda + sha256: 505d62fb2a487aff594a30f6c419f8e861fb3a47e25e407dae2779ac4a585b18 + md5: 8a6b4281c176f1695ae0015f420e6aa9 depends: - - __osx >=10.13 - - libcxx >=16 - license: LicenseRef-Qhull + - __osx >=11.0 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libcxx >=19 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 528122 - timestamp: 1720814002588 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda - sha256: 873ac689484262a51fd79bc6103c1a1bedbf524924d7f0088fb80703042805e4 - md5: 6483b1f59526e05d7d894e466b5b6924 + size: 3131502 + timestamp: 1766315339805 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.0-h7a62e17_0.conda + sha256: f71026a40df9ef28ada6d97c0f441e7ca9bafa03d0ae0c3f5f03d1971acb656b + md5: 1aecee022672c6c97c827ceb6b0e2ead depends: + - libcxx >=19 - __osx >=11.0 - - libcxx >=16 - license: LicenseRef-Qhull + - icu >=78.3,<79.0a0 + license: MIT + license_family: MIT purls: [] - size: 516376 - timestamp: 1720814307311 -- pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - name: questionary - version: 2.1.1 - sha256: a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59 - requires_dist: - - prompt-toolkit>=2.0,<4.0 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/rdkit-2025.09.5-py312h3ecb6ed_0.conda - sha256: 2a7d3f80028ddd2611c7161745e7b25ed8ea68f98e3716701031ce0ad02e65a2 - md5: 0debb97ca2e922053426914d0fa07f78 + run_exports: + weak: + - libpsl >=0.23.0,<0.24.0a0 + size: 72956 + timestamp: 1785426941596 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/librdkit-2025.09.5-h26160e4_0.conda + sha256: 60c117d66605417c59aa9888cc0cc67171edf59c763ae52bc10996866b679dc5 + md5: 1788df09e1f77cea1d40890503174d57 depends: - - __glibc >=2.17,<3.0.a0 + - __osx >=11.0 - cairo >=1.18.4,<2.0a0 - libboost >=1.86.0,<1.87.0a0 - - libboost-python >=1.86.0,<1.87.0a0 + - libcxx >=19 - libfreetype >=2.14.1 - libfreetype6 >=2.14.1 - - libgcc >=14 - - libpq >=18.1,<19.0a0 - - librdkit 2025.09.5 h3c5c181_0 - - libstdcxx >=14 - - matplotlib-base - - numpy >=1.23,<3 - - pandas - - pillow - - pycairo - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - reportlab - - sqlalchemy license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/rdkit?source=hash-mapping - size: 20209344 - timestamp: 1770019899047 -- conda: https://conda.anaconda.org/conda-forge/osx-64/rdkit-2025.09.5-py312h0981512_0.conda - sha256: 0bc438d858884ef4880bd611e6e6e1fd4b8cab6b5eb5c44dd1282056f4aa6cb7 - md5: cda1367cdd3d4e3a98ec7bfc5776ac3c + purls: [] + size: 6989824 + timestamp: 1770019047093 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda + sha256: 202be45db5726757a8ea1f374f85aacc18c504f5ff15b2558496dff4c8779c48 + md5: 9ed5ab909c449bdcae72322e44875a18 depends: - - __osx >=10.13 - - cairo >=1.18.4,<2.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libboost-python >=1.86.0,<1.87.0a0 - - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libpq >=18.1,<19.0a0 - - librdkit 2025.09.5 ha1af57f_0 - - matplotlib-base - - numpy >=1.23,<3 - - pandas - - pillow - - pycairo - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - reportlab - - sqlalchemy + - __osx >=11.0 + license: ISC + purls: [] + run_exports: + weak: + - libsodium >=1.0.22,<1.0.23.0a0 + size: 247352 + timestamp: 1779164136206 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1ae2325_0.conda + sha256: 745662565e103f290e9dc4263bbd88285082f8cf699854fe2d5f1e35a4a0d326 + md5: 0e3477c0c3e718dcf2eb74ccc8f68570 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 929203 + timestamp: 1785016131414 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + sha256: 8bfe837221390ffc6f111ecca24fa12d4a6325da0c8d131333d63d6c37f27e0a + md5: b68e8f66b94b44aaa8de4583d3d4cc40 + depends: + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/rdkit?source=hash-mapping - size: 19131272 - timestamp: 1770019208091 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda - sha256: 782a6ea9660dc2d4a9d4a3f34275107bb872c8d37d65e32f2d0ae314f3479295 - md5: ee238e1211540b376a3b199c27b1ddf5 + purls: [] + size: 279193 + timestamp: 1745608793272 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + sha256: e9248077b3fa63db94caca42c8dbc6949c6f32f94d1cafad127f9005d9b1507f + md5: e2a72ab2fa54ecb6abab2b26cde93500 + depends: + - __osx >=11.0 + - lerc >=4.0.0,<5.0a0 + - libcxx >=19 + - libdeflate >=1.25,<1.26.0a0 + - libjpeg-turbo >=3.1.0,<4.0a0 + - liblzma >=5.8.1,<6.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + size: 373892 + timestamp: 1762022345545 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.8.0-cpu_generic_h853542e_2.conda + sha256: e895a1d1140ce945cbe11da980e5f1e6226f742efdd0fd70b635958485e79806 + md5: cca2b6c3fe58d0b289349dcab482596d depends: - __osx >=11.0 - - cairo >=1.18.4,<2.0a0 - - libboost >=1.86.0,<1.87.0a0 - - libboost-python >=1.86.0,<1.87.0a0 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 - - libpq >=18.1,<19.0a0 - - librdkit 2025.09.5 h26160e4_0 - - matplotlib-base + - liblapack >=3.9.0,<4.0a0 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=19.1.7 - numpy >=1.23,<3 - - pandas - - pillow - - pycairo - - python >=3.12,<3.13.0a0 + - pybind11-abi 4 - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - - reportlab - - sqlalchemy + - sleef >=3.9.0,<4.0a0 + constrains: + - openblas * openmp_* + - pytorch 2.8.0 cpu_generic_*_2 + - libopenblas * openmp_* + - pytorch-cpu 2.8.0 + - pytorch-gpu <0.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/rdkit?source=hash-mapping - size: 18792593 - timestamp: 1770019774785 -- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 - md5: d7d95fc8287ea7bf33e0e7116d2b95ec - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 345073 - timestamp: 1765813471974 -- conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - sha256: 4614af680aa0920e82b953fece85a03007e0719c3399f13d7de64176874b80d5 - md5: eefd65452dfe7cce476a519bece46704 - depends: - - __osx >=10.13 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL purls: [] - size: 317819 - timestamp: 1765813692798 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 - md5: f8381319127120ce51e081dce4865cf4 + size: 29866556 + timestamp: 1762101159180 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + sha256: 042c7488ad97a5629ec0a991a8b2a3345599401ecc75ad6a5af73b60e6db9689 + md5: c0d87c3c8e075daf1daf6c31b53e8083 depends: - __osx >=11.0 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 313930 - timestamp: 1765813902568 -- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 - md5: 870293df500ca7e18bedefa5838a22ab - depends: - - attrs >=22.2.0 - - python >=3.10 - - rpds-py >=0.7.0 - - typing_extensions >=4.4.0 - - python license: MIT license_family: MIT - purls: - - pkg:pypi/referencing?source=hash-mapping - size: 51788 - timestamp: 1760379115194 -- conda: https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda - sha256: 61c9a2a7b4decaa300e5076ccdea874aad77ff13a55b7fd9b95744dba060eec9 - md5: c89e6c0a10804fb87536a3980685990d + purls: [] + size: 421195 + timestamp: 1753948426421 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda + sha256: a4de3f371bb7ada325e1f27a4ef7bcc81b2b6a330e46fac9c2f78ac0755ea3dd + md5: e5e7d467f80da752be17796b87fe6385 depends: - - charset-normalizer - - pillow >=9 - - python >=3.9,<4 - - rlpycairo >=0.2.0,<1 - - freetype-py >=2.3.0,<2.4 - - python + - __osx >=11.0 + constrains: + - libwebp 1.6.0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/reportlab?source=hash-mapping - size: 1718577 - timestamp: 1771171345253 -- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda - sha256: 7813c38b79ae549504b2c57b3f33394cea4f2ad083f0994d2045c2e24cb538c5 - md5: c65df89a0b2e321045a9e01d1337b182 - depends: - - python >=3.10 - - certifi >=2017.4.17 - - charset-normalizer >=2,<4 - - idna >=2.5,<4 - - urllib3 >=1.21.1,<3 - - python - constrains: - - chardet >=3.0.2,<6 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/requests?source=compressed-mapping - size: 63602 - timestamp: 1766926974520 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 - md5: 36de09a8d3e5d5e6f4ee63af49e59706 - depends: - - python >=3.9 - - six - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3339-validator?source=hash-mapping - size: 10209 - timestamp: 1733600040800 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 - md5: 912a71cc01012ee38e6b90ddd561e36f + purls: [] + size: 294974 + timestamp: 1752159906788 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda + sha256: bd3816218924b1e43b275863e21a3e13a5db4a6da74cca8e60bc3c213eb62f71 + md5: af523aae2eca6dfa1c8eec693f5b9a79 depends: - - python + - __osx >=11.0 + - pthread-stubs + - xorg-libxau >=1.0.11,<2.0a0 + - xorg-libxdmcp license: MIT license_family: MIT - purls: - - pkg:pypi/rfc3986-validator?source=hash-mapping - size: 7818 - timestamp: 1598024297745 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 - md5: 7234f99325263a5af6d4cd195035e8f2 + purls: [] + size: 323658 + timestamp: 1727278733917 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda + sha256: ff75b84cdb9e8d123db2fa694a8ac2c2059516b6cbc98ac21fb68e235d0fd354 + md5: 19edaa53885fc8205614b03da2482282 depends: - - python >=3.9 - - lark >=1.2.2 - - python + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.3 license: MIT license_family: MIT - purls: - - pkg:pypi/rfc3987-syntax?source=hash-mapping - size: 22913 - timestamp: 1752876729969 -- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda - sha256: b06ce84d6a10c266811a7d3adbfa1c11f13393b91cc6f8a5b468277d90be9590 - md5: 7a6289c50631d620652f5045a63eb573 + purls: [] + run_exports: {} + size: 466360 + timestamp: 1776377102261 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda + sha256: 2fe1d8de0854342ae9cabe408b476935f82f5636e153b3b497456264dc8ff3a1 + md5: 8e037d73747d6fe34e12d7bcac10cf21 depends: - - markdown-it-py >=2.2.0 - - pygments >=2.13.0,<3.0.0 - - python >=3.10 - - typing_extensions >=4.0.0,<5.0.0 - - python + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.3 h5ef1a60_0 + - libzlib >=1.3.2,<2.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/rich?source=compressed-mapping - size: 208472 - timestamp: 1771572730357 -- pypi: https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl - name: rich-click - version: 1.9.7 - sha256: 2f99120fca78f536e07b114d3b60333bc4bb2a0969053b1250869bcdc1b5351b - requires_dist: - - click>=8 - - colorama ; sys_platform == 'win32' - - rich>=12 - - typing-extensions>=4 ; python_full_version < '3.11' - - inline-snapshot>=0.24 ; extra == 'dev' - - jsonschema>=4 ; extra == 'dev' - - mypy>=1.14.1 ; extra == 'dev' - - nodeenv>=1.9.1 ; extra == 'dev' - - packaging>=25 ; extra == 'dev' - - pre-commit>=3.5 ; extra == 'dev' - - pytest>=8.3.5 ; extra == 'dev' - - pytest-cov>=5 ; extra == 'dev' - - rich-codex>=1.2.11 ; extra == 'dev' - - ruff>=0.12.4 ; extra == 'dev' - - typer>=0.15 ; extra == 'dev' - - types-setuptools>=75.8.0.20250110 ; extra == 'dev' - - markdown-include>=0.8.1 ; extra == 'docs' - - mike>=2.1.3 ; extra == 'docs' - - mkdocs[docs]>=1.6.1 ; extra == 'docs' - - mkdocs-github-admonitions-plugin>=0.1.1 ; extra == 'docs' - - mkdocs-glightbox>=0.4 ; extra == 'docs' - - mkdocs-include-markdown-plugin>=7.1.7 ; python_full_version >= '3.9' and extra == 'docs' - - mkdocs-material[imaging]~=9.5.18 ; extra == 'docs' - - mkdocs-material-extensions>=1.3.1 ; extra == 'docs' - - mkdocs-redirects>=1.2.2 ; extra == 'docs' - - mkdocs-rss-plugin>=1.15 ; extra == 'docs' - - mkdocstrings[python]>=0.26.1 ; extra == 'docs' - - rich-codex>=1.2.11 ; extra == 'docs' - - typer>=0.15 ; extra == 'docs' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl - name: rich-rst - version: 1.3.2 - sha256: a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a - requires_dist: - - docutils - - rich>=12.0.0 - - sphinx ; extra == 'docs' -- conda: https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda - sha256: 8d993b1a7d869855a1f6358dcc3de08dbeda9263d8c852d44bfc3900701c1e6c - md5: cc70086eaf08be7f62fd44842c013916 + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.3 + size: 41102 + timestamp: 1776377119495 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda + sha256: 507599a77c1ce823c2d3acaefaae4ead0686f183f3980467a4c4b8ba209eff40 + md5: 7177414f275db66735a17d316b0a81d6 depends: - - freetype-py >=2.3 - - pycairo >=1.20.0 - - python >=3.10 + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.3.2,<4.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/rlpycairo?source=hash-mapping - size: 15558 - timestamp: 1756864268077 -- conda: https://conda.anaconda.org/conda-forge/linux-64/rocm-core-7.0.2-h54a6638_0.conda - sha256: 2e762eb624cc396fd0ca902fd48749c61359f1eb91f1217ce11a78f66d5d16bb - md5: b56d3c2be254c7f1939167ac36a6c920 + purls: [] + size: 125507 + timestamp: 1730442214849 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + sha256: a18fa5d5bac452401459f966cf0d872224e8080c4ff93c77e168d43ab42ef9d7 + md5: f39288f0ea63ae962e1a2e4f355a0d75 depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - license: MIT - license_family: MIT + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other purls: [] - size: 20304 - timestamp: 1760541804711 -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda - sha256: 62f46e85caaba30b459da7dfcf3e5488ca24fd11675c33ce4367163ab191a42c - md5: 3ffc5a3572db8751c2f15bacf6a0e937 + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 47822 + timestamp: 1785277049190 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + sha256: 0daeedb3872ad0fdd6f0d7e7165c63488e8a315d7057907434145fba0c1e7b3d + md5: ff0820b5588b20be3b858552ecf8ffae depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.12.* *_cp312 + - __osx >=11.0 constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 383750 - timestamp: 1764543174231 -- conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.30.0-py312h8a6388b_0.conda - sha256: 3df6f3ad2697f5250d38c37c372b77cc2702b0c705d3d3a231aae9dc9f2eec62 - md5: 9adbe03b6d1b86cab37fb37709eb4e38 + - openmp 22.1.0|22.1.0.* + - intel-openmp <0.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + size: 285558 + timestamp: 1772028716784 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvmlite-0.48.0-py312hedd3284_1.conda + sha256: 172c61204e4c11fd94577fa88b834c5006c797330e834ee178684eeb7aca4381 + md5: d4613099d7163819fabc3158d82330e3 depends: - python - - __osx >=10.13 + - libcxx >=19 + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 - python_abi 3.12.* *_cp312 - constrains: - - __osx >=10.13 - license: MIT - license_family: MIT + - libzlib >=1.3.2,<2.0a0 + license: BSD-2-Clause + license_family: BSD purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 370624 - timestamp: 1764543158734 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda - sha256: ea06f6f66b1bea97244c36fd2788ccd92fd1fb06eae98e469dd95ee80831b057 - md5: a7cfbbdeb93bb9a3f249bc4c3569cd4c + - pkg:pypi/llvmlite?source=hash-mapping + run_exports: {} + size: 30228229 + timestamp: 1784043473570 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lol-html-3.0.1-h6fdd925_0.conda + sha256: 6e50d72c901df77c93e65a2cbce07cd61bbc2f7ee3da9775dc639b88168f6c17 + md5: 381123c14a77ac3d9be214dbd8e995ec depends: - - python - __osx >=11.0 - - python 3.12.* *_cpython - - python_abi 3.12.* *_cp312 constrains: - __osx >=11.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - lol-html >=3.0.1,<3.0.2.0a0 + size: 451898 + timestamp: 1785365151951 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ls-hpack-2.3.5-h1a92334_0.conda + sha256: cdd6b81d14ed7fb271a74e9ff056144c88ec703a8baa676bc27f05169a293857 + md5: 72a3080021b46d7733adfca14ddd69ed + depends: + - __osx >=11.0 license: MIT license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 358853 - timestamp: 1764543161524 -- pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl - name: ruff - version: 0.15.5 - sha256: 6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl - name: ruff - version: 0.15.5 - sha256: 89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: ruff - version: 0.15.5 - sha256: c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda - sha256: e3ad577361d67f6c078a6a7a3898bf0617b937d44dc4ccd57aa3336f2b5778dd - md5: 3e38daeb1fb05a95656ff5af089d2e4c + purls: [] + run_exports: + weak: + - ls-hpack >=2.3.5,<2.3.6.0a0 + size: 103549 + timestamp: 1779602259051 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + sha256: 94d3e2a485dab8bdfdd4837880bde3dd0d701e2b97d6134b8806b7c8e69c8652 + md5: 01511afc6cc1909c5303cf31be17b44f depends: - - __glibc >=2.17,<3.0.a0 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libgcc >=14 - - libgfortran - - libgfortran5 >=14.3.0 - - liblapack >=3.9.0,<4.0a0 - - libstdcxx >=14 - - numpy <2.7 - - numpy >=1.23,<3 - - numpy >=1.25.2 + - __osx >=11.0 + - libcxx >=18 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 148824 + timestamp: 1733741047892 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + sha256: db40fd25c6306bfda469f84cddd8b5ebb9aa08d509cecb49dfd0bb8228466d0c + md5: e56eaa1beab0e7fed559ae9c0264dd88 + depends: + - __osx >=11.0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - lzo >=2.10,<3.0a0 + size: 152755 + timestamp: 1753889267953 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda + sha256: 330394fb9140995b29ae215a19fad46fcc6691bdd1b7654513d55a19aaa091c1 + md5: 11d95ab83ef0a82cc2de12c1e0b47fe4 + depends: + - __osx >=11.0 - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 + constrains: + - jinja2 >=3.0.0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping - size: 17109648 - timestamp: 1771880675810 -- conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_0.conda - sha256: 81842a4b39f700e122c8ba729034c7fc7b3a6bfd8d3ca41fe64e2bc8b0d1a4f4 - md5: 07c955303eea8d00535164eb5f63ee97 + - pkg:pypi/markupsafe?source=hash-mapping + size: 25564 + timestamp: 1772445846939 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + sha256: 3c96c85dd723a4c16fce4446d1f0dc7d64e46b6ae4629c66d65984b8593ee999 + md5: fbc4f90b3d63ea4e6c30f7733a0b5bfd depends: - __osx >=11.0 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.22.0 + - freetype + - kiwisolver >=1.3.1 - libcxx >=19 - - libgfortran - - libgfortran5 >=14.3.0 - - liblapack >=3.9.0,<4.0a0 - - numpy <2.7 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - numpy >=1.23 - numpy >=1.23,<3 - - numpy >=1.25.2 + - packaging >=20.0 + - pillow >=8 + - pyparsing >=2.3.1 - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python-dateutil >=2.7 - python_abi 3.12.* *_cp312 - license: BSD-3-Clause - license_family: BSD + - qhull >=2020.2,<2020.3.0a0 + license: PSF-2.0 + license_family: PSF purls: - - pkg:pypi/scipy?source=compressed-mapping - size: 15312767 - timestamp: 1771881124085 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda - sha256: 7082a8c87ae32b6090681a1376e3335cf23c95608c68a3f96f3581c847f8b840 - md5: fd035cd01bb171090a990ae4f4143090 + - pkg:pypi/matplotlib?source=hash-mapping + size: 8243636 + timestamp: 1763060482877 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mdtraj-1.11.1-np2py312h2df5d9b_1.conda + sha256: bd36d146eb12964b89ebe3fb8b93ea35631e16d44b35ce061a551e44e7ca4030 + md5: 9e9a9796f7ec84ab4aeac2c5dd92c933 depends: + - python + - setuptools + - packaging + - numpy >=1.25.0 + - scipy + - pandas <3 + - pyparsing + - pytables + - snappy + - zlib - __osx >=11.0 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - libcxx >=19 - - libgfortran - - libgfortran5 >=14.3.0 - - liblapack >=3.9.0,<4.0a0 - - numpy <2.7 + - python 3.12.* *_cpython + - libzlib >=1.3.1,<2.0a0 - numpy >=1.23,<3 - - numpy >=1.25.2 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: BSD-3-Clause - license_family: BSD + constrains: + - gsd !=3.0.0 + license: LGPL-2.1-or-later purls: - - pkg:pypi/scipy?source=compressed-mapping - size: 13966986 - timestamp: 1771881089893 -- pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl - name: secretstorage - version: 3.5.0 - sha256: 0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 - requires_dist: - - cryptography>=2.0 - - jeepney>=0.6 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - sha256: 8fc024bf1a7b99fc833b131ceef4bef8c235ad61ecb95a71a6108be2ccda63e8 - md5: b70e2d44e6aa2beb69ba64206a16e4c6 + - pkg:pypi/mdtraj?source=hash-mapping + size: 1224239 + timestamp: 1769108736695 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda + sha256: 2700899ad03302a1751dbf2bca135407e470dd83ac897ab91dd8675d4300f158 + md5: a5635df796b71f6ca400fc7026f50701 + depends: + - __osx >=11.0 + - gmp >=6.3.0,<7.0a0 + - mpfr >=4.2.1,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + size: 104766 + timestamp: 1725629165420 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda + sha256: 4463e4e2aba7668e37a1b8532859191b4477a6f3602a5d6b4d64ad4c4baaeac5 + md5: 4e4ea852d54cc2b869842de5044662fb + depends: + - __osx >=11.0 + - gmp >=6.3.0,<7.0a0 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + size: 345517 + timestamp: 1725746730583 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 + md5: 068d497125e4bf8a66bf707254fff5ae + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + size: 797030 + timestamp: 1738196177597 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf-fortran-4.6.3-nompi_he024a0a_101.conda + sha256: 09879e18686cb396d706065f914c2093aeeead1b0cafb2aa30c0a7087048c938 + md5: b5a193c154d01fd4c22c86ff3a8fed1c + depends: + - libnetcdf * nompi_* + - hdf5 * nompi_* + - libgfortran + - libgfortran5 >=14.3.0 + - __osx >=11.0 + - libnetcdf >=4.10.1,<4.10.2.0a0 + - libzlib >=1.3.2,<2.0a0 + - hdf5 >=2.1.0,<3.0a0 + license: NetCDF + purls: [] + run_exports: + weak: + - netcdf-fortran >=4.6.3,<4.7.0a0 + size: 515383 + timestamp: 1783866601146 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlopt-2.11.0-np2py312h60fbb24_1.conda + sha256: 8499c919b93436ddd320cd5bb62a9f7a562e9b4d9e78c13002e3cb503f22198b + md5: cc2962cd398554508019051e2b37b73a depends: - - __osx - - pyobjc-framework-cocoa - - python >=3.10 - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 22519 - timestamp: 1770937603551 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - sha256: 59656f6b2db07229351dfb3a859c35e57cc8e8bcbc86d4e501bff881a6f771f1 - md5: 28eb91468df04f655a57bcfbb35fc5c5 + - libcxx >=19 + - __osx >=11.0 + - python 3.12.* *_cpython + - numpy >=1.23,<3 + - python_abi 3.12.* *_cp312 + license: LGPL-2.1-or-later + purls: + - pkg:pypi/nlopt?source=hash-mapping + run_exports: + weak: + - nlopt >=2.11.0,<2.12.0a0 + size: 386252 + timestamp: 1780814016910 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.66.0-py312hdf3e818_1.conda + sha256: f6db7906c12696f0a0c1ecc53f6602511a10cd2f2724f675a4e6a53baadb9de0 + md5: fbff14af0c66d8b0dc41c9e94829e58b depends: - - __linux - - python >=3.10 - python - license: BSD-3-Clause + - llvmlite >=0.48.0,<0.49.0a0 + - numpy >=1.22.3,<2.5 + - __osx >=11.0 + - llvm-openmp >=19.1.7 + - libcxx >=19 + - python_abi 3.12.* *_cp312 + - numpy >=1.23,<3 + constrains: + - tbb >=2021.6.0 + - libopenblas >=0.3.18,!=0.3.20 + - cuda-version >=11.2 + - cudatoolkit >=11.2 + - scipy >=1.0 + - cuda-python >=11.6 + license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 24108 - timestamp: 1770937597662 -- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 - md5: 8e194e7b992f99a5015edbd4ebd38efd + - pkg:pypi/numba?source=hash-mapping + run_exports: {} + size: 6054071 + timestamp: 1785940732319 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numexpr-2.14.1-py312h3de7d89_1.conda + sha256: e6bac966c7aa4207a2cc2de020041d3b729e37c68279fc73774e34f688e96b1c + md5: 0d4d77d16b5032ab0b4ca3f66f14672d depends: - - python >=3.10 + - __osx >=11.0 + - libcxx >=19 + - numpy >=1.23,<3 + - numpy >=1.23.0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/setuptools?source=compressed-mapping - size: 639697 - timestamp: 1773074868565 -- pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - name: shellingham - version: 1.5.4 - sha256: 7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d - md5: 3339e3b65d58accf4ca4fb8748ab16b3 + - pkg:pypi/numexpr?source=hash-mapping + size: 197663 + timestamp: 1762595288926 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.2-py312he281c53_1.conda + sha256: 7fd2f1a33b244129dcc2163304d103a7062fc38f01fe13945c9ea95cef12b954 + md5: 4afbe6ffff0335d25f3c5cc78b1350a4 depends: - - python >=3.9 - python - license: MIT - license_family: MIT + - libcxx >=19 + - __osx >=11.0 + - python 3.12.* *_cpython + - libblas >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 + - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/six?source=hash-mapping - size: 18455 - timestamp: 1753199211006 -- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda - sha256: 57afc2ab5bdb24cf979964018dddbc5dfaee130b415e6863765e45aed2175ee4 - md5: e8a0b4f5e82ecacffaa5e805020473cb + - pkg:pypi/numpy?source=hash-mapping + size: 6840961 + timestamp: 1770098400654 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ocl_icd_wrapper_apple-1.0.0-h27ca646_0.tar.bz2 + sha256: dd04e58c813d46c41f65561f92922d25053d2049a10759e679e26f1c785678e7 + md5: 9104716a5fb54f62431752dc863cd909 depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - - libgcc >=14 - - libstdcxx >=14 - license: BSL-1.0 + - khronos-opencl-icd-loader >=2020.6.16 + license: BSD-2-Clause + license_family: BSD purls: [] - size: 1951720 - timestamp: 1756274576844 -- conda: https://conda.anaconda.org/conda-forge/osx-64/sleef-3.9.0-h289094c_0.conda - sha256: 7b6749d48be1cea8e4191141b35fe667f776e0b0972d7cf286b276c9bbb57d9d - md5: 97fc81ba1dc812fb37000fe39afa3bf8 + size: 18631 + timestamp: 1605242282408 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/opencl-headers-2025.06.13-h286801f_0.conda + sha256: 5c6269546780749ad5f4e780f0c7d7dcaaf6c20961ebd4588d82fdcda6cdf386 + md5: f4d4d97714ae85265e4299b8e56ca6eb depends: - - __osx >=10.13 - - libcxx >=19 - - llvm-openmp >=19.1.7 - license: BSL-1.0 + - __osx >=11.0 + - libcxx >=18 + license: Apache-2.0 + license_family: APACHE purls: [] - size: 1484394 - timestamp: 1756274644799 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda - sha256: 799d0578369e67b6d0d6ecdacada411c259629fc4a500b99703c5e85d0a68686 - md5: 68f833178f171cfffdd18854c0e9b7f9 + size: 55476 + timestamp: 1749853726041 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + sha256: 60aca8b9f94d06b852b296c276b3cf0efba5a6eb9f25feb8708570d3a74f00e4 + md5: 4b5d3a91320976eec71678fad1e3569b depends: - __osx >=11.0 - libcxx >=19 - - llvm-openmp >=19.1.7 - license: BSL-1.0 - purls: [] - size: 587027 - timestamp: 1756274982526 -- conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda - sha256: 48f3f6a76c34b2cfe80de9ce7f2283ecb55d5ed47367ba91e8bb8104e12b8f11 - md5: 98b6c9dc80eb87b2519b97bcf7e578dd - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - license: BSD-3-Clause + - libpng >=1.6.55,<1.7.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-2-Clause license_family: BSD purls: [] - size: 45829 - timestamp: 1762948049098 -- conda: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.2.2-h01f5ddf_1.conda - sha256: 1525e6d8e2edf32dabfe2a8e2fc8bf2df81c5ef9f0b5374a3d4ccfa672bfd949 - md5: 2e993292ec18af5cd480932d448598cf + size: 319697 + timestamp: 1772625397692 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openldap-2.6.13-hf7f56bc_0.conda + sha256: 4a7b691a5b2241ee10f59a9e51f68be4cf1e4294829cebb40d198139a561e780 + md5: 7940b03e5c1e85b0c8b8a74f3011783f depends: + - __osx >=11.0 + - cyrus-sasl >=2.1.28,<3.0a0 + - krb5 >=1.22.2,<1.23.0a0 - libcxx >=19 - - __osx >=10.13 - license: BSD-3-Clause + - openssl >=3.5.6,<4.0a0 + license: OLDAP-2.8 license_family: BSD purls: [] - size: 40023 - timestamp: 1762948053450 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda - sha256: cb9305ede19584115f43baecdf09a3866bfcd5bcca0d9e527bd76d9a1dbe2d8d - md5: fca4a2222994acd7f691e57f94b750c5 + run_exports: + weak: + - openldap >=2.6.13,<2.7.0a0 + size: 844724 + timestamp: 1775742074928 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openmm-8.4.0-py312haf4a1b8_2_khronos.conda + build_number: 2 + sha256: 76334121b6ede512d6c1f49e4a63aa8b1adcf97fd9a6481e556b02bf83c2c59f + md5: e768e0a1c90d00377e8531e27359c0aa depends: + - __osx >=11.0 + - khronos-opencl-icd-loader >=2024.10.24 - libcxx >=19 + - numpy >=1.23,<3 + - ocl_icd_wrapper_apple + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - scipy + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/openmm?source=hash-mapping + size: 11119876 + timestamp: 1765254816298 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + sha256: 361f5c5e60052abc12bdd1b50d7a1a43e6a6653aab99a2263bf2288d709dcf67 + md5: f4f6ad63f98f64191c3e77c5f5f29d76 + depends: - __osx >=11.0 - license: BSD-3-Clause - license_family: BSD + - ca-certificates + license: Apache-2.0 + license_family: Apache purls: [] - size: 38883 - timestamp: 1762948066818 -- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad - md5: 03fe290994c5e4ec17293cfb6bdce520 + size: 3104268 + timestamp: 1769556384749 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.19.0-py312h766f71e_0.conda + sha256: 76ea1f736255aeea8b917ea826a0c186ab267656b156709ca374df5dc8ff4d26 + md5: a98fa17883d7862ef3049f171a03b9dc depends: - - python >=3.10 + - __osx >=11.0 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - typing-extensions >=4.12 license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/sniffio?source=hash-mapping - size: 15698 - timestamp: 1762941572482 -- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - sha256: 23b71ecf089967d2900126920e7f9ff18cdcef82dbff3e2f54ffa360243a17ac - md5: 18de09b20462742fe093ba39185d9bac + - pkg:pypi/optree?source=hash-mapping + size: 429640 + timestamp: 1771868574672 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + sha256: 93aa5b02e2394080a32fee9fb151da3384d317a42472586850abb37b28f314db + md5: fcbba82205afa4956c39136c68929385 depends: - - python >=3.10 - license: MIT - license_family: MIT + - __osx >=11.0 + - libcxx >=19 + - numpy >=1.22.4 + - numpy >=1.23,<3 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python-dateutil >=2.8.2 + - python-tzdata >=2022.7 + - python_abi 3.12.* *_cp312 + - pytz >=2020.1 + constrains: + - xarray >=2022.12.0 + - scipy >=1.10.0 + - tabulate >=0.9.0 + - pytables >=3.8.0 + - xlsxwriter >=3.0.5 + - pyxlsb >=1.0.10 + - odfpy >=1.4.1 + - zstandard >=0.19.0 + - fastparquet >=2022.12.0 + - gcsfs >=2022.11.0 + - beautifulsoup4 >=4.11.2 + - qtpy >=2.3.0 + - xlrd >=2.0.1 + - pandas-gbq >=0.19.0 + - s3fs >=2022.11.0 + - pyreadstat >=1.2.0 + - tzdata >=2022.7 + - html5lib >=1.1 + - fsspec >=2022.11.0 + - lxml >=4.9.2 + - numexpr >=2.8.4 + - blosc >=1.21.3 + - openpyxl >=3.1.0 + - pyarrow >=10.0.1 + - python-calamine >=0.1.7 + - numba >=0.56.4 + - sqlalchemy >=2.0.0 + - pyqt5 >=5.15.9 + - psycopg2 >=2.9.6 + - bottleneck >=1.3.6 + - matplotlib >=3.6.3 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/soupsieve?source=hash-mapping - size: 38187 - timestamp: 1769034509657 -- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.48-py312h5253ce2_0.conda - sha256: ae7154760597c7112d6bce24fe0fc7850ee830503f7646fcfd4d35fece34d162 - md5: 8279b6aea65b8141f6ec46804321bf8a + - pkg:pypi/pandas?source=hash-mapping + size: 13893993 + timestamp: 1764615503244 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/parmed-4.3.1-py312h0ec3ceb_0.conda + sha256: cfcfaf36fd9eae9f995e535b87f5762343589ce13e760b477128da7fdf1fd367 + md5: cb620cbf05811cd2b2ea088d3417b028 depends: - - python - - greenlet !=0.4.17 - - typing-extensions >=4.6.0 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 + - libcxx >=19 + - numpy >=1.14 + - pandas + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT + license: LGPL-2.1-or-later + license_family: LGPL purls: - - pkg:pypi/sqlalchemy?source=compressed-mapping - size: 3704046 - timestamp: 1772644902869 -- conda: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.48-py312hba6025d_0.conda - sha256: 36455c443ecc0ae2e6886c9fb3237e29d0f2378ab6f175e28822abca534c70fc - md5: c1cee82f5cdb8120930947da5e1e2675 + - pkg:pypi/parmed?source=hash-mapping + size: 19381423 + timestamp: 1769768478779 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + sha256: 5e2e443f796f2fd92adf7978286a525fb768c34e12b1ee9ded4000a41b2894ba + md5: 9b4190c4055435ca3502070186eba53a depends: - - python - - greenlet !=0.4.17 - - typing-extensions >=4.6.0 - __osx >=11.0 - - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT - purls: - - pkg:pypi/sqlalchemy?source=compressed-mapping - size: 3691545 - timestamp: 1772644924605 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda - sha256: 9ed0b76709c7832bb0a516f29d4c7f441c29444e8f99c07c33911f446161dc55 - md5: 08da67cb6a9f3170cab44cb9c4958856 + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 850231 + timestamp: 1763655726735 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda + build_number: 7 + sha256: b0c55040d2994fd6bf2f83786561d92f72306d982d6ea12889acad24a9bf43b8 + md5: ba3cbe93f99e896765422cc5f7c3a79e + license: GPL-1.0-or-later OR Artistic-1.0-Perl + purls: [] + size: 14439531 + timestamp: 1703311335652 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.1.1-py312h4e908a4_0.conda + sha256: 4729476631c025dfce555a5fd97f1b0b97e765e7c01aee5b7e59b880d8335006 + md5: 537e6079e50e219252d016254b0d2573 depends: - python - - greenlet !=0.4.17 - - typing-extensions >=4.6.0 - __osx >=11.0 - python 3.12.* *_cpython + - libwebp-base >=1.6.0,<2.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 - python_abi 3.12.* *_cp312 - license: MIT - license_family: MIT + - libjpeg-turbo >=3.1.2,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - libxcb >=1.17.0,<2.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - lcms2 >=2.18,<3.0a0 + license: HPND purls: - - pkg:pypi/sqlalchemy?source=compressed-mapping - size: 3693700 - timestamp: 1772644975255 -- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 - md5: b1b505328da7a6b246787df4b5a49fbc + - pkg:pypi/pillow?source=hash-mapping + size: 954096 + timestamp: 1770794152238 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda + sha256: 29c9b08a9b8b7810f9d4f159aecfd205fce051633169040005c0b7efad4bc718 + md5: 17c3d745db6ea72ae2fce17e7338547f depends: - - asttokens - - executing - - pure_eval - - python >=3.9 + - __osx >=11.0 + - libcxx >=19 license: MIT license_family: MIT - purls: - - pkg:pypi/stack-data?source=hash-mapping - size: 26988 - timestamp: 1733569565672 -- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda - sha256: 1c8057e6875eba958aa8b3c1a072dc9a75d013f209c26fd8125a5ebd3abbec0c - md5: 32d866e43b25275f61566b9391ccb7b5 - depends: - - __unix - - cpython - - gmpy2 >=2.0.8 - - mpmath >=1.1.0,<1.5 - - python >=3.10 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/sympy?source=compressed-mapping - size: 4661767 - timestamp: 1771952371059 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-h8d10470_1.conda - sha256: 2e3238234ae094d5a5f7c559410ea8875351b6bac0d9d0e576bf64b732b8029e - md5: e3259be3341da4bc06c5b7a78c8bf1bd - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libhwloc >=2.12.1,<2.12.2.0a0 - - libstdcxx >=14 - license: Apache-2.0 - license_family: APACHE purls: [] - size: 181262 - timestamp: 1762509955687 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - sha256: 6b6727a13d1ca6a23de5e6686500d0669081a117736a87c8abf444d60c1e40eb - md5: 17b43cee5cc84969529d5d0b0309b2cb + size: 248045 + timestamp: 1754665282033 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda + sha256: 6d0e21c76436374635c074208cfeee62a94d3c37d0527ad67fd8a7615e546a05 + md5: fd856899666759403b3c16dcba2f56ff depends: - - __unix - - ptyprocess - - python >=3.10 - - tornado >=6.1.0 - python - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 24749 - timestamp: 1766513766867 -- pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - name: threadpoolctl - version: 3.6.0 - sha256: 43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 - md5: f1acf5fdefa8300de697982bcb1761c9 - depends: - - python >=3.5 - - webencodings >=0.4 + - __osx >=11.0 + - python 3.12.* *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/tinycss2?source=hash-mapping - size: 28285 - timestamp: 1729802975370 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac - md5: cffd3bdd58090148f4cfcd831f4b26ab - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - constrains: - - xorg-libx11 >=1.8.12,<2.0a0 - license: TCL - license_family: BSD - purls: [] - size: 3301196 - timestamp: 1769460227866 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - sha256: 7f0d9c320288532873e2d8486c331ec6d87919c9028208d3f6ac91dc8f99a67b - md5: 6e6efb7463f8cef69dbcb4c2205bf60e + - pkg:pypi/psutil?source=hash-mapping + size: 239031 + timestamp: 1769678393511 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda + sha256: 8ed65e17fbb0ca944bfb8093b60086e3f9dd678c3448b5de212017394c247ee3 + md5: 415816daf82e0b23a736a069a75e9da7 depends: - - __osx >=10.13 - - libzlib >=1.3.1,<2.0a0 - license: TCL - license_family: BSD + - __osx >=11.0 + license: MIT + license_family: MIT purls: [] - size: 3282953 - timestamp: 1769460532442 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 - md5: a9d86bc62f39b94c4661716624eb21b0 + size: 8381 + timestamp: 1726802424786 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycairo-1.29.0-py312h2437029_0.conda + sha256: 343bdde9b0d80caed910fc6a071d86b81c7c7240be1d5fdbfa7eb9153c55136d + md5: 7a27a42db2d7cc92779abbc26e24d343 depends: - __osx >=11.0 + - cairo >=1.18.4,<2.0a0 + - libexpat >=2.7.1,<3.0a0 - libzlib >=1.3.1,<2.0a0 - license: TCL - license_family: BSD - purls: [] - size: 3127137 - timestamp: 1769460817696 -- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda - sha256: 62940c563de45790ba0f076b9f2085a842a65662268b02dd136a8e9b1eaf47a8 - md5: 72e780e9aa2d0a3295f59b1874e3768b + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: LGPL-2.1-only OR MPL-1.1 + purls: + - pkg:pypi/pycairo?source=hash-mapping + size: 105984 + timestamp: 1763047065752 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py312hd3c0895_0.conda + sha256: 4e583aab0854a3a9c88e3e5c55348f568a1fddce43952a74892e490537327522 + md5: affb6b478c21735be55304d47bfe1c63 depends: - - python >=3.10 - python + - typing-extensions >=4.6.0,!=4.7.0 + - python 3.12.* *_cpython + - __osx >=11.0 + - python_abi 3.12.* *_cp312 + constrains: + - __osx >=11.0 license: MIT license_family: MIT purls: - - pkg:pypi/tomli?source=compressed-mapping - size: 21453 - timestamp: 1768146676791 -- pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl - name: tomli-w - version: 1.2.0 - sha256: 188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl - name: tomlkit - version: 0.14.0 - sha256: 592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-1.8.2-pyhd8ed1ab_0.conda - sha256: d3171f8d6689db5208e36f1acab7bcaa38fe4ce0cbbd0cfa59e100813754ceab - md5: 5d6e1558a945d0333ae7dc557add7201 - depends: - - lightning-utilities >=0.8.0 - - numpy >1.20.0 - - packaging >17.1 - - python >=3.9 - - pytorch >=2.0.0 - - setuptools - - typing_extensions - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/torchmetrics?source=hash-mapping - size: 396413 - timestamp: 1756932778045 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py312h4c3975b_0.conda - sha256: bed440cad040f0fe76266f9a527feecbaf00385b68a96532aa69614fe5153f8e - md5: e03a4bf52d2170d64c816b2a52972097 + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1715338 + timestamp: 1746625327204 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + sha256: b015f430fe9ea2c53e14be13639f1b781f68deaa5ae74cd8c1d07720890cd02a + md5: c65d7abdc9e60fd3af0ed852591adf1b depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: Apache-2.0 - license_family: Apache + - setuptools + license: MIT + license_family: MIT purls: - - pkg:pypi/tornado?source=compressed-mapping - size: 850918 - timestamp: 1765458857375 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py312h404bc50_0.conda - sha256: 44ba44075b754a0da5a476d5cdc6783e290d3f26d355c9fc236abaaefa902d4d - md5: fc935f8c37abef2b3cc3b9f15b951c6d + - pkg:pypi/pyobjc-core?source=hash-mapping + size: 476750 + timestamp: 1763151865523 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda + sha256: 3710f5ae09c2ea77ba4d82cc51e876d9fc009b878b197a40d3c6347c09ae7d7c + md5: f0bae1b67ece138378923e340b940051 depends: - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: Apache-2.0 - license_family: Apache + license: MIT + license_family: MIT purls: - - pkg:pypi/tornado?source=hash-mapping - size: 854453 - timestamp: 1765836802876 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda - sha256: 114bfa1b859a64c589c428fce0ff8e358d8f0aaa7b98d353b94a95c7bceae640 - md5: fde4548a1e99c14eea9752f270ab68aa + - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping + size: 377723 + timestamp: 1763160705325 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytables-3.11.1-py312h4f4653e_5.conda + sha256: 331621809e293900a77ecf1f1ca15bd8b9ef08794d44b64f0ca8bc0cf2f6d651 + md5: c9e305aba5a02811158661684dd486ae depends: - __osx >=11.0 + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - c-blosc2 >=3.2.3,<3.3.0a0 + - hdf5 >=2.1.0,<3.0a0 + - libcxx >=19 + - libzlib >=1.3.2,<2.0a0 + - numexpr + - numpy >=1.20.0 + - numpy >=1.25,<3 + - packaging + - py-cpuinfo - python >=3.12,<3.13.0a0 - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: Apache-2.0 - license_family: Apache + - typing-extensions >=4.4.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/tornado?source=hash-mapping - size: 854598 - timestamp: 1765836762571 -- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda - sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 - md5: e5ce43272193b38c2e9037446c1d9206 + - pkg:pypi/tables?source=hash-mapping + run_exports: {} + size: 1761858 + timestamp: 1784970902224 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda + sha256: e658e647a4a15981573d6018928dec2c448b10c77c557c29872043ff23c0eb6a + md5: 8e7608172fa4d1b90de9a745c2fd2b81 depends: - - python >=3.10 - - __unix - - python - license: MPL-2.0 and MIT - purls: - - pkg:pypi/tqdm?source=compressed-mapping - size: 94132 - timestamp: 1770153424136 -- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 - md5: 019a7385be9af33791c989871317e1ed + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + purls: [] + size: 12127424 + timestamp: 1772730755512 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.8.0-cpu_generic_py312_h05d1414_2.conda + sha256: c1638d808b837232a0912bd627fed4157078cc97ee7784bafde9284b38dfc77f + md5: 67c81fe3c3b188ef6beeb22f21db51ae depends: - - python >=3.9 + - __osx >=11.0 + - filelock + - fsspec + - jinja2 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libtorch 2.8.0.* *_2 + - libuv >=1.51.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=19.1.7 + - networkx + - nomkl + - numpy >=1.23,<3 + - optree >=0.13.0 + - pybind11 + - pybind11-abi 4 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - setuptools + - sleef >=3.9.0,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.10.0 + constrains: + - pytorch-cpu 2.8.0 + - pytorch-gpu <0.0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/traitlets?source=hash-mapping - size: 110051 - timestamp: 1733367480074 -- pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl - name: trove-classifiers - version: 2026.1.14.14 - sha256: 1f9553927f18d0513d8e5ff80ab8980b8202ce37ecae0e3274ed2ef11880e74d -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - purls: [] - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 - md5: a0a4a3035667fc34f29bfbd5c190baa6 + - pkg:pypi/torch?source=hash-mapping + size: 23288257 + timestamp: 1762101789000 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda + sha256: 737959262d03c9c305618f2d48c7f1691fb996f14ae420bfd05932635c99f873 + md5: 95a5f0831b5e0b1075bbd80fcffc52ac depends: - - python >=3.10 - - typing_extensions >=4.12.0 + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - yaml >=0.2.5,<0.3.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/typing-inspection?source=hash-mapping - size: 18923 - timestamp: 1764158430324 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d + - pkg:pypi/pyyaml?source=hash-mapping + size: 187278 + timestamp: 1770223990452 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda + noarch: python + sha256: 2f31f799a46ed75518fae0be75ecc8a1b84360dbfd55096bc2fe8bd9c797e772 + md5: 2f6b79700452ef1e91f45a99ab8ffe5a depends: - - python >=3.10 - python - license: PSF-2.0 - license_family: PSF + - libcxx >=19 + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.12 + - zeromq >=4.3.5,<4.4.0a0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/typing-extensions?source=hash-mapping - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c - md5: f6d7aa696c67756a650e91e15e88223c + - pkg:pypi/pyzmq?source=hash-mapping + size: 191641 + timestamp: 1771717073430 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + sha256: 873ac689484262a51fd79bc6103c1a1bedbf524924d7f0088fb80703042805e4 + md5: 6483b1f59526e05d7d894e466b5b6924 depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/typing-utils?source=hash-mapping - size: 15183 - timestamp: 1733331395943 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c - md5: ad659d0a2b3e47e38d829aa8cad2d610 - license: LicenseRef-Public-Domain + - __osx >=11.0 + - libcxx >=16 + license: LicenseRef-Qhull purls: [] - size: 119135 - timestamp: 1767016325805 -- conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda - sha256: 895bbfe9ee25c98c922799de901387d842d7c01cae45c346879865c6a907f229 - md5: 0b6c506ec1f272b685240e70a29261b8 + size: 516376 + timestamp: 1720814307311 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rdkit-2025.09.5-py312h7e68b95_0.conda + sha256: 782a6ea9660dc2d4a9d4a3f34275107bb872c8d37d65e32f2d0ae314f3479295 + md5: ee238e1211540b376a3b199c27b1ddf5 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - __osx >=11.0 + - cairo >=1.18.4,<2.0a0 + - libboost >=1.86.0,<1.87.0a0 + - libboost-python >=1.86.0,<1.87.0a0 + - libcxx >=19 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libpq >=18.1,<19.0a0 + - librdkit 2025.09.5 h26160e4_0 + - matplotlib-base + - numpy >=1.23,<3 + - pandas + - pillow + - pycairo - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 - license: Apache-2.0 - license_family: Apache + - reportlab + - sqlalchemy + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/unicodedata2?source=compressed-mapping - size: 410641 - timestamp: 1770909099497 -- conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py312h1a1c95f_0.conda - sha256: 29bdc3648a4b8011c572003234ebfaa13666aa71760c9f89f798758ccebd7448 - md5: 563dc18b746f873408b76e068e2b4baa + - pkg:pypi/rdkit?source=hash-mapping + size: 18792593 + timestamp: 1770019774785 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 + md5: f8381319127120ce51e081dce4865cf4 depends: - - __osx >=10.13 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/unicodedata2?source=hash-mapping - size: 406056 - timestamp: 1770909495553 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda - sha256: e935d0c11581e31e89ce4899a28b16f924d1a3c1af89f18f8a2c5f5728b3107f - md5: 45b836f333fd3e282c16fff7dc82994e + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 313930 + timestamp: 1765813902568 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + sha256: ea06f6f66b1bea97244c36fd2788ccd92fd1fb06eae98e469dd95ee80831b057 + md5: a7cfbbdeb93bb9a3f249bc4c3569cd4c depends: + - python - __osx >=11.0 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython + - python 3.12.* *_cpython - python_abi 3.12.* *_cp312 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/unicodedata2?source=compressed-mapping - size: 415828 - timestamp: 1770909782683 -- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 - md5: e7cb0f5745e4c5035a460248334af7eb - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/uri-template?source=hash-mapping - size: 23990 - timestamp: 1733323714454 -- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a - md5: 9272daa869e03efe68833e3dc7a02130 - depends: - - backports.zstd >=1.0.0 - - brotli-python >=1.2.0 - - h2 >=4,<5 - - pysocks >=1.5.6,<2.0,!=1.5.7 - - python >=3.10 + constrains: + - __osx >=11.0 license: MIT license_family: MIT purls: - - pkg:pypi/urllib3?source=hash-mapping - size: 103172 - timestamp: 1767817860341 -- pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl - name: userpath - version: 1.9.2 - sha256: 2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d - requires_dist: - - click - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - name: uv - version: 0.10.9 - sha256: 880dd4cffe4bd184e8871ddf4c7d3c3b042e1f16d2682310644aa8d61eaea3e6 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: uv - version: 0.10.9 - sha256: 7c9d6deb30edbc22123be75479f99fb476613eaf38a8034c0e98bba24a344179 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - name: uv - version: 0.10.9 - sha256: a7a784254380552398a6baf4149faf5b31a4003275f685c28421cf8197178a08 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl - name: virtualenv - version: 21.2.0 - sha256: 1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f - requires_dist: - - distlib>=0.3.7,<1 - - filelock>=3.24.2,<4 ; python_full_version >= '3.10' - - filelock>=3.16.1,<=3.19.1 ; python_full_version < '3.10' - - importlib-metadata>=6.6 ; python_full_version < '3.8' - - platformdirs>=3.9.1,<5 - - python-discovery>=1 - - typing-extensions>=4.13.2 ; python_full_version < '3.11' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl - name: wcmatch - version: '10.1' - sha256: 5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a - requires_dist: - - bracex>=2.1.1 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda - sha256: e298b508b2473c4227206800dfb14c39e4b14fd79d4636132e9e1e4244cdf4aa - md5: c3197f8c0d5b955c904616b716aca093 + - pkg:pypi/rpds-py?source=hash-mapping + size: 358853 + timestamp: 1764543161524 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/s2n-1.7.6-h1b28cb6_0.conda + sha256: d2133326625aabcd20a7908bd2783cd207adf1a042c0512b493e49599fd5315a + md5: aa98fac113d87d7501704ad35ed071f2 depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/wcwidth?source=compressed-mapping - size: 71550 - timestamp: 1770634638503 -- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - sha256: 21f6c8a20fe050d09bfda3fb0a9c3493936ce7d6e1b3b5f8b01319ee46d6c6f6 - md5: 6639b6b0d8b5a284f027a2003669aa65 + - __osx >=11.0 + - openssl >=3.5.7,<4.0a0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - s2n >=1.7.6,<1.7.7.0a0 + size: 277238 + timestamp: 1784674786218 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.0-np2py312he5ca3e3_0.conda + sha256: e140dce2c42d0a121a113e29fbde4fd95a69cdf528ea195558ba25b809b439b7 + md5: b2b777cdad320a08da92bb1f58040bfd depends: - - python >=3.10 + - python + - numpy >=1.24.1 + - scipy >=1.10.0 + - joblib >=1.4.0 + - threadpoolctl >=3.5.0 + - narwhals >=2.0.1 + - python 3.12.* *_cpython + - llvm-openmp >=19.1.7 + - libcxx >=19 + - __osx >=11.0 + - numpy >=1.23,<3 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/webcolors?source=hash-mapping - size: 18987 - timestamp: 1761899393153 -- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 - md5: 2841eb5bfc75ce15e9a0054b98dcd64d + - pkg:pypi/scikit-learn?source=hash-mapping + run_exports: {} + size: 9400478 + timestamp: 1780401354221 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h0f234b1_0.conda + sha256: 7082a8c87ae32b6090681a1376e3335cf23c95608c68a3f96f3581c847f8b840 + md5: fd035cd01bb171090a990ae4f4143090 depends: - - python >=3.9 + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/webencodings?source=hash-mapping - size: 15496 - timestamp: 1733236131358 -- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 - md5: 2f1ed718fcd829c184a6d4f0f2e07409 + - pkg:pypi/scipy?source=hash-mapping + size: 13966986 + timestamp: 1771881089893 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda + sha256: 799d0578369e67b6d0d6ecdacada411c259629fc4a500b99703c5e85d0a68686 + md5: 68f833178f171cfffdd18854c0e9b7f9 depends: - - python >=3.10 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/websocket-client?source=hash-mapping - size: 61391 - timestamp: 1759928175142 -- conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - sha256: 826af5e2c09e5e45361fa19168f46ff524e7a766022615678c3a670c45895d9a - md5: dc257b7e7cad9b79c1dfba194e92297b + - __osx >=11.0 + - libcxx >=19 + - llvm-openmp >=19.1.7 + license: BSL-1.0 + purls: [] + size: 587027 + timestamp: 1756274982526 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + sha256: cb9305ede19584115f43baecdf09a3866bfcd5bcca0d9e527bd76d9a1dbe2d8d + md5: fca4a2222994acd7f691e57f94b750c5 depends: - - python >=3.10 + - libcxx >=19 + - __osx >=11.0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/widgetsnbextension?source=hash-mapping - size: 889195 - timestamp: 1762040404362 -- conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.2-pyhcf101f3_0.conda - sha256: 5001a8b0a26810f5b8e20a05bca8cd458b0f6dcfbe00bf648aec017b1fd341f1 - md5: e91301ac8b2ad640df6165db24914332 + purls: [] + size: 38883 + timestamp: 1762948066818 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.48-py312hb3ab3e3_0.conda + sha256: 9ed0b76709c7832bb0a516f29d4c7f441c29444e8f99c07c33911f446161dc55 + md5: 08da67cb6a9f3170cab44cb9c4958856 depends: - - python >=3.10 - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - __osx >=11.0 + - python 3.12.* *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/xmltodict?source=hash-mapping - size: 20026 - timestamp: 1758191083015 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b - md5: fb901ff28063514abb6046c9ec2c4a45 + - pkg:pypi/sqlalchemy?source=hash-mapping + size: 3693700 + timestamp: 1772644975255 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 + md5: a9d86bc62f39b94c4661716624eb21b0 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: MIT - license_family: MIT + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD purls: [] - size: 58628 - timestamp: 1734227592886 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libice-1.1.2-h6e16a3a_0.conda - sha256: ab190f758a1d7cf2bdd3656e6eb90b7316cdd03a32214638f691e02ad798aaed - md5: d894608e2c18127545d67a096f1b4bab + size: 3127137 + timestamp: 1769460817696 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py312h4409184_0.conda + sha256: 114bfa1b859a64c589c428fce0ff8e358d8f0aaa7b98d353b94a95c7bceae640 + md5: fde4548a1e99c14eea9752f270ab68aa depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 50154 - timestamp: 1734227708757 + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 854598 + timestamp: 1765836762571 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda + sha256: e935d0c11581e31e89ce4899a28b16f924d1a3c1af89f18f8a2c5f5728b3107f + md5: 45b836f333fd3e282c16fff7dc82994e + depends: + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/unicodedata2?source=hash-mapping + size: 415828 + timestamp: 1770909782683 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libice-1.1.2-h5505292_0.conda sha256: 0e68b75a51901294ab21c031dcc1e485a65770a4893f98943b0908c4217b14e1 md5: daf3b34253eea046c9ab94e0c3b2f83d @@ -10920,30 +12469,6 @@ packages: purls: [] size: 48418 timestamp: 1734227712919 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 - md5: 1c74ff8c35dcadf952a16f752ca5aa49 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libuuid >=2.38.1,<3.0a0 - - xorg-libice >=1.1.2,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 27590 - timestamp: 1741896361728 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libsm-1.2.6-h6e16a3a_0.conda - sha256: 9f0cb0a0a94a76f07ed449ee404c83fb91e77cd732cd0dcff395e90cc02338ef - md5: 267dc632a1c41345622c935bb6026dc4 - depends: - - __osx >=10.13 - - xorg-libice >=1.1.2,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 24556 - timestamp: 1741896589948 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libsm-1.2.6-h5505292_0.conda sha256: 9bd3cb47ad7bb6c2d0b3b39d76c0e0a7b1d39fc76524fe76a7ff014073467bf5 md5: a01171a0aee17fc4e74a50971a87755d @@ -10955,29 +12480,6 @@ packages: purls: [] size: 24419 timestamp: 1741896544082 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda - sha256: 516d4060139dbb4de49a4dcdc6317a9353fb39ebd47789c14e6fe52de0deee42 - md5: 861fb6ccbc677bb9a9fb2468430b9c6a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libxcb >=1.17.0,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 839652 - timestamp: 1770819209719 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libx11-1.8.13-hb12da3d_0.conda - sha256: 579c55d463309b4c1f13213ee64546b341c5ca8d4b21afed7c5c7c2475781e57 - md5: 186cc85e36539d096fd18a221053ec1f - depends: - - __osx >=10.13 - - libxcb >=1.17.0,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 785295 - timestamp: 1770819968527 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libx11-1.8.13-hf948f5a_0.conda sha256: e8889281828ee19afb2fdd7b5d7a4b7c2e013b2ae38af4529040de542f92b065 md5: 85b1ce864f9a18468db4c583c7778c7d @@ -10989,27 +12491,6 @@ packages: purls: [] size: 756862 timestamp: 1770819743113 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda - sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b - md5: b2895afaf55bf96a8c8282a2e47a5de0 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 15321 - timestamp: 1762976464266 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda - sha256: 928f28bd278c7da674b57d71b2e7f4ac4e7c7ce56b0bf0f60d6a074366a2e76d - md5: 47f1b8b4a76ebd0cd22bd7153e54a4dc - depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 13810 - timestamp: 1762977180568 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda sha256: adae11db0f66f86156569415ed79cda75b2dbf4bea48d1577831db701438164f md5: 78b548eed8227a689f93775d5d23ae09 @@ -11020,27 +12501,6 @@ packages: purls: [] size: 14105 timestamp: 1762976976084 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 - md5: 1dafce8548e38671bea82e3f5c6ce22f - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 20591 - timestamp: 1762976546182 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda - sha256: b7b291cc5fd4e1223058542fca46f462221027779920dd433d68b98e858a4afc - md5: 435446d9d7db8e094d2c989766cfb146 - depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 19067 - timestamp: 1762977101974 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda sha256: f7fa0de519d8da589995a1fe78ef74556bb8bc4172079ae3a8d20c3c81354906 md5: 9d1299ace1924aa8f4e0bc8e71dd0cf7 @@ -11051,29 +12511,6 @@ packages: purls: [] size: 19156 timestamp: 1762977035194 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda - sha256: 79c60fc6acfd3d713d6340d3b4e296836a0f8c51602327b32794625826bd052f - md5: 34e54f03dfea3e7a2dcf1453a85f1085 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - xorg-libx11 >=1.8.12,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 50326 - timestamp: 1769445253162 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxext-1.3.7-hf3981d6_0.conda - sha256: 9dd99f57c23ecb0629e18155162c49b2a874d40f6014ae249722e767cb5168a9 - md5: be170f3b869f5cd0e517aac01898129c - depends: - - __osx >=10.13 - - xorg-libx11 >=1.8.12,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 43673 - timestamp: 1769446004494 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxext-1.3.7-h84a0fba_0.conda sha256: 18bbf20b4da142b368e1ae8c2124a3fd7148e2003480ad8b1acdcaa3e6454b07 md5: 72851739795cdef9bb7124114c630df9 @@ -11085,45 +12522,6 @@ packages: purls: [] size: 42748 timestamp: 1769445838425 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - sha256: 044c7b3153c224c6cedd4484dd91b389d2d7fd9c776ad0f4a34f099b3389f4a1 - md5: 96d57aba173e878a2089d5638016dc5e - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - xorg-libx11 >=1.8.10,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 33005 - timestamp: 1734229037766 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - sha256: a8afba4a55b7b530eb5c8ad89737d60d60bc151a03fbef7a2182461256953f0e - md5: 279b0de5f6ba95457190a1c459a64e31 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - xorg-libice >=1.1.1,<2.0a0 - - xorg-libsm >=1.2.4,<2.0a0 - - xorg-libx11 >=1.8.10,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 379686 - timestamp: 1731860547604 -- conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxt-1.3.1-h6e16a3a_0.conda - sha256: c1bfd12f50739d4fbf3dc47bd8f0e584e2797a6214bf2c0a2eef0cc6eaed5c65 - md5: 31f0e8aa8a497df5461768a15898dbad - depends: - - __osx >=10.13 - - xorg-libice >=1.1.1,<2.0a0 - - xorg-libsm >=1.2.4,<2.0a0 - - xorg-libx11 >=1.8.10,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 199297 - timestamp: 1731860558365 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxt-1.3.1-h5505292_0.conda sha256: c32235891d65e49e97babe649c45ec2e40a148b4e6ca4cae4ed84811238e0aae md5: a5c47d582f31083353559dc9aff907c3 @@ -11137,27 +12535,6 @@ packages: purls: [] size: 185960 timestamp: 1731860774152 -- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad - md5: a77f85f77be52ff59391544bfe73390a - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: MIT - license_family: MIT - purls: [] - size: 85189 - timestamp: 1753484064210 -- conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - sha256: a335161bfa57b64e6794c3c354e7d49449b28b8d8a7c4ed02bf04c3f009953f9 - md5: a645bb90997d3fc2aea0adf6517059bd - depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 79419 - timestamp: 1753484072608 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac md5: 78a0fe9e9c50d2c381e8ee47e3ea437d @@ -11168,116 +12545,36 @@ packages: purls: [] size: 83386 timestamp: 1753484079473 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - sha256: 47cfe31255b91b4a6fa0e9dbaf26baa60ac97e033402dbc8b90ba5fee5ffe184 - md5: 8035e5b54c08429354d5d64027041cad - depends: - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 310648 - timestamp: 1757370847287 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - sha256: 30aa5a2e9c7b8dbf6659a2ccd8b74a9994cdf6f87591fcc592970daa6e7d3f3c - md5: d940d809c42fbf85b05814c3290660f5 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + sha256: 01fd50d2801b23b59fafea6bf704a6c5faf0f5969104400eae0e6572cb2e5304 + md5: d31c0e54c4f9c51100ec8c812ee925d1 depends: - - __osx >=10.13 - libcxx >=19 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 259628 - timestamp: 1757371000392 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - sha256: b6f9c130646e5971f6cad708e1eee278f5c7eea3ca97ec2fdd36e7abb764a7b8 - md5: 26f39dfe38a2a65437c29d69906a0f68 - depends: - __osx >=11.0 - - libcxx >=19 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.22,<1.0.23.0a0 license: MPL-2.0 license_family: MOZILLA purls: [] - size: 244772 - timestamp: 1757371008525 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae - md5: 30cd29cb87d819caead4d55184c1d115 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/zipp?source=hash-mapping - size: 24194 - timestamp: 1764460141901 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda - sha256: 5d7c0e5f0005f74112a34a7425179f4eb6e73c92f5d109e6af4ddeca407c92ab - md5: c9f075ab2f33b3bbee9e62d4ad0a6cd8 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libzlib 1.3.1 hb9d3cd8_2 - license: Zlib - license_family: Other - purls: [] - size: 92286 - timestamp: 1727963153079 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - sha256: 219edbdfe7f073564375819732cbf7cc0d7c7c18d3f546a09c2dfaf26e4d69f3 - md5: c989e0295dcbdc08106fe5d9e935f0b9 - depends: - - __osx >=10.13 - - libzlib 1.3.1 hd23fc13_2 - license: Zlib - license_family: Other - purls: [] - size: 88544 - timestamp: 1727963189976 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - sha256: 58f8860756680a4831c1bf4f294e2354d187f2e999791d53b1941834c4b37430 - md5: e3170d898ca6cb48f1bb567afb92f775 + run_exports: + weak: + - zeromq >=4.3.5,<4.4.0a0 + size: 245404 + timestamp: 1779124076307 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda + sha256: ab46d85e4fcffff1b1c5cac517afa40b7ae784cf76fc9da1f55f6a6934291eb6 + md5: 2c966485853aa27985fbec7e3fcc4e77 depends: - __osx >=11.0 - - libzlib 1.3.1 h8359307_2 - license: Zlib - license_family: Other - purls: [] - size: 77606 - timestamp: 1727963209370 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - sha256: ea4e50c465d70236408cb0bfe0115609fd14db1adcd8bd30d8918e0291f8a75f - md5: 2aadb0d17215603a82a2a6b0afd9a4cb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - license: Zlib - license_family: Other - purls: [] - size: 122618 - timestamp: 1770167931827 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - sha256: 4a1beb656761c7d8c9a53474bfd3932c30d82af5d93a32b8ef626c01c059d981 - md5: b3ecb6480fd46194e3f7dd0ff4445dff - depends: - - __osx >=10.13 - - libcxx >=19 + - libzlib 1.3.2 h8088a28_3 license: Zlib license_family: Other purls: [] - size: 120464 - timestamp: 1770168263684 + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 81744 + timestamp: 1785277062753 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda sha256: a339606a6b224bb230ff3d711e801934f3b3844271df9720165e0353716580d4 md5: d99c2a23a31b0172e90f456f580b695e @@ -11289,28 +12586,6 @@ packages: purls: [] size: 94375 timestamp: 1770168363685 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 - md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 - depends: - - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 601375 - timestamp: 1764777111296 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - sha256: 47101a4055a70a4876ffc87b750ab2287b67eca793f21c8224be5e1ee6394d3f - md5: 727109b184d680772e3122f40136d5ca - depends: - - __osx >=10.13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 528148 - timestamp: 1764777156963 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 md5: ab136e4c34e97f34fb621d2592a393d8 @@ -11322,3 +12597,1014 @@ packages: purls: [] size: 433413 timestamp: 1764777166076 +- pypi: ./ + name: mdfactory + requires_dist: + - cyclopts + - loguru + - matplotlib + - mdanalysis>=2.0 + - networkx + - numpy + - pandas + - platformdirs + - pyarrow + - pydantic>=2.0 + - pyyaml + - questionary + - scipy + - bump-my-version ; extra == 'dev' + - hatch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pyinstrument ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-mock ; extra == 'dev' + - ruff ; extra == 'dev' + - tqdm ; extra == 'dev' + - foundry-dev-tools ; extra == 'foundry' + - parsl>=2024.1.1 ; extra == 'parsl' + - submitit>=1.5.1 ; extra == 'submitit' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + name: docutils + version: 0.22.4 + sha256: d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl + name: tblib + version: 3.2.2 + sha256: 26bdccf339bcce6a88b2b5432c988b266ebbe63a4e593f6b578b1d2e723d2b76 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl + name: ruff + version: 0.15.15 + sha256: ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/08/e8/baae856d6764901cd13fe928cd4086ed755ae22f66928301a9327a4a2a3e/mdanalysis-2.10.0-cp312-cp312-macosx_11_0_arm64.whl + name: mdanalysis + version: 2.10.0 + sha256: 100a3beb970ff296eade14d491533997eea8e6cffce067f453cdc41df9a7eb78 + requires_dist: + - numpy>=1.26.0 + - griddataformats>=0.4.0 + - mmtf-python>=1.0.0 + - joblib>=0.12 + - scipy>=1.5.0 + - matplotlib>=1.5.1 + - tqdm>=4.43.0 + - threadpoolctl + - packaging + - filelock + - mda-xdrlib + - netcdf4>=1.0 ; extra == 'extra-formats' + - h5py>=2.10 ; extra == 'extra-formats' + - chemfiles>=0.10 ; extra == 'extra-formats' + - parmed ; extra == 'extra-formats' + - pyedr>=0.7.0 ; extra == 'extra-formats' + - pytng>=0.2.3 ; extra == 'extra-formats' + - gsd>3.0.0 ; extra == 'extra-formats' + - rdkit>=2022.9.1 ; extra == 'extra-formats' + - imdclient>=0.2.2 ; extra == 'extra-formats' + - biopython>=1.80 ; extra == 'analysis' + - seaborn ; extra == 'analysis' + - scikit-learn ; extra == 'analysis' + - tidynamics>=1.0.0 ; extra == 'analysis' + - networkx>=2.0 ; extra == 'analysis' + - waterdynamics ; extra == 'analysis' + - pathsimanalysis ; extra == 'analysis' + - mdahole2 ; extra == 'analysis' + - sphinx ; extra == 'doc' + - sphinx-sitemap ; extra == 'doc' + - mdanalysis-sphinx-theme>=1.3.0 ; extra == 'doc' + - sphinxcontrib-bibtex ; extra == 'doc' + - pybtex ; extra == 'doc' + - pybtex-docutils ; extra == 'doc' + - dask ; extra == 'parallel' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl + name: python-dotenv + version: 1.2.2 + sha256: 1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a + requires_dist: + - click>=5.0 ; extra == 'cli' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl + name: loguru + version: 0.7.3 + sha256: 31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c + requires_dist: + - colorama>=0.3.4 ; sys_platform == 'win32' + - aiocontextvars>=0.2.0 ; python_full_version < '3.7' + - win32-setctime>=1.0.0 ; sys_platform == 'win32' + - pre-commit==4.0.1 ; python_full_version >= '3.9' and extra == 'dev' + - tox==3.27.1 ; python_full_version < '3.8' and extra == 'dev' + - tox==4.23.2 ; python_full_version >= '3.8' and extra == 'dev' + - pytest==6.1.2 ; python_full_version < '3.8' and extra == 'dev' + - pytest==8.3.2 ; python_full_version >= '3.8' and extra == 'dev' + - pytest-cov==2.12.1 ; python_full_version < '3.8' and extra == 'dev' + - pytest-cov==5.0.0 ; python_full_version == '3.8.*' and extra == 'dev' + - pytest-cov==6.0.0 ; python_full_version >= '3.9' and extra == 'dev' + - pytest-mypy-plugins==1.9.3 ; python_full_version >= '3.6' and python_full_version < '3.8' and extra == 'dev' + - pytest-mypy-plugins==3.1.0 ; python_full_version >= '3.8' and extra == 'dev' + - colorama==0.4.5 ; python_full_version < '3.8' and extra == 'dev' + - colorama==0.4.6 ; python_full_version >= '3.8' and extra == 'dev' + - freezegun==1.1.0 ; python_full_version < '3.8' and extra == 'dev' + - freezegun==1.5.0 ; python_full_version >= '3.8' and extra == 'dev' + - exceptiongroup==1.1.3 ; python_full_version >= '3.7' and python_full_version < '3.11' and extra == 'dev' + - mypy==0.910 ; python_full_version < '3.6' and extra == 'dev' + - mypy==0.971 ; python_full_version == '3.6.*' and extra == 'dev' + - mypy==1.4.1 ; python_full_version == '3.7.*' and extra == 'dev' + - mypy==1.13.0 ; python_full_version >= '3.8' and extra == 'dev' + - sphinx==8.1.3 ; python_full_version >= '3.11' and extra == 'dev' + - sphinx-rtd-theme==3.0.2 ; python_full_version >= '3.11' and extra == 'dev' + - myst-parser==4.0.0 ; python_full_version >= '3.11' and extra == 'dev' + - build==1.2.2 ; python_full_version >= '3.11' and extra == 'dev' + - twine==6.0.1 ; python_full_version >= '3.11' and extra == 'dev' + requires_python: '>=3.5,<4.0' +- pypi: https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl + name: distro + version: 1.9.0 + sha256: 7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + name: rich-rst + version: 1.3.2 + sha256: a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a + requires_dist: + - docutils + - rich>=12.0.0 + - sphinx ; extra == 'docs' +- pypi: https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl + name: truststore + version: 0.10.4 + sha256: adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + name: dill + version: 0.4.1 + sha256: 1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d + requires_dist: + - objgraph>=1.7.2 ; extra == 'graph' + - gprof2dot>=2022.7.29 ; extra == 'profile' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl + name: distlib + version: 0.4.1 + sha256: 9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97 +- pypi: https://files.pythonhosted.org/packages/26/d9/8fa5571ddd21b2b7189bd8b0bb4e90be1659a54dda5af51c7f6bf2b5666f/pyinstrument-5.1.2-cp312-cp312-macosx_10_13_universal2.whl + name: pyinstrument + version: 5.1.2 + sha256: 2519865d4bf58936f2506c1c46a82d29a20f3239aa50c941df1ca9618c7da5f0 + requires_dist: + - pytest ; extra == 'test' + - flaky ; extra == 'test' + - trio ; extra == 'test' + - cffi>=1.17.0 ; extra == 'test' + - greenlet>=3 ; extra == 'test' + - pytest-asyncio==0.23.8 ; extra == 'test' + - ipython ; extra == 'test' + - click ; extra == 'bin' + - nox ; extra == 'bin' + - sphinx==7.4.7 ; extra == 'docs' + - myst-parser==3.0.1 ; extra == 'docs' + - furo==2024.7.18 ; extra == 'docs' + - sphinxcontrib-programoutput==0.17 ; extra == 'docs' + - sphinx-autobuild==2024.4.16 ; extra == 'docs' + - numpy ; extra == 'examples' + - django ; extra == 'examples' + - litestar ; extra == 'examples' + - typing-extensions ; extra == 'types' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.14.1 + sha256: 6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl + name: pyarrow + version: 23.0.1 + sha256: 813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl + name: sortedcontainers + version: 2.4.0 + sha256: a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 +- pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + name: threadpoolctl + version: 3.6.0 + sha256: 43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/36/01/b168791bfbfb0322ef6d38d236f6f17a02e41fb7753e23e4cdb0f19ac969/bump_my_version-1.3.0-py3-none-any.whl + name: bump-my-version + version: 1.3.0 + sha256: 3cdaa54588d2443a29303b77e7539417187952c3d22f87bfdd32c0fe6af2f570 + requires_dist: + - click<8.4 + - httpx>=0.28.1 + - pydantic-settings + - pydantic>=2.0.0 + - questionary + - rich + - rich-click + - tomlkit + - wcmatch>=8.5.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/38/ad/57a31ea9ffc53a2fb5cd9a60b5edb9e4df7c526ba80be4517c6d73cf4fa7/uv-0.11.18-py3-none-macosx_10_12_x86_64.whl + name: uv + version: 0.11.18 + sha256: 90685bda9e15600ae9a2a10c326008a69f28d8652555a2e760e1493e4b1ae7b5 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + name: questionary + version: 2.1.1 + sha256: a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59 + requires_dist: + - prompt-toolkit>=2.0,<4.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3c/f1/efea3da858043ed9c078f507ab744b6d00933c7bc8a75a24821937600178/mmtf_python-1.1.3-py2.py3-none-any.whl + name: mmtf-python + version: 1.1.3 + sha256: 502031c509a8a6d73e042781abbd88b84c1afffe65097eb0c1b70f329ffd1e6e + requires_dist: + - msgpack>=1.0.0 + - check-manifest ; extra == 'dev' + - coverage ; extra == 'test' +- pypi: https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl + name: coverage + version: 7.14.1 + sha256: a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl + name: userpath + version: 1.9.2 + sha256: 2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d + requires_dist: + - click + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/4c/16/ad7fc0f8948075b9ab7f6957519492e861b2f16469f1ab0a916f7c3c4243/mdanalysis-2.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: mdanalysis + version: 2.10.0 + sha256: c1403b319f5b9054d3135984948ecd485702d3960a867c491283f836451dc2bd + requires_dist: + - numpy>=1.26.0 + - griddataformats>=0.4.0 + - mmtf-python>=1.0.0 + - joblib>=0.12 + - scipy>=1.5.0 + - matplotlib>=1.5.1 + - tqdm>=4.43.0 + - threadpoolctl + - packaging + - filelock + - mda-xdrlib + - netcdf4>=1.0 ; extra == 'extra-formats' + - h5py>=2.10 ; extra == 'extra-formats' + - chemfiles>=0.10 ; extra == 'extra-formats' + - parmed ; extra == 'extra-formats' + - pyedr>=0.7.0 ; extra == 'extra-formats' + - pytng>=0.2.3 ; extra == 'extra-formats' + - gsd>3.0.0 ; extra == 'extra-formats' + - rdkit>=2022.9.1 ; extra == 'extra-formats' + - imdclient>=0.2.2 ; extra == 'extra-formats' + - biopython>=1.80 ; extra == 'analysis' + - seaborn ; extra == 'analysis' + - scikit-learn ; extra == 'analysis' + - tidynamics>=1.0.0 ; extra == 'analysis' + - networkx>=2.0 ; extra == 'analysis' + - waterdynamics ; extra == 'analysis' + - pathsimanalysis ; extra == 'analysis' + - mdahole2 ; extra == 'analysis' + - sphinx ; extra == 'doc' + - sphinx-sitemap ; extra == 'doc' + - mdanalysis-sphinx-theme>=1.3.0 ; extra == 'doc' + - sphinxcontrib-bibtex ; extra == 'doc' + - pybtex ; extra == 'doc' + - pybtex-docutils ; extra == 'doc' + - dask ; extra == 'parallel' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + name: docstring-parser + version: 0.17.0 + sha256: cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708 + requires_dist: + - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'dev' + - pytest ; extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'docs' + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl + name: hatchling + version: 1.30.1 + sha256: 161eacafb3c6f91526e92116d21426369f2c36e98c36a864f11a96345ad4ee31 + requires_dist: + - packaging>=24.2 + - pathspec>=0.10.1 + - pluggy>=1.0.0 + - tomli>=1.2.2 ; python_full_version < '3.11' + - trove-classifiers + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/57/8c/8d037a8010e92959631fa05811df43e4710b8e828bd18ea3da73189d5ce8/mdanalysis-2.10.0-cp312-cp312-macosx_10_13_x86_64.whl + name: mdanalysis + version: 2.10.0 + sha256: 97ef2e20916516db9553e7e5ff509d8be5fbb21f9bac87d74cc634e99508d3d1 + requires_dist: + - numpy>=1.26.0 + - griddataformats>=0.4.0 + - mmtf-python>=1.0.0 + - joblib>=0.12 + - scipy>=1.5.0 + - matplotlib>=1.5.1 + - tqdm>=4.43.0 + - threadpoolctl + - packaging + - filelock + - mda-xdrlib + - netcdf4>=1.0 ; extra == 'extra-formats' + - h5py>=2.10 ; extra == 'extra-formats' + - chemfiles>=0.10 ; extra == 'extra-formats' + - parmed ; extra == 'extra-formats' + - pyedr>=0.7.0 ; extra == 'extra-formats' + - pytng>=0.2.3 ; extra == 'extra-formats' + - gsd>3.0.0 ; extra == 'extra-formats' + - rdkit>=2022.9.1 ; extra == 'extra-formats' + - imdclient>=0.2.2 ; extra == 'extra-formats' + - biopython>=1.80 ; extra == 'analysis' + - seaborn ; extra == 'analysis' + - scikit-learn ; extra == 'analysis' + - tidynamics>=1.0.0 ; extra == 'analysis' + - networkx>=2.0 ; extra == 'analysis' + - waterdynamics ; extra == 'analysis' + - pathsimanalysis ; extra == 'analysis' + - mdahole2 ; extra == 'analysis' + - sphinx ; extra == 'doc' + - sphinx-sitemap ; extra == 'doc' + - mdanalysis-sphinx-theme>=1.3.0 ; extra == 'doc' + - sphinxcontrib-bibtex ; extra == 'doc' + - pybtex ; extra == 'doc' + - pybtex-docutils ; extra == 'doc' + - dask ; extra == 'parallel' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl + name: pytest-mock + version: 3.15.1 + sha256: 0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d + requires_dist: + - pytest>=6.2.5 + - pre-commit ; extra == 'dev' + - pytest-asyncio ; extra == 'dev' + - tox ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl + name: typeguard + version: 4.5.2 + sha256: fcf9de18bd945cdb4c7b996e12b4c51ce83f92f191314a6d7cf1739586ec98cf + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - typing-extensions>=4.14.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5e/ed/139ce29a650d9d4d35ce78b3fc4741da3d2a6d0f4db7b88d5f4ffcff8c77/griddataformats-1.1.0-py3-none-any.whl + name: griddataformats + version: 1.1.0 + sha256: d3dfc4d9f16d5e7bb4d387195434f24ef0339aa246ebfbf357fdfb4b26828385 + requires_dist: + - numpy>=1.21 + - scipy + - mrcfile + - pytest ; extra == 'test' + - numpy ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/65/1e/0f58a02b4310e495159421946abd298ae53897ea8f5ebb6b82fb7fd0f71d/parsl-2026.6.1-py3-none-any.whl + name: parsl + version: 2026.6.1 + sha256: cba922c33d2a3df214700ec6477d72254d20f6664b7366d3ce853524e27731b9 + requires_dist: + - pyzmq>=17.1.2 + - typeguard>=2.10,!=3.*,<5 + - typing-extensions>=4.6,<5 + - dill + - tblib + - requests + - sortedcontainers + - psutil>=5.5.1 + - setproctitle + - filelock>=3.13,<4 + - sqlalchemy>=2,<2.1 ; extra == 'monitoring' + - pydot>=1.4.2 ; extra == 'visualization' + - networkx>=3.2,<3.3 ; extra == 'visualization' + - flask>=1.0.2 ; extra == 'visualization' + - flask-sqlalchemy ; extra == 'visualization' + - pandas>=2.2,<3 ; extra == 'visualization' + - plotly ; extra == 'visualization' + - python-daemon ; extra == 'visualization' + - boto3 ; extra == 'aws' + - kubernetes ; extra == 'kubernetes' + - ipython<=8.6.0 ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - sphinx>=7.4,<8 ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - google-auth ; extra == 'google-cloud' + - google-api-python-client ; extra == 'google-cloud' + - python-gssapi ; extra == 'gssapi' + - azure<=4 ; extra == 'azure' + - msrestazure ; extra == 'azure' + - work-queue ; extra == 'workqueue' + - pyyaml ; extra == 'flux' + - cffi ; extra == 'flux' + - jsonschema ; extra == 'flux' + - proxystore ; extra == 'proxystore' + - radical-pilot==1.90 ; extra == 'radical-pilot' + - radical-utils==1.90 ; extra == 'radical-pilot' + - globus-compute-sdk>=2.34.0 ; extra == 'globus-compute' + - globus-sdk ; extra == 'globus-transfer' + - sqlalchemy>=2,<2.1 ; extra == 'all' + - pydot>=1.4.2 ; extra == 'all' + - networkx>=3.2,<3.3 ; extra == 'all' + - flask>=1.0.2 ; extra == 'all' + - flask-sqlalchemy ; extra == 'all' + - pandas>=2.2,<3 ; extra == 'all' + - plotly ; extra == 'all' + - python-daemon ; extra == 'all' + - boto3 ; extra == 'all' + - kubernetes ; extra == 'all' + - ipython<=8.6.0 ; extra == 'all' + - nbsphinx ; extra == 'all' + - sphinx>=7.4,<8 ; extra == 'all' + - sphinx-rtd-theme ; extra == 'all' + - google-auth ; extra == 'all' + - google-api-python-client ; extra == 'all' + - python-gssapi ; extra == 'all' + - azure<=4 ; extra == 'all' + - msrestazure ; extra == 'all' + - work-queue ; extra == 'all' + - pyyaml ; extra == 'all' + - cffi ; extra == 'all' + - jsonschema ; extra == 'all' + - proxystore ; extra == 'all' + - radical-pilot==1.90 ; extra == 'all' + - radical-utils==1.90 ; extra == 'all' + - globus-compute-sdk>=2.34.0 ; extra == 'all' + - globus-sdk ; extra == 'all' + requires_python: '>=3.10.0' +- pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl + name: tomlkit + version: 0.15.0 + sha256: 4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6d/97/a87901aef6b7e7e4a34c6dd6cc17dca8594a592ef9d9dd765fca2b7facf7/rich_click-1.9.8-py3-none-any.whl + name: rich-click + version: 1.9.8 + sha256: 12873865396e6927835d4eabb1cc3996edcd65b7ac9b2391a29eca4f335a2f93 + requires_dist: + - click>=8 + - colorama ; sys_platform == 'win32' + - rich>=12 + - typing-extensions>=4 ; python_full_version < '3.11' + - inline-snapshot>=0.24 ; extra == 'dev' + - jsonschema>=4 ; extra == 'dev' + - mypy>=1.14.1 ; extra == 'dev' + - nodeenv>=1.9.1 ; extra == 'dev' + - packaging>=25 ; extra == 'dev' + - pre-commit>=3.5 ; extra == 'dev' + - pytest>=8.3.5 ; extra == 'dev' + - pytest-cov>=5 ; extra == 'dev' + - rich-codex>=1.2.11 ; extra == 'dev' + - ruff>=0.12.4 ; extra == 'dev' + - typer>=0.15,<0.26 ; extra == 'dev' + - types-setuptools>=75.8.0.20250110 ; extra == 'dev' + - markdown-include>=0.8.1 ; extra == 'docs' + - mike>=2.1.3 ; extra == 'docs' + - mkdocs[docs]>=1.6.1 ; extra == 'docs' + - mkdocs-github-admonitions-plugin>=0.1.1 ; extra == 'docs' + - mkdocs-glightbox>=0.4 ; extra == 'docs' + - mkdocs-include-markdown-plugin>=7.1.7 ; python_full_version >= '3.9' and extra == 'docs' + - mkdocs-material[imaging]~=9.5.18 ; extra == 'docs' + - mkdocs-material-extensions>=1.3.1 ; extra == 'docs' + - mkdocs-redirects>=1.2.2 ; extra == 'docs' + - mkdocs-rss-plugin>=1.15 ; extra == 'docs' + - mkdocstrings[python]>=0.26.1 ; extra == 'docs' + - rich-codex>=1.2.11 ; extra == 'docs' + - typer>=0.15,<0.26 ; extra == 'docs' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl + name: hyperlink + version: 21.0.0 + sha256: e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4 + requires_dist: + - idna>=2.5 + - typing ; python_full_version < '3.5' + requires_python: '>=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/6f/50/0512adb83cadfeaa1a215dc9784defff5043c5aa052d15015e3d8013af75/pyinstrument-5.1.2-cp312-cp312-macosx_11_0_arm64.whl + name: pyinstrument + version: 5.1.2 + sha256: 059442106b8b5de29ae5ac1bdc20d044fed4da534b8caba434b6ffb119037bf5 + requires_dist: + - pytest ; extra == 'test' + - flaky ; extra == 'test' + - trio ; extra == 'test' + - cffi>=1.17.0 ; extra == 'test' + - greenlet>=3 ; extra == 'test' + - pytest-asyncio==0.23.8 ; extra == 'test' + - ipython ; extra == 'test' + - click ; extra == 'bin' + - nox ; extra == 'bin' + - sphinx==7.4.7 ; extra == 'docs' + - myst-parser==3.0.1 ; extra == 'docs' + - furo==2024.7.18 ; extra == 'docs' + - sphinxcontrib-programoutput==0.17 ; extra == 'docs' + - sphinx-autobuild==2024.4.16 ; extra == 'docs' + - numpy ; extra == 'examples' + - django ; extra == 'examples' + - litestar ; extra == 'examples' + - typing-extensions ; extra == 'types' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/6f/cb/3f4ee8233f30c7926f1ed4885ff32b79ec7ce3210370f43e1cb2b385bed6/mrcfile-1.5.4-py2.py3-none-any.whl + name: mrcfile + version: 1.5.4 + sha256: 195370a13db5ce19600499f9bdf90fd9888979bf39de19a17c19024436c6f4c2 + requires_dist: + - numpy>=1.16.0 +- pypi: https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl + name: coverage + version: 7.14.1 + sha256: fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl + name: trove-classifiers + version: 2026.6.1.19 + sha256: ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3 +- pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + name: jaraco-classes + version: 3.4.0 + sha256: f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + requires_dist: + - more-itertools + - sphinx>=3.5 ; extra == 'docs' + - jaraco-packaging>=9.3 ; extra == 'docs' + - rst-linker>=1.9 ; extra == 'docs' + - furo ; extra == 'docs' + - sphinx-lint ; extra == 'docs' + - jaraco-tidelift>=1.4 ; extra == 'docs' + - pytest>=6 ; extra == 'testing' + - pytest-checkdocs>=2.4 ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-mypy ; extra == 'testing' + - pytest-enabler>=2.2 ; extra == 'testing' + - pytest-ruff>=0.2.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl + name: pre-commit + version: 4.6.0 + sha256: e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b + requires_dist: + - cfgv>=2.0.0 + - identify>=1.0.0 + - nodeenv>=0.11.1 + - pyyaml>=5.1 + - virtualenv>=20.10.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + name: keyring + version: 25.7.0 + sha256: be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f + requires_dist: + - pywin32-ctypes>=0.2.0 ; sys_platform == 'win32' + - secretstorage>=3.2 ; sys_platform == 'linux' + - jeepney>=0.4.2 ; sys_platform == 'linux' + - importlib-metadata>=4.11.4 ; python_full_version < '3.12' + - jaraco-classes + - jaraco-functools + - jaraco-context + - pytest>=6,!=8.1.* ; extra == 'test' + - pyfakefs ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; extra == 'type' + - pygobject-stubs ; extra == 'type' + - shtab ; extra == 'type' + - types-pywin32 ; extra == 'type' + - shtab>=1.1.0 ; extra == 'completion' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/87/01/6ec7210775ea5e4989a10d89eda6c5ea7ff06caa614231ad533d74fecac8/cyclopts-4.8.0-py3-none-any.whl + name: cyclopts + version: 4.8.0 + sha256: ef353da05fec36587d4ebce7a6e4b27515d775d184a23bab4b01426f93ddc8d4 + requires_dist: + - attrs>=23.1.0 + - docstring-parser>=0.15,<4.0 + - rich-rst>=1.3.1,<2.0.0 + - rich>=13.6.0 + - tomli>=2.0.0 ; python_full_version < '3.11' + - typing-extensions>=4.8.0 ; python_full_version < '3.11' + - ipdb>=0.13.9 ; extra == 'debug' + - line-profiler>=3.5.1 ; extra == 'debug' + - coverage[toml]>=5.1 ; extra == 'dev' + - mkdocs>=1.4.0 ; extra == 'dev' + - pre-commit>=2.16.0 ; extra == 'dev' + - pydantic>=2.11.2,<3.0.0 ; extra == 'dev' + - pytest-cov>=3.0.0 ; extra == 'dev' + - pytest-mock>=3.7.0 ; extra == 'dev' + - pytest>=8.2.0 ; extra == 'dev' + - pyyaml>=6.0.1 ; extra == 'dev' + - syrupy>=4.0.0 ; extra == 'dev' + - toml>=0.10.2,<1.0.0 ; extra == 'dev' + - trio>=0.10.0 ; extra == 'dev' + - gitpython>=3.1.31 ; extra == 'docs' + - myst-parser[linkify]>=3.0.1,<5.0.0 ; extra == 'docs' + - sphinx-autodoc-typehints>=1.25.2,<4.0.0 ; extra == 'docs' + - sphinx-copybutton>=0.5,<1.0 ; extra == 'docs' + - sphinx-rtd-dark-mode>=1.3.0,<2.0.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0,<4.0.0 ; extra == 'docs' + - sphinx>=7.4.7,<8.2.0 ; extra == 'docs' + - markdown>=3.3 ; extra == 'mkdocs' + - mkdocs>=1.4.0 ; extra == 'mkdocs' + - pymdown-extensions>=10.0 ; extra == 'mkdocs' + - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'toml' + - trio>=0.10.0 ; extra == 'trio' + - pyyaml>=6.0.1 ; extra == 'yaml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/87/ce/ae2911859847f9ba1d6b23027e53481cbeb50b93234f355a968d300ca2cb/httpx2-2.3.0-py3-none-any.whl + name: httpx2 + version: 2.3.0 + sha256: 6f393663bdf6dbe7fe90118e3eb5b2bd024a675cae0390ac08cec9198812d8b7 + requires_dist: + - anyio + - httpcore2==2.3.0 + - idna + - truststore>=0.10 + - brotli ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'brotli' + - click==8.* ; extra == 'cli' + - pygments==2.* ; extra == 'cli' + - rich>=10,<15 ; extra == 'cli' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - zstandard>=0.18.0 ; python_full_version < '3.14' and extra == 'zstd' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + name: nodeenv + version: 1.10.0 + sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- pypi: https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl + name: ruff + version: 0.15.15 + sha256: 77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/91/4d/2ca3ca9906ce6e05070f431c54d54ccbaf57a980cfa58032d35b0b0ac1f8/pyinstrument-5.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: pyinstrument + version: 5.1.2 + sha256: 12af1e83795b6c640d657d339014dd1ff718b182dec736d7d1f1d8a97534eb53 + requires_dist: + - pytest ; extra == 'test' + - flaky ; extra == 'test' + - trio ; extra == 'test' + - cffi>=1.17.0 ; extra == 'test' + - greenlet>=3 ; extra == 'test' + - pytest-asyncio==0.23.8 ; extra == 'test' + - ipython ; extra == 'test' + - click ; extra == 'bin' + - nox ; extra == 'bin' + - sphinx==7.4.7 ; extra == 'docs' + - myst-parser==3.0.1 ; extra == 'docs' + - furo==2024.7.18 ; extra == 'docs' + - sphinxcontrib-programoutput==0.17 ; extra == 'docs' + - sphinx-autobuild==2024.4.16 ; extra == 'docs' + - numpy ; extra == 'examples' + - django ; extra == 'examples' + - litestar ; extra == 'examples' + - typing-extensions ; extra == 'types' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + name: identify + version: 2.6.19 + sha256: 20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a + requires_dist: + - ukkonen ; extra == 'license' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl + name: cryptography + version: 48.0.0 + sha256: a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f + requires_dist: + - cffi>=2.0.0 ; platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + requires_python: '!=3.9.0,>=3.9,!=3.9.1' +- pypi: https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl + name: jaraco-functools + version: 4.5.0 + sha256: 79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4 + requires_dist: + - more-itertools + - pytest>=6,!=8.1.* ; extra == 'test' + - jaraco-classes ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.14 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl + name: pyarrow + version: 23.0.1 + sha256: f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl + name: bracex + version: '2.6' + sha256: 0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + name: pytest-cov + version: 7.1.0 + sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 + requires_dist: + - coverage[toml]>=7.10.6 + - pluggy>=1.2 + - pytest>=7 + - process-tests ; extra == 'testing' + - pytest-xdist ; extra == 'testing' + - virtualenv ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl + name: pydantic-settings + version: 2.14.1 + sha256: 6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de + requires_dist: + - pydantic>=2.7.0 + - python-dotenv>=0.21.0 + - typing-inspection>=0.4.0 + - boto3>=1.35.0 ; extra == 'aws-secrets-manager' + - types-boto3[secretsmanager] ; extra == 'aws-secrets-manager' + - azure-identity>=1.16.0 ; extra == 'azure-key-vault' + - azure-keyvault-secrets>=4.8.0 ; extra == 'azure-key-vault' + - google-cloud-secret-manager>=2.23.1 ; extra == 'gcp-secret-manager' + - tomli>=2.0.1 ; extra == 'toml' + - pyyaml>=6.0.1 ; extra == 'yaml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/af/3b/130515418bdd4be1aec5941ca2fa53dc0281750434e835ff633e6f8cd944/uv-0.11.18-py3-none-macosx_11_0_arm64.whl + name: uv + version: 0.11.18 + sha256: 0571ec649f25e2cca9eb994637aa1ae27ab3db58f87c5a9b5f9776d5d7cd2298 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl + name: jeepney + version: 0.9.0 + sha256: 97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 + requires_dist: + - pytest ; extra == 'test' + - pytest-trio ; extra == 'test' + - pytest-asyncio>=0.17 ; extra == 'test' + - testpath ; extra == 'test' + - trio ; extra == 'test' + - async-timeout ; python_full_version < '3.11' and extra == 'test' + - trio ; extra == 'trio' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl + name: secretstorage + version: 3.5.0 + sha256: 0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 + requires_dist: + - cryptography>=2.0 + - jeepney>=0.6 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl + name: virtualenv + version: 21.4.2 + sha256: 854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae + requires_dist: + - distlib>=0.3.7,<1 + - filelock>=3.24.2,<4 ; python_full_version >= '3.10' + - filelock>=3.16.1,<=3.19.1 ; python_full_version < '3.10' + - importlib-metadata>=6.6 ; python_full_version < '3.8' + - platformdirs>=3.9.1,<5 + - python-discovery>=1.4 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c2/dd/3357218c69360d1cecc196c230c9a1d5c9afd5dba362056e23e60a5e64e5/httpcore2-2.3.0-py3-none-any.whl + name: httpcore2 + version: 2.3.0 + sha256: 477e9e334f74e5240dcac002e890580f36a57d40ff0fb14cc9655731d23b8415 + requires_dist: + - h11>=0.16 + - truststore>=0.10 + - anyio>=4.5.0,<5.0 ; extra == 'asyncio' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - trio>=0.22.0,<1.0 ; extra == 'trio' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl + name: tomli-w + version: 1.2.0 + sha256: 188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl + name: python-discovery + version: 1.4.0 + sha256: 26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da + requires_dist: + - filelock>=3.15.4 + - platformdirs>=4.3.6,<5 + - furo>=2025.12.19 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' + - sphinx>=9.1 ; extra == 'docs' + - sphinxcontrib-mermaid>=2 ; extra == 'docs' + - sphinxcontrib-towncrier>=0.4 ; extra == 'docs' + - towncrier>=25.8 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'testing' + - coverage>=7.5.4 ; extra == 'testing' + - pytest-mock>=3.14 ; extra == 'testing' + - pytest>=8.3.5 ; extra == 'testing' + - setuptools>=75.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ca/c4/8112b3c95db60a39c98db0641dd49bd4228f2fedb9d0ef5e4f3f48b52a0d/uv-0.11.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: uv + version: 0.11.18 + sha256: 9a4ee93dd0cc86046eb234ff8f3a5090c6cbb1466a825c5201aaf4ee4b45158d + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + name: iniconfig + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: setproctitle + version: 1.3.7 + sha256: 2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629 + requires_dist: + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + name: pytest + version: 9.0.3 + sha256: 2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1.0.1 + - packaging>=22 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + name: cfgv + version: 3.5.0 + sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + name: shellingham + version: 1.5.4 + sha256: 7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl + name: pyarrow + version: 23.0.1 + sha256: 7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e4/bd/0a7f877ec78e6910868e39a68c2a1964fa0737a4008ee1f3fd7e2fd8915f/hatch-1.17.0-py3-none-any.whl + name: hatch + version: 1.17.0 + sha256: cb742cc9113085c7dc88fde5a54e594846d8395102ae86e32179d5e72ff3c589 + requires_dist: + - backports-zstd>=1.0.0 ; python_full_version < '3.14' + - click>=8.0.6 + - distro>=1.0.0 ; sys_platform == 'linux' + - hatchling>=1.27.0 + - httpx2>=0.22.0 + - hyperlink>=21.0.0 + - keyring>=23.5.0 + - packaging>=24.2 + - pexpect~=4.8 + - platformdirs>=2.5.0 + - pyproject-hooks + - python-discovery>=1.1 + - rich>=11.2.0 + - shellingham>=1.4.0 + - tomli-w>=1.0 + - tomlkit>=0.11.1 + - userpath~=1.7 + - uv>=0.5.23 + - virtualenv>=21 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl + name: more-itertools + version: 11.1.0 + sha256: 4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl + name: wcmatch + version: '10.1' + sha256: 5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a + requires_dist: + - bracex>=2.1.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl + name: setproctitle + version: 1.3.7 + sha256: cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798 + requires_dist: + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl + name: pathspec + version: 1.1.1 + sha256: a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189 + requires_dist: + - hyperscan>=0.7 ; extra == 'hyperscan' + - typing-extensions>=4 ; extra == 'optional' + - google-re2>=1.1 ; extra == 're2' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl + name: jaraco-context + version: 6.1.2 + sha256: bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535 + requires_dist: + - backports-tarfile ; python_full_version < '3.12' + - pytest>=6,!=8.1.* ; extra == 'test' + - jaraco-test>=5.6.0 ; extra == 'test' + - portend ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.14 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl + name: setproctitle + version: 1.3.7 + sha256: 2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e + requires_dist: + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: ruff + version: 0.15.15 + sha256: 48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4 + requires_python: '>=3.7' diff --git a/pyproject.toml b/pyproject.toml index 2322bad..8a7fa27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,9 @@ foundry = [ submitit = [ "submitit>=1.5.1", ] +parsl = [ + "parsl>=2024.1.1", +] dev = [ "bump-my-version", "hatch", @@ -94,18 +97,32 @@ openff-interchange = ">=0.3" openff-units = "*" openff-nagl = "*" openmm = "*" +pdbfixer = "*" [tool.pixi.pypi-dependencies] mdfactory = { path = ".", editable = true } +parsl = ">=2024.1.1" [tool.pixi.tasks] test = "pytest -k 'not build' mdfactory" # lint = "ruff check ." # format-check = "ruff format --check ." +[tool.pixi.feature.docs.dependencies] +bun = ">=1.3" +pip = "*" + +[tool.pixi.feature.docs.tasks] +docs-install = { cmd = "bun install", cwd = "docs" } +docs-fumapy = { cmd = "python -m pip install ./docs/node_modules/fumadocs-python", depends-on = ["docs-install"] } +docs-dev = { cmd = "bun run dev", cwd = "docs", depends-on = ["docs-fumapy"] } +docs-build = { cmd = "bun run build", cwd = "docs", depends-on = ["docs-fumapy"] } +docs-generate = { cmd = "bun run docs:generate", cwd = "docs", depends-on = ["docs-fumapy"] } + [tool.pixi.environments] default = { solve-group = "default" } dev = { features = ["dev"], solve-group = "default" } +docs = { features = ["docs"], solve-group = "default" } # --- Ruff linter/formatter --------------------------------------------------- @@ -164,6 +181,7 @@ select = [ ignore = [ "D105", # Missing docstring in magic functions "D107", # Missing docstring in __init__ + "PLC0415", # Import not at top of file (lazy imports for optional deps) "PLR0912", # Too many branches "PLR0913", # Too many arguments in function definition "PLR0915", # Too many statements @@ -209,3 +227,4 @@ addopts = "-m 'not foundry_live and not slow' -v --cov mdfactory --cov-report xm [tool.coverage.run] omit = ["mdfactory/tests/*"] + diff --git a/workflows/build.nf b/workflows/build.nf deleted file mode 100644 index a574e3a..0000000 --- a/workflows/build.nf +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env nextflow - -nextflow.enable.dsl = 2 - -params.csv_file = null -params.output_dir = "./results" - -// Process 1: Generate folders and YAML files -process GENERATE_FOLDERS { - input: - path csv_file - - output: - stdout emit: output_info - - script: - // Resolve output directory relative to launch directory, not work directory - def launch_dir = workflow.launchDir - def output_path = params.output_dir.startsWith('/') ? params.output_dir : "${launch_dir}/${params.output_dir}" - """ - ABSOLUTE_OUTPUT="${output_path}" - echo "Creating output in: \$ABSOLUTE_OUTPUT" >&2 - - mkdir -p "\$ABSOLUTE_OUTPUT" - mdfactory prepare-build ${csv_file} --output "\$ABSOLUTE_OUTPUT" - - echo "Files created:" >&2 - ls -la "\$ABSOLUTE_OUTPUT" >&2 - - echo "\$ABSOLUTE_OUTPUT" - """ -} - -// Process 2: Read system directories from summary YAML -process READ_SYSTEM_DIRECTORIES { - input: - val output_dir - val summary_yaml - - output: - stdout emit: system_dirs - - script: - """ - python3 << 'EOF' -import yaml - -with open('${output_dir}/${summary_yaml}', 'r') as f: - data = yaml.safe_load(f) - -for dir_path in data.get('system_directory', []): - print(dir_path) -EOF - """ -} - -// Process 3: Build each system -process BUILD_FILES { - input: - val system_dir - - output: - val system_dir - - script: - """ - hash_name=\$(basename "${system_dir}") - cd "${system_dir}" - mdfactory build "\${hash_name}.yaml" - """ -} - -workflow { - if (!params.csv_file) { - error "Please provide --csv_file parameter" - } - - csv_ch = Channel.fromPath(params.csv_file, checkIfExists: true) - summary_yaml = file(params.csv_file).baseName + ".yaml" - - GENERATE_FOLDERS(csv_ch) - - output_dir = GENERATE_FOLDERS.out.output_info.map { it.trim() } - READ_SYSTEM_DIRECTORIES(output_dir, summary_yaml) - - system_dirs_ch = READ_SYSTEM_DIRECTORIES.out.system_dirs - .splitText() - .map { it.trim() } - .filter { it && it != "" } - - BUILD_FILES(system_dirs_ch) -} diff --git a/workflows/nextflow.config b/workflows/nextflow.config deleted file mode 100644 index 47a06a7..0000000 --- a/workflows/nextflow.config +++ /dev/null @@ -1,38 +0,0 @@ -// nextflow.config - Minimal configuration for MD Factory Pipeline -// Simple version with just the essentials - -// Show process output in terminal (so you can see echo commands) -process { - echo = true - - // Retry failed processes once - errorStrategy = 'retry' - maxRetries = 1 -} - -// Generate useful reports -report { - enabled = true - file = "nextflow-report.html" -} - -timeline { - enabled = true - file = "nextflow-timeline.html" -} - -dag { - enabled = true - file = "nextflow-dag.html" -} - -trace { - enabled = true - file = "nextflow-trace.txt" -} - -// Pipeline defaults -params { - csv_file = null - output_dir = './results' -} diff --git a/workflows/run_full_pipeline.sh b/workflows/run_full_pipeline.sh deleted file mode 100755 index b9fe9e4..0000000 --- a/workflows/run_full_pipeline.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -# optional: -# conda activate mdfactory - -# SET VARIABLES -CSV_FILE="test.csv" -OUTPUT_DIR="testfolder" - -# Derive summary YAML filename from CSV stem (basename only) -CSV_BASENAME="$(basename "${CSV_FILE}")" -SUMMARY_YAML="${CSV_BASENAME%.csv}.yaml" - -# Step 1: Build systems from CSV -nextflow run build.nf --csv_file "${CSV_FILE}" --output_dir "${OUTPUT_DIR}" - -# Step 2: Run GROMACS simulation chain -nextflow run simulate.nf -c simulate.config --base_dir "${OUTPUT_DIR}" --config_yaml "${OUTPUT_DIR}/${SUMMARY_YAML}" diff --git a/workflows/simulate.config b/workflows/simulate.config deleted file mode 100644 index 933765d..0000000 --- a/workflows/simulate.config +++ /dev/null @@ -1,38 +0,0 @@ -// nextflow.config - Minimal configuration for MD Factory Pipeline -// Simple version with just the essentials - - -// Generate useful reports -report { - enabled = true - file = "nextflow-report.html" -} - -timeline { - enabled = true - file = "nextflow-timeline.html" -} - -dag { - enabled = true - file = "nextflow-dag.html" -} - -trace { - enabled = true - file = "nextflow-trace.txt" -} - -// SLURM configuration for GROMACS pipeline -process { - executor = 'slurm' - queue = 'stdg_int' - echo = true - // Load required modules - beforeScript = 'module load gromacs-gpu' - clusterOptions = '--gres=gpu:1' - // Resource settings for all processes - cpus = 12 - time = '1h' - memory = '4GB' -} diff --git a/workflows/simulate.nf b/workflows/simulate.nf deleted file mode 100644 index d2b61df..0000000 --- a/workflows/simulate.nf +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env nextflow - -nextflow.enable.dsl = 2 - -// Parameters -params.config_yaml = "config.yaml" -params.base_dir = "." - -// Read YAML configuration -def readYamlConfig(yamlFile) { - def yaml = new org.yaml.snakeyaml.Yaml() - def config = yaml.load(new File(yamlFile).text) - return config.hash -} - -workflow { - // Read the YAML file and create channel - hash_list = readYamlConfig(params.config_yaml) - hash_channel = Channel.fromList(hash_list) - - // Create input channel with all required files for minimization - min_input_ch = hash_channel.map { hash -> - tuple( - hash, - file("${params.base_dir}/${hash}/em.mdp"), - file("${params.base_dir}/${hash}/system.pdb"), - file("${params.base_dir}/${hash}/topology.top") - ) - } - - // Run the GROMACS chain for each hash - minimization(min_input_ch) - nvt_equilibration(minimization.out) - npt_equilibration(nvt_equilibration.out) - production(npt_equilibration.out) -} - -process minimization { - tag "$hash" - - publishDir "${params.base_dir}/${hash}", mode: 'copy', overwrite: true, pattern: "min.*" - - input: - tuple val(hash), path(em_mdp), path(system_pdb), path(topology_top) - - output: - tuple val(hash), path("min.gro"), path("min.log"), emit: min_results - - script: - """ - # Files are already staged by Nextflow - use them directly - gmx grompp -f ${em_mdp} -c ${system_pdb} -p ${topology_top} -o min.tpr -maxwarn 1 - gmx mdrun -deffnm min -nt 12 - """ -} - -process nvt_equilibration { - tag "$hash" - - publishDir "${params.base_dir}/${hash}", mode: 'copy', overwrite: true, pattern: "nvt.*" - - input: - tuple val(hash), path(min_gro), path(min_log) - - output: - tuple val(hash), path("nvt.gro"), path("nvt.cpt"), path("nvt.log"), emit: nvt_results - - script: - def absolute_base_dir = file(params.base_dir).toAbsolutePath() - """ - # Stage additional required files - ln -s ${absolute_base_dir}/${hash}/nvt.mdp . - ln -s ${absolute_base_dir}/${hash}/topology.top . - - # Preprocessing for NVT - gmx grompp -f nvt.mdp -c ${min_gro} -r ${min_gro} -p topology.top -o nvt.tpr -maxwarn 1 - - # Run NVT equilibration - gmx mdrun -deffnm nvt -nt 12 - """ -} - -process npt_equilibration { - tag "$hash" - - publishDir "${params.base_dir}/${hash}", mode: 'copy', overwrite: true, pattern: "npt.*" - - input: - tuple val(hash), path(nvt_gro), path(nvt_cpt), path(nvt_log) - - output: - tuple val(hash), path("npt.gro"), path("npt.cpt"), path("npt.log"), emit: npt_results - - script: - def absolute_base_dir = file(params.base_dir).toAbsolutePath() - """ - # Stage additional required files - ln -s ${absolute_base_dir}/${hash}/npt.mdp . - ln -s ${absolute_base_dir}/${hash}/topology.top . - - # Preprocessing for NPT - gmx grompp -f npt.mdp -c ${nvt_gro} -r ${nvt_gro} -t ${nvt_cpt} -p topology.top -o npt.tpr -maxwarn 2 - - # Run NPT equilibration - gmx mdrun -deffnm npt -nt 12 - """ -} - -process production { - tag "$hash" - - publishDir "${params.base_dir}/${hash}", mode: 'copy', overwrite: true, pattern: "prod.*" - - input: - tuple val(hash), path(npt_gro), path(npt_cpt), path(npt_log) - - output: - tuple val(hash), path("prod.gro"), path("prod.xtc"), path("prod.cpt"), path("prod.log"), path("prod.edr"), emit: prod_results - - script: - def absolute_base_dir = file(params.base_dir).toAbsolutePath() - """ - # Stage additional required files - ln -s ${absolute_base_dir}/${hash}/md.mdp . - ln -s ${absolute_base_dir}/${hash}/topology.top . - - # Preprocessing for production - gmx grompp -f md.mdp -c ${npt_gro} -t ${npt_cpt} -p topology.top -o prod.tpr -maxwarn 2 - - # Run production simulation - gmx mdrun -deffnm prod -nt 12 -v - """ -}