diff --git a/.agents/rules/architecture.md b/.agents/rules/architecture.md new file mode 100644 index 0000000..e1e71fa --- /dev/null +++ b/.agents/rules/architecture.md @@ -0,0 +1,20 @@ +# Architecture and Code Formatting Rules + +When writing or modifying code in this project, adhere strictly to the following rules: + +## 1. Core Frameworks & Tooling +- **PPO & CleanRL**: Proximal Policy Optimization (PPO) is the baseline algorithm. Use CleanRL as the starting framework for PPO, ensuring adaptation for continuous action spaces. +- **JAX / Flax**: All Artificial Neural Network (ANN) controller architectures must be implemented using Flax (neural networks in JAX). Ensure full compatibility with the JAX/Flax ecosystem. +- **MuJoCo**: The simulation environment uses a MuJoCo brittle star. Ensure that XML structures (sensors, actuators, joints, morphology) respect realistic constraints and adhere to the project requirements. + +## 2. Clean Code Principles +- **Naming Conventions**: Variables and functions must have consistent, intention-revealing names. A descriptive name is universally preferred over a short name with an explanatory comment. +- **Single Responsibility Function Design**: Functions must be modular. Minimize arguments and completely avoid boolean flag arguments that control execution behavior. +- **Commenting**: Code explains the "how". Comments are strictly reserved for explaining the "why". +- **No Commented-out Code**: The AI must **never** generate commented-out or dead code. Delete it using version control instead. +- **YAGNI & Complexity Management**: You Aren't Gonna Need It. Avoid premature optimization or unnecessary abstraction. Only introduce complexity with documented justification. Break large functions into small, testable blocks. +- **No Notebooks for Core Logic**: Jupyter Notebooks are explicitly forbidden for general software development as they discourage modularity. They should only be used for prototyping, tutorials, or post-processing analysis. + +## 3. Formatting & Linting +- **Ruff**: Output perfectly formatted code adhering to the Google style standard. Always format and lint the code using `ruff` (see `pyproject.toml` and `ruff.toml`). +- **Separation of Concerns**: Configuration code must be completely separated from implementation logic. Core logic must never be mixed with scripts or notebooks. diff --git a/.agents/rules/general.md b/.agents/rules/general.md new file mode 100644 index 0000000..c62f3ed --- /dev/null +++ b/.agents/rules/general.md @@ -0,0 +1,15 @@ +# General AI Agent Rules + +When assisting with this project, the AI Agent must strictly abide by these overarching operational rules: + +## 1. Scientific Integrity +- **No Hallucinations**: You must never fabricate results, hallucinate citations, or generate false empirical claims. Do not guess what happened if a process fails; rely strictly on outputs and logs. + +## 2. Agent Operational Constraints +- **Absolute Paths**: Always use absolute paths when making tool calls or reading/writing files. +- **Refactoring Guardrails**: Do not commence massive files/directory refactors or major system migrations without explicitly communicating the plan and asking for user clarification or approval first. +- **No Boilerplate Feedback**: The AI must not produce generic boilerplate summaries or overly generic advice. Ensure all outputs are completely contextual, robust, and well-reasoned. + +## 3. Communication +- **Artifacts and UI**: Use artifacts (like `implementation_plan.md` or `task.md`) appropriately for tracking design phases and updates. +- **Explicit Documenting**: If performing design choices, list them explicitly. Keep the output focused on the exact task. diff --git a/.agents/rules/git-workflow.md b/.agents/rules/git-workflow.md new file mode 100644 index 0000000..312e457 --- /dev/null +++ b/.agents/rules/git-workflow.md @@ -0,0 +1,25 @@ +# Git Workflow & Repository Structure Rules + +When performing Git operations and managing the repository layout, follow these rules: + +## 1. Committing Practices +- **Frequent & Small**: Produce small, logical commits instead of massive monolithic ones. +- **Conventional Commits**: Commit messages must adhere to the Conventional Commits specification (e.g., `feat: ...`, `fix: ...`, `refactor: ...`). +- **Single Functionality**: Each commit should relate to exactly one piece of functionality or distinct structural change. + +## 2. Branching & Merging +- **Branch `dev`**: The `dev` branch is the primary integration branch for pushing and merging code. +- **Branch `main`**: Only stable, finalized releases may be pushed to `main`. +- **Feature Branches**: Organize distinct work into logical feature branches when pushing to the remote server, maintaining an organized Git history. + +## 3. Artifact Management & Exclusions +- **LFS Only**: Data files, trained models, and large datasets must **never** be committed directly to Git. Ensure they are tracked with Git Large File Storage (LFS). **CRITICAL**: Every developer and AI agent must have `git-lfs` installed locally for the repository hooks to successfully pull these large files. Run `git lfs install` after cloning or setting up your environment. + +## 4. Repository Layout Strictness +Ensure generated code is meticulously placed in the correct directories: +- `src/` for algorithms, network designs, and core agent modules. +- `env/` for MuJoCo wrappers and environment definitions. +- `config/` for experiment configurations (using json, gin, or yaml). +- `experiments/` for executable scripts. +- `docs/` for ReadTheDocs or Doxygen documentation, and decision logs. +- `tests/` for unit tests and verification scripts. diff --git a/.agents/rules/method.md b/.agents/rules/method.md new file mode 100644 index 0000000..40e4b20 --- /dev/null +++ b/.agents/rules/method.md @@ -0,0 +1,14 @@ +# Methodology and Process Rules + +The agent must adhere to the following scientific and operational practices, focused on robustness and reproducibility: + +## 1. Scientific Context & Methodology +- **Research Focus**: Maintain focus on the project's objective: studying how controller modularity affects learning speed, coordination, and fault tolerance in brittle-star locomotion. +- **Hypothesis-Driven Design**: Base execution on clear hypotheses. Document all design decisions prior to implementation (in `/docs/decisions/` or via Artifacts/Plans). +- **Scaffolding Approach**: Start development with simple setups before scaling to complex environments and varying morphologies. +- **Value of Negative Results**: Understand that a controller failing to learn locomotion, when coupled with a thorough analysis of the failure, holds strong scientific value. Do not artificially force a positive result. + +## 2. Reproducibility Protection +- **Dependency Management (uv)**: This project strictly prefers `uv`. Do **not** manually modify the `uv.lock` file. Add dependencies via `uv add ` and sync environments via `uv sync --frozen` (or via devcontainers). +- **Consistent Initialization**: The AI must avoid hidden randomness. Ensure that all runs use consistent seed initialization. +- **Experiment Tracking**: Use Weights & Biases (wandb) for tracking and logging all experiments and parameters. Ensure every experiment-friendly parameter is properly logged. diff --git a/.agents/skills/lint/SKILL.md b/.agents/skills/lint/SKILL.md new file mode 100644 index 0000000..b5a70cf --- /dev/null +++ b/.agents/skills/lint/SKILL.md @@ -0,0 +1,24 @@ +--- +name: Lint and Format Code +description: Instructions for checking code style and formatting using Ruff. +--- + +# Skill: Lint and Format Code + +The goal of this skill is to enforce the project's adherence to the Google style standard and to maintain high code quality across the python source files. + +## Instructions + +1. **Format Code**: To automatically format all Python files according to the `ruff.toml` specifications: + ```bash + uv run ruff format . + ``` + +2. **Check for Lints / Auto-fix**: To check the repository for style violations and automatically fix safe corrections: + ```bash + uv run ruff check --fix . + ``` + +## Important Considerations +- The `ruff.toml` file at the root handles all lint and format configuration. Do not ignore configurations when applying fixes. +- If Ruff points out complex errors that cannot be auto-fixed, analyze the code and manually address the violations, prioritizing descriptive naming and adherence to the single-responsibility principle. diff --git a/.agents/skills/test/SKILL.md b/.agents/skills/test/SKILL.md new file mode 100644 index 0000000..93a939b --- /dev/null +++ b/.agents/skills/test/SKILL.md @@ -0,0 +1,25 @@ +--- +name: Run Tests +description: Instructions for executing the project test suite to verify code correctness. +--- + +# Skill: Run Tests + +The goal of this skill is to verify that the project is functioning correctly after development changes. + +## Instructions + +1. **Verify Environment**: The project operates dynamically with hardware acceleration (GPU via JAX) and uses `uv` for dependency/execution management. +2. **Execute Tests**: + - To run basic verification (e.g., checking JAX initialization and hardware detection), run the test script: + ```bash + uv run python -m tests.test_jax_init + ``` + - If a broader suite of modular tests is added (e.g., `pytest`), execute tests in the `tests/` directory with: + ```bash + uv run pytest tests/ + ``` + +## Important Considerations +- If you are running tests inside the local environment without a Devcontainer, verify whether `uv sync --frozen` (for CPU) or `uv sync --frozen --extra cuda` (for GPU) has been executed to avoid import errors. +- Do not run bare `python ...` without `uv run` locally, unless you are strictly operating inside a pre-activated `.venv` inside a Devcontainer. diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 0000000..c30e5a9 --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["@commitlint/config-conventional"] +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..bebd64c --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,30 @@ +FROM mcr.microsoft.com/devcontainers/python:3.12 + +# Install uv for dependency management +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Install system dependencies for JAX, OpenGL, and Mujoco +RUN apt-get update && apt-get install -y --no-install-recommends \ + libgl1 \ + libosmesa6-dev \ + libglew-dev \ + libglfw3 \ + patchelf \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set environment variables for hardware acceleration and rendering +ENV LD_LIBRARY_PATH=/usr/lib/nvidia +ENV NVIDIA_VISIBLE_DEVICES=all +ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics + +# Set the workspace path (defaults to /workspaces/project but can be overridden) +ARG WORKSPACE_PATH=/workspaces/project +ENV WORKSPACE_PATH=$WORKSPACE_PATH +ENV PATH="$WORKSPACE_PATH/.venv/bin:$PATH" + +# Ensure the workspace and .venv directories exist and are owned by the vscode user +# This prevents permission errors when the postCreateCommand runs uv sync +RUN mkdir -p $WORKSPACE_PATH/.venv && chown -R vscode:vscode $WORKSPACE_PATH + +USER vscode +WORKDIR $WORKSPACE_PATH diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..be1fa12 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,49 @@ +{ + "name": "Brittle Star JAX/CUDA", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + "WORKSPACE_PATH": "/workspaces/${localWorkspaceFolderBasename}" + } + }, + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "charliermarsh.ruff", + "ms-python.vscode-pylance", + "ms-toolsai.jupyter" + ], + "settings": { + "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python", + "python.analysis.localRoot": "${containerWorkspaceFolder}", + "python.analysis.extraPaths": [ + "${containerWorkspaceFolder}/.venv/lib/python3.12/site-packages" + ] + } + }, + "jetbrains": { + "plugins": [ + "Pythonid", + "fleet.python", + "com.koxudaxi.ruff" + ] + } + }, + "remoteUser": "vscode", + "runArgs": [ + "--gpus", + "all" + ], + // Ensure the .venv persists using a named volume for performance and parity + "mounts": [ + "source=${localWorkspaceFolderBasename}-venv,target=${containerWorkspaceFolder}/.venv,type=volume" + ], + // Invoke the hardware-aware sync script + "postCreateCommand": "bash ./.devcontainer/post-create.sh", + "features": { + "ghcr.io/devcontainers/features/common-utils:1": {} + } +} \ No newline at end of file diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 0000000..576bad1 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +# Detect if a GPU is available via nvidia-smi. +# This works for Linux hosts and Windows (WSL2) with NVIDIA Container Toolkit. +# On Mac (Apple Silicon) or systems without NVIDIA GPUs, this will skip the 'cuda' extra. +if command -v nvidia-smi &> /dev/null && nvidia-smi &> /dev/null; then + echo "GPU detected. Syncing with 'cuda' extra..." + uv sync --frozen --extra cuda +else + echo "No GPU detected or nvidia-smi failed. Syncing without 'cuda' extra..." + uv sync --frozen +fi + +echo "Environment synced successfully." + +# Install pre-commit hooks so they are active in the devcontainer +uv run pre-commit install diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0fc68fc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Model weights +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text + +# Media +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.avi filter=lfs diff=lfs merge=lfs -text + +# Datasets +*.csv filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..2b6b033 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint + +on: + pull_request: + types: [opened, synchronize, ready_for_review] + branches: + - main + - dev + +jobs: + ruff: + if: ${{ !github.event.pull_request.draft }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run Ruff check + uses: astral-sh/ruff-action@v1 + with: + args: "check" + - name: Run Ruff format check + uses: astral-sh/ruff-action@v1 + with: + args: "format --check" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..bcf6f2f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: Test + +on: + pull_request: + types: [opened, synchronize, ready_for_review] + branches: + - main + - dev + +jobs: + pytest: + if: ${{ !github.event.pull_request.draft }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "latest" + + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install dependencies + run: uv sync --frozen + + - name: Run tests with pytest + run: uv run pytest tests/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c86e484 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: + - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook + rev: v9.16.0 + hooks: + - id: commitlint + stages: [commit-msg] + additional_dependencies: ["@commitlint/config-conventional"] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.9 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: no-commit-to-branch + args: ['--branch', 'main', '--branch', 'dev'] diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..efd1399 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contribution Guidelines + +This document outlines the contribution protocols for the scientific software engineering project focusing on bio-inspired control architectures for brittle-star-like robots. The primary objective of this project is to produce scientific insight, rather than a commercial product. + +## 1. Scientific Context & Methodology + +* **Research Focus:** The goal is to study how controller modularity affects learning speed, coordination, and fault tolerance in brittle-star locomotion. +* **Hypothesis-Driven Design:** Clear hypotheses must dictate a structured methodology and rigorous evaluation. All design decisions must be formally documented prior to implementation. +* **Scaffolding Approach:** Development must start with simple setups before progressively increasing the complexity of environments and morphologies. +* **Evaluation of Results:** Negative results possess scientific validity when thoroughly analyzed. If a controller fails to learn locomotion, providing a comprehensive analysis of the failure is considered a strong scientific contribution. +* **Reproducibility:** Contributors must utilize fixed library versions. Configuration systems (such as json, gin, or yaml) must be employed to ensure reproducible runs. + +## 2. Clean Code & Code Quality + +Code readability is paramount, as code is read far more frequently than it is written. + +* **Naming Conventions:** Variables and functions must utilize consistent, intention-revealing names. A long, descriptive name is strictly preferred over a short name accompanied by a comment. +* **Function Design:** Functions must be modular and adhere to the single responsibility principle. Arguments must be minimized, and boolean flag arguments controlling behavior should be avoided. +* **Commenting:** Code must document the "how," while comments are strictly reserved for documenting the "why". Commented-out code is prohibited and must be deleted via version control. +* **YAGNI:** Contributors must adhere to the "You Aren't Gonna Need It" (YAGNI) principle and actively avoid premature optimization. +* **Notebooks:** Jupyter Notebooks are strictly limited to quick prototyping, tutorials, demonstrations, or post-processing analysis. They are explicitly forbidden for general software development because they discourage modularity. + +## 3. Version Control & Repository Structure + +* **Git Practices:** Commits must be frequent and small. Each commit should relate to exactly one piece of functionality. +* **Branching Strategy:** The `dev` branch serves as the integration branch for pushing and merging code. Only stable releases may be pushed to the `main` branch. +* **Artifact Management:** Data files, trained models, and large datasets must never be committed directly to Git. Git Large File Storage (LFS) must be used for tracking large files. **All developers must have `git-lfs` installed locally** (see `DEVELOPMENT.md` for setup). +* **Repository Layout:** The repository must maintain the following core directories: `src/` for algorithms, `env/` for MuJoCo wrappers, `config/` for experiment configurations, `experiments/` for scripts, `docs/` for Doxygen or ReadTheDocs documentation, and `tests/` for unit tests. + +## 4. Architecture & Tooling + +* **Algorithms & Frameworks:** Proximal Policy Optimization (PPO) is the recommended baseline algorithm. CleanRL should be used as a starting point and adapted for continuous action spaces. All Artificial Neural Network (ANN) controller architectures must be implemented using Flax. +* **Simulation:** The simulation environment utilizes a MuJoCo brittle star. XML MuJoCo structures must remain realistic and respect morphological constraints. +* **Experiment Tracking:** Weights & Biases (wandb) must be utilized for tracking and logging all experiments. +* **Code Styling:** All code must conform to the chosen style guide (i.e. Google standard). This is enforced using build tools and pre-commit hooks such as flake8, black, or isort. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..6ec220b --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,61 @@ +# Development Guide + +This guide outlines how to set up the development environment for this project, prioritizing **reproducible builds**, **environment parity**, and **cross-hardware compatibility**. + +## Reproducibility &uv + +This project uses [uv](https://github.com/astral-sh/uv) to manage dependencies and virtual environments. The `uv.lock` file is the absolute source of truth for package versions and must always be committed. + +### Source of Truth + +- **Never modify `uv.lock` manually.** +- To add a dependency, run `uv add `. +- To update dependencies, run `uv lock --upgrade`. +- To sync your environment with the lockfile, run `uv sync --frozen`. + +## Git LFS (Critical) + +**All developers must have Git LFS installed locally.** This repository tracks model weights (`.pt`, `.safetensors`, etc.), recordings (`.mp4`), and datasets using Git LFS. + +- **Setup:** Run `git lfs install` after cloning this repository. If you are using the `.devcontainer` or `flake.nix`, LFS is typically available automatically. +- If you clone without LFS installed, run `git lfs pull` after installation to fetch the actual data files instead of the small pointer files. + +## Devcontainer Setup (Recommended) + +The devcontainer provides an identical experience to local development but with all system dependencies pre-configured. It automatically detects your hardware (GPU vs CPU) and syncs the appropriate dependencies. + +### Prerequisites + +- Docker Desktop or Docker Engine. +- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (for GPU support). + +### Setup for VS Code + +1. Install the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension. +2. Open the project and click **Reopen in Container**. +3. On first launch, the `post-create.sh` script will: + - Detect if an NVIDIA GPU is available via `nvidia-smi`. + - Run `uv sync --frozen --extra cuda` if a GPU is found. + - Run `uv sync --frozen` otherwise. +4. The environment is stored in a **named volume** for `.venv` to ensure persistence and performance. + +### Setup for JetBrains IDEs + +1. The IDE will detect the `.devcontainer/devcontainer.json` file. +2. The environment is pre-configured to point to `/workspaces/project/.venv`. +3. The hardware-aware sync will run automatically during container creation. + +## Local Development (Alternative) + +If you prefer not to use Docker: +1. Install [uv](https://docs.astral.sh/uv/getting-started/installation/). +2. Run `uv sync --frozen` (CPU) or `uv sync --frozen --extra cuda` (GPU). + +## Hardware Acceleration (JAX) + +Verify your setup by running the JAX initialization test: + +```bash +uv run pytest tests/test_jax_init.py +``` +In the devcontainer, this will succeed on both CPU and GPU. A `GpuDevice` is expected if a GPU is detected and the `cuda` extra was installed. diff --git a/flake.nix b/flake.nix index c766ec9..bdcdf41 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,10 @@ # Editor of your choice (nix-jetbrains-plugins.lib.buildIdeWithPlugins pkgs "pycharm" pluginList) ]; + + shellHook = '' + uv run pre-commit install + ''; }; }); } diff --git a/pyproject.toml b/pyproject.toml index a44dd05..240725d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dependencies = [ "biorobot==0.4.2", "evosax==0.2.0", "ipykernel==7.2.0", - "jax[cuda13]==0.9.0.1", + "jax==0.9.0.1", "matplotlib==3.10.8", "mediapy==1.2.6", "pyopengl>=3.1.10", @@ -16,7 +16,14 @@ dependencies = [ "wandb==0.24.2", ] +[project.optional-dependencies] +cuda = [ + "jax[cuda13]==0.9.0.1", +] + [dependency-groups] dev = [ + "pre-commit>=4.0.0", + "pytest>=8.0.0", "ruff>=0.15.2", ] diff --git a/ruff.toml b/ruff.toml index d3c196a..7bc4795 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,3 +1,4 @@ +[lint] extend-select = [ # "PLC0103", # invalid-name # "PLC0104", # disallowed-name @@ -40,10 +41,10 @@ extend-select = [ "PLC2401", # non-ascii-name # "PLC2403", # non-ascii-module-import # "PLC2503", # bad-file-encoding - "PLC2801", # unnecessary-dunder-call + # "PLC2801", # unnecessary-dunder-call (requires preview) # "PLC3001", # unnecessary-lambda-assignment "PLC3002", # unnecessary-direct-lambda-call - "E999", # syntax-error + # "E999", # syntax-error (removed from ruff) # "PLE0011", # unrecognized-inline-option # "PLE0013", # bad-plugin-value # "PLE0014", # bad-configuration-section @@ -130,7 +131,7 @@ extend-select = [ # "PLE1137", # unsupported-assignment-operation # "PLE1138", # unsupported-delete-operation # "PLE1139", # invalid-metaclass - "PLE1141", # dict-iter-missing-items + # "PLE1141", # dict-iter-missing-items (requires preview) "PLE1142", # await-outside-async # "PLE1143", # unhashable-member # "PLE1144", # invalid-slice-step @@ -168,7 +169,7 @@ extend-select = [ # "PLE3102", # positional-only-arguments-expected # "PLE3701", # invalid-field-call # "PLE4702", # modified-iterating-dict - "PLE4703", # modified-iterating-set + # "PLE4703", # modified-iterating-set (requires preview) # "PLF0001", # fatal # "PLF0002", # astroid-error # "PLF0010", # parse-error @@ -210,7 +211,7 @@ extend-select = [ # "PLW0238", # unused-private-member # "PLW0239", # overridden-final-method # "PLW0240", # subclassed-final-class - "PLW0244", # redefined-slots-in-subclass + # "PLW0244", # redefined-slots-in-subclass (requires preview) "PLW0245", # super-without-brackets # "PLW0246", # useless-parent-delegation # "PLW0301", # unnecessary-semicolon @@ -274,7 +275,7 @@ extend-select = [ "PLW1508", # invalid-envvar-default "PLW1509", # subprocess-popen-preexec-fn # "PLW1510", # subprocess-run-check - "PLW1514", # unspecified-encoding + # "PLW1514", # unspecified-encoding (requires preview) # "PLW1515", # forgotten-debug-statement # "PLW1518", # method-cache-max-size-none "PLW2101", # useless-with-lock @@ -298,7 +299,7 @@ extend-select = [ # "PLW4906", # deprecated-attribute ] -ignore = [ +extend-ignore = [ # "PLC0116", # missing-function-docstring # "PLC0200", # consider-using-enumerate # "PLC0305", # trailing-newlines diff --git a/tests/test_jax_init.py b/tests/test_jax_init.py new file mode 100644 index 0000000..e672b55 --- /dev/null +++ b/tests/test_jax_init.py @@ -0,0 +1,7 @@ +import jax + + +def test_jax_initializes(): + """Verify that JAX initializes and exposes at least one device.""" + devices = jax.devices() + assert len(devices) > 0, "JAX should expose at least one device" diff --git a/uv.lock b/uv.lock index 164ef5f..ce5ecc2 100644 --- a/uv.lock +++ b/uv.lock @@ -14,7 +14,7 @@ dependencies = [ { name = "biorobot" }, { name = "evosax" }, { name = "ipykernel" }, - { name = "jax", extra = ["cuda13"] }, + { name = "jax" }, { name = "matplotlib" }, { name = "mediapy" }, { name = "pyopengl" }, @@ -22,8 +22,15 @@ dependencies = [ { name = "wandb" }, ] +[package.optional-dependencies] +cuda = [ + { name = "jax", extra = ["cuda13"] }, +] + [package.dev-dependencies] dev = [ + { name = "pre-commit" }, + { name = "pytest" }, { name = "ruff" }, ] @@ -32,16 +39,22 @@ requires-dist = [ { name = "biorobot", specifier = "==0.4.2" }, { name = "evosax", specifier = "==0.2.0" }, { name = "ipykernel", specifier = "==7.2.0" }, - { name = "jax", extras = ["cuda13"], specifier = "==0.9.0.1" }, + { name = "jax", specifier = "==0.9.0.1" }, + { name = "jax", extras = ["cuda13"], marker = "extra == 'cuda'", specifier = "==0.9.0.1" }, { name = "matplotlib", specifier = "==3.10.8" }, { name = "mediapy", specifier = "==1.2.6" }, { name = "pyopengl", specifier = ">=3.1.10" }, { name = "pyopengl-accelerate", specifier = ">=3.1.10" }, { name = "wandb", specifier = "==0.24.2" }, ] +provides-extras = ["cuda"] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.15.2" }] +dev = [ + { name = "pre-commit", specifier = ">=4.0.0" }, + { name = "pytest", specifier = ">=8.0.0" }, + { name = "ruff", specifier = ">=0.15.2" }, +] [[package]] name = "absl-py" @@ -151,6 +164,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -285,6 +307,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "dm-control" version = "1.0.37" @@ -406,6 +437,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl", hash = "sha256:14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae", size = 2511, upload-time = "2023-02-27T18:28:39.447Z" }, ] +[[package]] +name = "filelock" +version = "3.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, +] + [[package]] name = "flax" version = "0.12.2" @@ -511,6 +551,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/3f/efeb7c6801c46e11bd666a5180f0d615f74f72264212f74f39586c6fda9d/glfw-2.10.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.py39.py310.py311.py312.py313.py314-none-manylinux_2_28_x86_64.whl", hash = "sha256:ce6724bb7cb3d0543dcba17206dce909f94176e68220b8eafee72e9f92bcf542", size = 243522, upload-time = "2026-01-28T05:58:03.517Z" }, { url = "https://files.pythonhosted.org/packages/cf/b9/b04c3aa0aad2870cfe799f32f8b59789c98e1816bbce9e83f4823c5b840b/glfw-2.10.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.py39.py310.py311.py312.py313.py314-none-win32.whl", hash = "sha256:fca724a21a372731edb290841edd28a9fb1ee490f833392752844ac807c0086a", size = 552682, upload-time = "2026-01-28T05:58:05.649Z" }, { url = "https://files.pythonhosted.org/packages/bd/e1/6d6816b296a529ac9b897ad228b1e084eb1f92319e96371880eebdc874a6/glfw-2.10.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.py39.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:823c0bd7770977d4b10e0ed0aef2f3682276b7c88b8b65cfc540afce5951392f", size = 559464, upload-time = "2026-01-28T05:58:07.261Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a8/d4dab8a58fc2e6981fc7a58c4e56ba9d777fb24931cec6a22152edbb3540/glfw-2.10.0-py2.py3-none-macosx_10_6_intel.whl", hash = "sha256:a0d1f29f206219cc291edfb6cace663a86da2470632551c998e3db82d48ea177", size = 105288, upload-time = "2026-03-10T17:21:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/14/61/68d35e001872a7705112418da236fa2418d4f2e5419f8b2837f9b81bb3da/glfw-2.10.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:d28d6f3ef217e64e35dc6fd0a7acb4cec9bfe7cd14dd9b35a7228a87002de154", size = 102139, upload-time = "2026-03-10T17:21:21.645Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/ca5984081aaae07c9d371cb11dc4e4ff603510678ed9b73e58b6c351fe63/glfw-2.10.0-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:f968b522bb6a0e04aaf4dcac30a476d7229308bb2bac406a60587debb5a61e29", size = 229998, upload-time = "2026-03-10T17:21:23.549Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c4/82ac75fdcfba2896da7a573c0fc7f8ceb8f77ead6866d500d06c32f1c464/glfw-2.10.0-py2.py3-none-manylinux2014_x86_64.whl", hash = "sha256:68cf3752bdadb6f4bc0a876247c28c88c7251ac39f8af076ed938fdfd71e72dd", size = 241944, upload-time = "2026-03-10T17:21:26.102Z" }, + { url = "https://files.pythonhosted.org/packages/e3/96/9f691823cca5eb6a08f346bd0ff03b78032db9370b509a1e9c8976fb20a5/glfw-2.10.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:44d98de5dbf8f727e0cb29f9b29d29528ea7570f2e6f42f8430a69df05f12b48", size = 231009, upload-time = "2026-03-10T17:21:28.481Z" }, + { url = "https://files.pythonhosted.org/packages/3f/93/977b9e679e356871d428ae7a1139ec767dd5177bed58a6344b4d2199e00f/glfw-2.10.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cca5158d62189e08792b1ae54f92307a282921a0e7783315b467e21b0a381c88", size = 243480, upload-time = "2026-03-10T17:21:30.538Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bd/cea9569c8f2188b0a104472951420434a3e1f5cf26f5836ef9d7227a1a30/glfw-2.10.0-py2.py3-none-win32.whl", hash = "sha256:5e024509989740e8e7b86cc4aab508195495f79879072b0e1f68bd036a2916ad", size = 552641, upload-time = "2026-03-10T17:21:32.653Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9b/4366ad3e1c0688146c70aa6143584d6a8d88583b9390f106250e25a3d5cd/glfw-2.10.0-py2.py3-none-win_amd64.whl", hash = "sha256:7f787ee8645781f10e8800438ce4357ab38c573ffb191aba380c1e72eba6311c", size = 559423, upload-time = "2026-03-10T17:21:34.766Z" }, ] [[package]] @@ -537,6 +585,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, ] +[[package]] +name = "identify" +version = "2.6.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -568,6 +625,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "ipykernel" version = "7.2.0" @@ -983,6 +1049,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numpy" version = "2.4.2" @@ -1285,6 +1360,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -1439,6 +1539,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1451,6 +1567,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1786,6 +1915,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, ] +[[package]] +name = "virtualenv" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, +] + [[package]] name = "wandb" version = "0.24.2"